Нахождение всех циклов

9

У меня есть конечное множество , функция , и в общей сложности порядка на . Я хочу , чтобы найти число различных циклов в .Sf:SS<SS

Для данного элемента я могу использовать алгоритм Флойда (или Брента и т. Д.), Чтобы найти длину цикла, в который повторные применения посылают ; с немного большим усилием я могу идентифицировать этот цикл (например, по его элементу -minimal). Плохой метод решения проблемы - повторять каждый элемент, сортировать полученные минимальные элементы, исключая дубликаты, и возвращать счетчик. Но это потенциально включает в себя много проходов по одним и тем же элементам и большие требования к пространству.sSfs<

Какие методы имеют лучшие временные и пространственные характеристики? Я даже не уверен, каков лучший способ измерить необходимое пространство - если - это функция тождества, то любой метод, который хранит все циклы, будет использовать пространство .fΩ(n)

Чарльз
источник
4
Один из естественных способов измерения пространства - рассматривать S как набор n-битных строк, а f как оракула. Тогда наивный алгоритм, который вы описали, требует экспоненциального пространства. Можно искать алгоритм, который использует только полиномиальное пространство, но для меня это вряд ли возможно.
Цуёси Ито
Вот что я имел в виду под «я не знаю, как лучше всего измерить пространство». Возможно, я должен нацелить O (poly (n) + y), где y - выход, так что используемое пространство будет полиномиальным, пока y достаточно мало.
Чарльз
Есть ли у вашей функции f полезные свойства? Являются ли алгоритм полиномиальный или экспоненциальный предпочитаемым вами способ выражения размера входного будет несколько спорно , если практический ответ в том , что алгоритм займет время и пространство порядка мощности S .
Ниль де Бодрап,
@Niel de Beaudrap: я не уверен, какие свойства были бы полезны. Я ожидаю, что число различных циклов невелико, хотя, вероятно, ; вот почему я предложил функцию и вместо просто . Я готов использовать экспоненциальную пробел в количестве битов вывода, если это необходимо. O(n3)ynn
Чарльз

Ответы:

7

Если все, что вы хотите сделать, это подсчитать количество циклов, вы можете сделать это с помощью 2 | S | бит (плюс изменение) стоит места. Маловероятно, что вы сможете добиться большего успеха, если S или f не обладают какими-то особенно удобными свойствами.

Начните с массива A, хранящего целые числа {0,1,2} - по одному на элемент S - инициализируется нулем; мы укажем их как (unexplored), (partially explored)и (fully explored). Инициализировать счетчик циклов на ноль. Для каждого элемента s  ∈  S по порядку сделайте следующее:

  1. Если A [ s ] =  (fully explored), перейдите к шагу 6.
  2. Установите A [ s ] ←  (partially explored)и установите итератор j  ←  f (s) .
  3. Пока A [ j ] =  (unexplored), установите A [ j ] ←  (partially explored)и установите j  ←  f (j) .
  4. Если A [ j ] =  (partially explored), мы замкнули новый цикл; увеличение c на 1. (Если вы хотите сохранить запись какого-либо представителя этого цикла, текущее значение j будет произвольным выбором; конечно, это не обязательно будет минимальным элементом в цикле под вашим предпочтением order <.) В противном случае мы имеем A [ j ] =  (fully explored), что означает, что мы обнаружили предварительно исследованную орбиту, которая заканчивается в уже подсчитанном цикле; не увеличивать c .
  5. Чтобы указать, что орбита, начинающаяся с s , теперь полностью исследована, установите j  ←  s .
    Пока A [ j ] =  (partially explored), установите A [ j ] ←  (fully explored)и установите j  ←  f (j) .
  6. Перейдите к следующему элементу сек  ∈  S .

Таким образом, каждый цикл среди орбит, индуцированных f, будет учитываться один раз; и любые элементы, которые вы записали в качестве представителей, будут элементами разных циклов. Требования к памяти 2 | S | для массива A, O (log | S |) для счетчика циклов и других шансов и результатов.

Каждый элемент s  ∈  S будет посещаться как минимум дважды: один раз, когда значение A [ s ] изменяется с (unexplored)на (partially explored), и один раз для изменения на (fully explored). Общее число повторных посещений любых узлов после того, как они были (fully explored)выше, ограничено числом попыток найти новые циклы, которые не могут это сделать, что составляет самое большее | S | - вытекающие из основного контура итерации по всем элементам S . Таким образом, мы можем ожидать, что этот процесс будет включать не более 3 | S | обход узлов, считая все посещения или повторные посещения узлов.

