Объясните, как работает поиск начального узла цикла в связанном списке циклов? - PullRequest
139 голосов
/ 29 мая 2010

Я понимаю, что встреча Черепахи и Заяца завершает существование петли, но как перемещение черепахи в начало связанного списка, сохраняя зайца в месте встречи, с последующим перемещением обоих по одному шагу за раз, заставляет их встречаться в начальной точке цикл?

Ответы [ 20 ]

294 голосов
/ 24 мая 2011

Позвольте мне попытаться уточнить свои алгоритмы обнаружения цикла, которые предоставляются в http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare.

Я ссылаюсь на цифру drawing в моем объяснении.

Как это работает

Давайте возьмем черепаху и зайца (название указателей), указывающих на начало списка с циклом.

Давайте предположим, что, если мы будем перемещать черепаху по 1 шагу за раз, а по 2 шага за раз, они в конечном итоге встретятся в определенный момент. Покажем, что в первую очередь эта гипотеза верна.

На рисунке показан список с циклом. Цикл имеет длину n, и мы изначально на m шагах от цикла. Также предположим, что место встречи находится в k шагах от начала цикла, а черепаха и заяц встречаются после всего i шагов.

Должны соблюдаться следующие 2 условия:

1) i = m + p * n + k

2) 2i = m + q * n + k

Первый говорит, что черепаха движется на i шагов, и на этих i шагах она сначала попадает в цикл. Затем он проходит цикл p раз для некоторого положительного числа p. Наконец, он проходит через k больше узлов, пока не встретит зайца.

Аналогичное верно для зайца. Он перемещается на 2i шага, и на этих 2i шагах он сначала попадает в цикл. Затем он проходит цикл q раз для некоторого положительного числа q. Наконец, он проходит через k больше узлов, пока не встретит черепаху.

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

Итак, используя простое соотношение скорости, времени и расстояния,

2 ( m + p * n + k ) = m + q * n + k

=> 2m + 2pn + 2k = m + nq + k 

=>  m + k = ( q - 2p ) n

Среди m, n, k, p, q первые два являются свойствами данного списка. Если мы можем показать, что существует хотя бы один набор значений для k, q, p, который делает это уравнение верным, мы покажем, что гипотеза верна.

Один такой набор решений выглядит следующим образом:

p = 0

q = m

k = m n - m

Мы можем проверить, что эти значения работают следующим образом:

m + k = ( q - 2p ) n  

=> m + mn - m = ( m - 2*0) n

=> mn = mn.

Для этого набора i равно

i = m + p n + k

=> m + 0 * n + mn - m = mn.

Конечно, вы должны видеть, что это не обязательно наименьшее из возможных. Другими словами, черепаха и заяц могли встречаться уже много раз. Однако, поскольку мы показываем, что они встречаются в какой-то момент хотя бы раз, мы можем сказать, что гипотеза верна. Поэтому им придется встретиться, если мы переместим одного из них на 1 шаг, а другого - на 2 шага за раз.

Теперь мы можем перейти ко второй части алгоритма, которая заключается в том, как найти начало цикла.

Начало цикла

После того, как черепаха и заяц встретятся, давайте вернем черепаху в начало списка и оставим зайца там, где он встретился (это k шагов от начала цикла).

Гипотеза состоит в том, что если мы позволим им двигаться с одинаковой скоростью (1 шаг для обоих), то в первый раз, когда они снова встретятся, начнется цикл.

Давайте докажем эту гипотезу.

Давайте сначала предположим, что какой-то оракул говорит нам, что такое m.

Тогда, если мы позволим им двигаться m + k шагов, черепаха должна прибыть в точку, с которой они встречались изначально (k шагов от начала цикла - см. На рисунке).

Ранее мы показали, что m + k = (q - 2p) n.

Поскольку m + k шагов кратно длине цикла n, заяц за это время прошел бы цикл (q-2p) раз и вернулся бы к той же точке (k шагов от начала цикла) ).

Теперь, вместо того, чтобы позволить им двигаться m + k шагов, если мы позволим им двигаться только m шагов, черепаха прибудет в начало цикла. Заяц может пройти на k шагов меньше, чем завершение (q-2p) поворотов. Поскольку он начал k шагов перед началом цикла, заяц должен был прийти к началу цикла.

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

