Если у вас очень мало циклов, вот алгоритм, который будет использовать меньше места, но для его завершения потребуется значительно больше времени.
[Правка.] Мой предыдущий анализ во время выполнения упустил решающую стоимость определения того, входят ли посещаемые нами узлы в число ранее выбранных; этот ответ был несколько пересмотрен, чтобы исправить это.
Мы снова итерация через все элементы 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 .
Для п = | 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) - ожидаемое количество циклов.