Если вы хотите отслеживать репрезентативные элементы циклов, и вы действительно хотите, чтобы они были минимальными элементами, вы можете ограничить количество посещений узлов 4 | S |, если вы добавите дополнительный «круг вокруг цикла» на шаге 4, чтобы найти представителя, который меньше, чем тот, где вы закрываете цикл. (Если бы орбиты под f состояли только из циклов, этой дополнительной работы можно было бы избежать, но это не относится к произвольным f .)

Ниль де Бодрап
источник
Отлично, это улучшает глупый алгоритм , который я имел в виду. Мне на самом деле не нужны представители; Я представил всякий случай, это было бы полезно для какого-то алгоритма. O(|S|log|S|)<
Чарльз
Интересно, есть ли способ использовать намного меньше места в случае, когда полных циклов мало, не используя больше полиномиального пространства. Ах, неважно; это будет делать для моих нужд.
Чарльз
1
Мне кажется, что это должно быть в #L (с использованием питания матрицы). Может ли это быть # L-hard?
Каве
@Charles: посмотрите мой более свежий ответ, который даст вам улучшения, если вы знаете, что #cycles ∈ o ( | S | ). Он использует больше, чем полилог | S | пространство, но если вы хотите обменять пространство и время, это может быть лучше для вас.
Ниль де Бёдрап,
@ Ниль де Бодрап: Спасибо! +1 для обоих. Этот алгоритм кажется лучшим, если данные помещаются в память; как только это выльется, я посмотрю на использование другого. (Возможно, что другому было бы лучше, если бы я мог поместить все в кэш, но это может быть слишком хлопотно.)
Чарльз
5

Если у вас очень мало циклов, вот алгоритм, который будет использовать меньше места, но для его завершения потребуется значительно больше времени.

[Правка.] Мой предыдущий анализ во время выполнения упустил решающую стоимость определения того, входят ли посещаемые нами узлы в число ранее выбранных; этот ответ был несколько пересмотрен, чтобы исправить это.

Мы снова итерация через все элементы S . Когда мы исследуем орбиты элементов s  ∈  S , мы выбираем из узлов, которые мы посетили, чтобы иметь возможность проверить, сталкиваемся ли мы с ними снова. Мы также ведем список выборок из «компонентов» - объединений орбит, которые заканчиваются в общем цикле (и, следовательно, равных количеству циклов) - которые были посещены ранее.

Инициализировать пустой список компонентов complist. Каждый компонент представлен коллекцией образцов из этого компонента; мы также поддерживаем дерево поиска, в samplesкотором хранятся все элементы, которые были выбраны в качестве образцов для какого-либо компонента. Пусть G - последовательность целых чисел вплоть до n , для которой членство эффективно определяется путем вычисления некоторого логического предиката; например, степени 2 или совершенные p- й степени для некоторого целого числа p . Для каждого s  ∈  S сделайте следующее:

  1. Если s в samples, перейдите к шагу 5.
  2. Инициализируйте пустой список cursample, итератор j  ← f ( s ) и счетчик t  ← 1.
  3. Пока j отсутствует в samples:
    - Если t  ∈  G , вставьте j в оба cursampleи samples.
    - Увеличьте t и установите j  ←  f (j) .
  4. Проверьте, находится ли j в cursample. Если нет, мы столкнулись с ранее исследованным компонентом: мы проверяем, к какому компоненту j принадлежит, и вставляем все элементы cursampleв соответствующий элемент, complistчтобы дополнить его. В противном случае мы повторно встретили элемент с текущей орбиты, что означает, что мы прошли цикл хотя бы один раз, не встретив каких-либо представителей ранее обнаруженных циклов: мы вставляем cursample, как набор выборок из недавно найденного компонента, в complist.
  5. Перейдите к следующему элементу сек  ∈  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) - ожидаемое количество циклов.

Ниль де Бодрап
источник
1

Вы можете избежать нескольких проходов через одни и те же элементы, используя структуру данных набора объединений . Один проход по каждому элементу объединяет множество, содержащее с множеством, содержащим . Это все еще использует много памяти.ssf(s)

Питер Тейлор
источник