Теперь мы знаем, что количество шагов, которое нам нужно переместить, пока они не встретятся, оказывается расстоянием от начала списка до начала цикла, м. Конечно, алгоритм не должен знать, что такое m. Он будет просто перемещать черепаху и зайца по одному шагу за раз, пока они не встретятся. Место встречи должно быть началом цикла, а количество шагов - расстоянием (м) до начала цикла. Предполагая, что мы знаем длину списка, мы также можем вычислить длину цикла вычитания m из длины списка.

113 голосов
/ 24 августа 2015

См. Это изображение:

enter image description here

Расстояние, пройденное медленным указателем до встречи = x + y

Расстояние, пройденное fastPointer до встречи = (x + y + z) + y = x + 2y + z

Поскольку fastPointer перемещается с double , скорость slowPointer и время постоянны для обоих, когда достигают точки встречи.

Таким образом, используя простое соотношение скорости, времени и расстояния 2 (x + y) = x + 2y + z => x + 2y + z = 2x + 2y => x = z

Следовательно, перемещая slowPointer в начало связанного списка, и заставляя slowPointer и fastPointer перемещать один узел за раз, они оба имеют одинаковое расстояние для покрытия .

Они достигнут точки начала цикла в связанном списке.

70 голосов
/ 29 мая 2010

Это алгоритм Флойд для обнаружения цикла . Вы спрашиваете о второй фазе алгоритма - как только вы нашли узел, который является частью цикла, как найти начало цикла?

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

Когда черепаха и заяц встречаются, мы нашли наименьшее i (число шагов, предпринятых черепахой), такое что X i = X 2i . Пусть mu представляет количество шагов, которые нужно пройти от X 0 до начала цикла, и пусть lambda представляет длину цикла. Тогда i = mu + a лямбда и 2i = mu + b лямбда, где a и b - целые числа, обозначающие, сколько раз черепаха и заяц проходили цикл. Вычитание первое уравнение из второго дает i = (b-a) * лямбда, поэтому я является целым кратным лямбда Следовательно, X i + mu = X mu . X i представляет собой место встречи черепахи и зайца. Если вы переместите черепаху обратно в начальный узел X 0 , и пусть черепаха и заяц продолжат с той же скоростью, после дополнительных шагов му черепаха достигнет X mu , и заяц достигнет X i + mu = X mu , поэтому вторая точка встречи обозначает начало цикла.

59 голосов
/ 25 марта 2016

Простой и недооцененный ответ Старого Монаха объясняет нахождение цикла, когда быстрый бегун завершает только один полный цикл. В этом ответе я объясняю случай, когда быстрый бегун несколько раз запускал цикл, прежде чем медленный бегун входит в цикл.


Использование того же изображения: enter image description here

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

  • Дистанция медленного прохождения: x + y
  • Быстрое расстояние: x + m (y + z) + y , т.е. дополнительно y там, где они встречаются

Поскольку быстрые пробеги с удвоенной скоростью замедления и то, что они бегали в одно и то же время, это означает, что если мы удвоим расстояние, пройденное медленным, мы получим расстояние, пройденное быстрым. Таким образом,

  • 2 (x + y) = x + m (y + z) + y

Решение для х т,

x = (m - 1) (y + z) + z

В реальном сценарии это будет означать, x = (м - 1) полный цикл цикла + дополнительное расстояние z .

Следовательно, если мы поместим один указатель в начало списка, а другой оставим в месте встречи, то перемещение их с той же скоростью приведет к тому, что указатель в цикле завершится m - 1 запускает цикл, а затем встречает другой указатель прямо в начале цикла.

7 голосов
/ 20 августа 2014

Figure 1

Во время первого столкновения черепаха переместилась на m + k шагов, как показано выше. Заяц движется в два раза быстрее черепахи, то есть заяц сдвинулся 2 (m + k) шагов. Из этих простых фактов мы можем вывести следующий граф.

Figure 1

В этот момент мы возвращаем черепаху к началу и заявляем, что и заяц, и черепаха должны двигаться по одному шагу за раз. По определению, после m шагов, черепаха будет в начале цикла. Где будет заяц?

Заяц также будет в начале цикла. Это видно из второго графика: когда черепаха была возвращена к началу, заяц прошел k шагов в свой последний цикл. После шагов m заяц завершит еще один цикл и столкнется с черепахой.

4 голосов
/ 26 октября 2014

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

После нахождения связанной точки в круговом связанном списке теперь проблема сводится к поиску точки пересечения задачи двух связанных списков.

