Якщо у вас дуже мало циклів, ось алгоритм, який використовуватиме менше місця, але для його завершення потрібно значно більше часу.
[Редагувати] Мій попередній аналіз часу пропустив вирішальну вартість визначення того, чи є вузли, які ми відвідуємо, серед тих, що раніше були відібрані; ця відповідь була дещо переглянута, щоб виправити це.
Ми знову ітерація через всі елементи S . Коли ми досліджуємо орбіти елементів s ∈ S , ми робимо вибірку з побудованих нами вузлів, щоб можна було перевірити, чи стикаємося ми знову. Ми також ведемо список зразків із "компонентів" - об'єднань орбіт, які закінчуються в загальному циклі (і, отже, рівнозначні циклам), які раніше були відвідані.
Ініціалізувати порожній список компонентів, complist
. Кожен компонент представлений колекцією зразків із цього компонента; ми також підтримуємо дерево пошуку, samples
яке зберігає всі ті елементи, які були обрані як зразки для того чи іншого компонента. Нехай G - послідовність цілих чисел до n , для яких належним чином визначити належність шляхом обчислення деякого булевого предиката; наприклад, потужності 2 або досконалі p- й потужності для деякого цілого p . Для кожного s ∈ S зробіть наступне:
- Якщо s знаходиться
samples
, перейдіть до кроку №5.
- Ініціалізуйте порожній список
cursample
, ітератор j ← f ( s ) та лічильник t ← 1.
- Якщо j не в
samples
:
- Якщо t ∈ G , вставте j в обидва cursample
і samples
.
- приріст t і задаємо j ← f (j) .
- Перевірте, чи є j
cursample
. Якщо ні, то ми зіткнулися з раніше вивченим компонентом: ми перевіряємо, до якого компонента j належить, і вставляємо всі елементи cursample
у відповідний елемент, complist
щоб його доповнити. В іншому випадку ми знову зіткнулися з елементом з поточної орбіти, це означає, що ми пройшли цикл хоча б один раз, не зустрічаючи жодних представників раніше виявлених циклів: ми вставляємо cursample
, як колекцію зразків із щойно знайденого компонента, в complist
.
- Перейдіть до іншого сек ∈ S .
Для n = | S |, нехай X (n) - монотонна зростаюча функція, що описує очікувану кількість циклів ( наприклад, X (n) = n 1/3 ), і нехай Y (n) = y (n) log ( n ) ∈ Ω ( X (n) log ( n )) є монотонною функцією збільшення, що визначає ціль для використання пам'яті ( наприклад, y (n) = n 1/2 ). Нам потрібно y (n) ∈ Ω ( X (n) ), оскільки для зберігання одного зразка з кожного компонента буде потрібно щонайменше пробіл X (n) log ( n ).
Чим більше елементів орбіти ми відбираємо, тим більша ймовірність, що ми швидко відберемо зразок у циклі в кінці орбіти і тим самим швидко виявимо цей цикл. З точки зору асимптотики, то має сенс отримати стільки зразків, скільки дозволяють наші межі пам’яті: ми також можемо встановити G, щоб очікувані y (n) елементи були меншими ніж n .
- Якщо очікується, що максимальна довжина орбіти в S буде L , ми можемо дозволити G - цілочисельні кратні L / y (n) .
- Якщо очікуваної довжини немає, ми можемо просто пробувати вибірку один раз кожні n / y (n)елементи; це в будь-якому випадку верхня межа інтервалів між зразками.
Якщо, шукаючи нового компонента, ми почнемо переходити елементи S, які ми раніше відвідали (або від нового компонента, який було виявлено, або старого, термінальний цикл якого вже знайдено), це займе щонайбільше n / y ( n) ітерації зіткнення з раніше відібраним елементом; це тоді верхня межа кількості разів, для кожної спроби знайти новий компонент ми перетинаємо надлишкові вузли. Оскільки ми робимо n таких спроб, то ми будемо зайво відвідувати елементи S не більше n 2 / y (n) разів.
Робота, необхідна для перевірки на членство в samples
- це O ( y (n) log y (n) ), яку ми повторюємо при кожному відвідуванні: сукупна вартість цієї перевірки - O ( n 2 log y (n) ). Існує також вартість додавання зразків до відповідних колекцій, що сукупно становить O ( y (n) log y (n) ). Нарешті, щоразу, коли ми знову стикаємося з раніше виявленим компонентом, ми повинні витрачати до X (n) log * y (n) часу, щоб визначити, який компонент ми знову розкрили; оскільки це може статися в n разів, сукупна робота, що займається, обмежена n X (n) log y (n) .
Таким чином, сукупна робота, яка виконується з перевірки того, чи перебувають у нас вузли серед зразків, домінують у процесі виконання: це коштує O ( n 2 log y (n) ). Тоді нам слід зробити y (n) якомога меншим, тобто O ( X (n) ).
Таким чином, можна перерахувати кількість циклів (що таке саме, як кількість компонентів, що закінчуються цими циклами) в просторі O ( X (n) log ( n )), приймаючи O ( n 2 log X (n) ) час для цього, де X (n) - очікувана кількість циклів.