2 голосов
/ 09 мая 2014

Подход:

Есть два указателя:

  • Медленный указатель, который перемещает один узел за раз.
  • Быстрый указатель, который перемещает два узла одновременно.

Если два указателя встречаются, это доказывает наличие цикла. После того, как они встретились, один из узлов будет указывать на голову, а затем оба будут проходить по одному узлу за раз. Они встретятся в начале цикла.

Обоснование: Когда два человека идут по круговой дорожке, один из которых в два раза быстрее другого, где они встречаются? Именно там, где они начали.

Теперь предположим, что быстрый бегун имеет преимущество в k шагов на n шагах. где они встретятся? Точно в n-k шагах. Когда медленный бегун преодолел (n-k) шагов, быстрый бегун покрыл бы k+2(n-k) шагов. ( т.е. k+2n-2k шагов, т.е. 2n-k шагов ). то есть (n-k) шагов (путь круговой, и нас не волнует количество раундов, после которых они встречаются; нас просто интересует положение, где они встречаются).

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

Примечание: Узел, в котором встретились оба указателя, находится на k шагах от начала цикла (внутри цикла), а головной узел также на k шагах от начала цикла. Поэтому, когда у нас есть одинаковые темпы продвижения бота по этим узлам, они встретятся в начале цикла.

Я полагаю, это просто. Пожалуйста, дайте мне знать, если какая-либо часть двусмысленна.

2 голосов
/ 06 декабря 2018

enter image description here кредит изображения

Расстояние вызова - количество ссылок, за которыми следует указатель, и время, которое занимает алгоритм для перемещения медленного указателя на одну ссылку и быстрого указателя на две ссылки. Перед циклом длиной C имеется N узлов, помеченных смещением цикла от k = 0 до C-1.

Чтобы достичь начала цикла, медленно требуется N времени и расстояния. Это означает, что быстро занимает N расстояние в цикле (N, чтобы добраться, N, чтобы вращаться). Таким образом, в момент времени N медленное значение равно смещению цикла k = 0, а быстрое значение равно смещению цикла k = N mod C.

Если N mod C равно нулю, медленные и быстрые теперь совпадают, и цикл находится в момент времени N и позиция цикла k = 0.

Если N mod C не равно нулю, тогда fast теперь должен догонять медленный, который в момент N равен C- (N mod C) расстоянию позади в цикле.

Поскольку быстрые ходы 2 для каждого 1 медленного, уменьшая расстояние на 1 на каждой итерации, это занимает столько же дополнительного времени, сколько расстояние между быстрым и медленным в момент N, который равен C- (N mod C). Поскольку замедление движется от смещения 0, это также смещение, где они встречаются.

Итак, если N mod C равно нулю, фаза 1 останавливается после N итераций в начале цикла. В противном случае фаза 1 останавливается после итераций N + C- (N mod C) со смещением C- (N mod C) в цикл.

// C++ pseudocode, end() is one after last element.

int t = 0;
T *fast = begin();
T *slow = begin();
if (fast == end()) return [N=0,C=0];
for (;;) {
    t += 1;
    fast = next(fast);
    if (fast == end()) return [N=(2*t-1),C=0];
    fast = next(fast);
    if (fast == end()) return [N=(2*t),C=0];
    slow = next(slow);
    if (*fast == *slow) break;
}

Хорошо, так что фаза 2: замедление занимает еще N шагов, чтобы добраться до цикла, и в этот момент быстрый (теперь движущийся 1 за шаг) находится в (C- (N mod C) + N) mod C = 0. Таким образом, они встречаются в начале цикла после фазы 2.

int N = 0;
slow = begin();
for (;;) {
    if (*fast == *slow) break;
    fast = next(fast);
    slow = next(slow);
    N += 1;
}

Для полноты, фаза 3 вычисляет длину цикла, продвигаясь еще раз через цикл:

int C = 0;
for (;;) {
    fast = next(fast);
    C += 1;
    if (fast == slow) break;
}
2 голосов
/ 22 июля 2013

Хорошо, давайте предположим, что заяц и черепаха встречаются в точке, которая находится в k шагах от начала цикла, число шагов до начала цикла равно мю, а длина цикла равна L.

Итак, теперь на месте встречи ->

Расстояние, пройденное черепахой = mu + a * L + k - Уравнение 1

(шаги, предпринятые для достижения начала цикла + шаги, предпринятые для охвата «и» итераций цикла + k шагов от начала цикла) (где a - некоторая положительная постоянная)

Расстояние, пройденное зайцем = mu + b * L + k - уравнение 2

(шаги, предпринятые для достижения начала цикла + шаги, предпринятые для охвата «b» итераций цикла, + k шагов от начала цикла) (где b - некоторая положительная постоянная и b> = a)

Итак, дополнительное расстояние, пройденное зайцем, равно = Уравнение 2 - Уравнение 1 = (b-a) * L

Обратите внимание, что это расстояние также равно расстоянию черепахи от начальной точки, поскольку заяц движется в 2 раза быстрее, чем черепаха. Это может быть приравнено к 'mu + k', которое также является расстоянием до точки встречи от начала, если мы не включаем многократные обходы цикла.

Таким образом, mu + k = (b-a) * L

Таким образом, некоторые шаги из этой точки могут привести к началу цикла (поскольку k шагов от начала цикла уже были предприняты для достижения точки встречи). Это может произойти в том же цикле или любом из последующих циклов. Таким образом, теперь, если мы переместим черепаху в начало связанного списка, потребуется несколько шагов, чтобы добраться до начальной точки цикла, а заяц предпримет несколько шагов, чтобы также достичь начала цикла, и, таким образом, они оба встретятся в начальная точка цикла.

P.S. Честно говоря, у меня был тот же вопрос, что и у оригинального плаката, и я прочитал первый ответ, они кое-что прояснили, но я не смог четко получить конечный результат, поэтому я попытался сделать это по-своему это легче понять.

1 голос
/ 15 октября 2015

Сведите проблему к проблеме петли, затем вернитесь к исходной проблеме

Я считаю следующее объяснение более интуитивным.

  1. Возьмите два указателя ( 1 = черепаха и 2 = заяц), которые начинаются с головы ( O ), 1 имеет длину шага 1 , 2 имеет длину шага 2 . Подумайте о том моменте, когда 1 достигает начального узла этого цикла ( A ).

    Мы хотим ответить на следующий вопрос «Где 2, когда 1 в A?» .

    Итак, OA = a - натуральное число (a >= 0). Но это можно записать следующим образом: a = k*n + b, где a, k, n, b are natural numbers:

    • n = длина цикла
    • k >= 0 = постоянная
    • 0 <= b <= n-1

    Это означает, что b = a % n.

    Например: если a = 20 и n = 8 => k = 2 и b = 4, потому что 20 = 2*8 + 4.

    Расстояние, пройденное 1 , составляет d = OA = a = k*n + b. Но в то же время 2 охватывает D = 2*d = d + d = OA + d = OA + k*n + b. Это означает, что когда 2 находится в A, оно должно охватывать k*n + b. Как видите, k - это количество кругов, но после этих кругов 2 будет b далеко от A. Итак, мы нашли, где 2 это когда 1 находится в A. Давайте назовем эту точку B, где AB = b.

    enter image description here

  2. Теперь мы сводим задачу к кругу. Вопрос «Где место встречи?» . Где это C ?

    enter image description here

    На каждом шаге 2 уменьшает расстояние с 1 на 1 (скажем, метр), потому что 1 идет дальше от 2 с 1, но в то же время 2 приближается к 1 на 2.

    Таким образом, пересечение будет, когда расстояние между 1 и 2 будет равно нулю. Это означает, что 2 уменьшает расстояние n - b. Для этого 1 сделает n - b шагов, в то время как 2 сделает 2*(n - b) шагов.

    Таким образом, точка пересечения будет n - b далеко от A (по часовой стрелке), потому что это расстояние, пройденное 1 до достижения 2, => расстояние между C и A равно CA = b, потому что AC = AB + BC = n - b и CA = n - AC. Не думайте, что AC = CA, поскольку расстояние AC не является тривиальным математическим расстоянием, это число шагов между A и C (где A является начальной точкой, а C является конечной точкой).

  3. Теперь вернемся к исходной схеме.

    Мы знаем, что a = k*n + b и CA = b.

    Мы можем взять 2 новых указателя 1 ' и 1' ', где 1' начинается с головы ( O ) и 1 '' начинается с точки пересечения ( C ).

    В то время как 1 ' идет от O до A , 1' ' идет от C до A и продолжает финишировать k кругов. Итак, точка пересечения A .

    enter image description here

    enter image description here

...