使用 Hare and Tortoise 方法在链表中检测循环

Posted

技术标签:

【中文标题】使用 Hare and Tortoise 方法在链表中检测循环【英文标题】:Cycle detection in linked list with the Hare and Tortoise approach 【发布时间】:2011-09-22 21:32:06 【问题描述】:

我知道,为了检测链表中的循环,我可以使用 Hare and Tortoise 方法,该方法包含 2 个指针(慢指针和快指针)。但是,在阅读wiki和其他资源后,我不明白为什么保证两个指针会在O(n)时间复杂度内相遇。

【问题讨论】:

您是在寻找正式的数学证明,还是只是非正式的解释? 正式(但易于理解)证明。检查顶部的第二个答案。 ***.com/questions/2936213/… 【参考方案1】:

这是一个非正式证明的尝试。

循环的形状无关紧要。它可以有一个长尾巴,和一个朝向末端的循环,或者只是一个从头到尾没有尾巴的循环。不管周期的形状如何,有一点很清楚 - 乌龟指针无法赶上兔子指针。如果两者要相遇,兔子指针必须从后面追上龟指针。

确定后,考虑两种可能性:

    野兔指针比乌龟指针落后一步。 野兔指针比乌龟指针落后两步。

所有更大的距离最终都会减少到一两个。

假设乌龟指针总是先移动(也可以反过来),那么在第一种情况下,乌龟指针向前移动一步。现在它们之间的距离为 2。当野兔指针现在走 2 步时,它们将落在同一个节点上。为了更容易理解,这里说明了相同的内容。过多的文字可能会妨碍您。

♛ = 野兔 ♙ = 乌龟 X = 两者相遇 ..♛♙... #1 - 最初,兔子比乌龟落后一步。 ..♛.♙.. #2 - 现在乌龟迈出了一步。现在野兔落后了两步。 ....X.. #3 - 接下来,兔子走了两步,它们相遇了!

现在让我们考虑第二种情况,它们之间的距离为 2。慢速指针向前移动一步,它们之间的距离变为 3。接下来,快速指针向前移动两步,它们之间的距离减小到 1,即与第一种情况相同,在第一种情况下,我们已经证明它们将再一步相遇。同样,为了便于理解,还进行了图示。

.♛.♙... #1 - 最初,兔子比乌龟落后两步。 .♛..♙.. #2 - 现在乌龟迈了一步,它们之间的距离变成了三步。 ...♛♙.. #3 - 接下来,兔子采取两个步骤,这与之前的情况相同。

现在,至于为什么这个算法保证在 O(n) 内,使用我们已经确定的兔子在乌龟前进之前遇到乌龟。这意味着一旦两者都在循环内,在乌龟完成另一轮之前,它将遇到兔子,因为兔子的移动速度是兔子的两倍。在最坏的情况下,循环将是一个有 n 个节点的圆。乌龟只能在 n 步内完成一轮,而兔子可以在这段时间内完成两轮。即使兔子在第一轮错过了乌龟(它会),它肯定会在第二轮赶上乌龟。

【讨论】:

知道了,谢谢!所以只是想确保我完全理解它 - 一旦慢速指针进入循环(快速指针显然已经在其中),保证快速指针第一次尝试绕过慢速指针时,它们实际上会见面。 是的,绝对的,而且由于快速指针在n 移动中遍历循环两次(考虑到循环的长度为n),它们保证在O(n) 中相遇。还要证明为什么我们不能有比O(n) 的下限,考虑整个列表循环并看起来像一个圆圈的最坏情况。两者都从节点 0 开始。当快指针完成一个循环时,慢指针已经在列表(n/2) 步骤的一半处。在另一个(n/2) 步骤中,快的将完成另一次迭代,而慢的将完成第一次迭代。【参考方案2】:

让迭代器AB 分别一个接一个地遍历列表。康迪尔存在一个循环。然后在A 进入循环的那一刻,B 已经在其中的某个地方。如果循环的长度是K,那么B 将在]K/2[ 移动中围绕它运行,所以最多在2*]K/2[ 迭代中我们会遇到B 出现在远处A 后面的情况1: B->A2: B->.->A,这是 B'th 回合。在此之后,显然,他们将在12 移动中相遇。因此,如果循环存在并从某个位置 P 开始,那么我们最多会进行 2*P + 2*]K/2[ + 2 = O(N) 迭代。

【讨论】:

【参考方案3】:
//if you just want to check if there is a loop

loop = false;
item = head-of-list; 
while (item != nil)

   if (item.next < 0) 
   
      loop = true; 
      break; 
   
   else
   
      actual = item;
      item = item.next; 
      actual.next = actual.next * -1; 
   
 

return loop; 

【讨论】:

【参考方案4】:

这是龟兔算法的while循环:

    while tortoise:
        hare = hare.next
        tortoise = tortoise.next
        # if not hare, states that i have a single node.
        # hare.next means that we have a tail value. So we do not have a cycle
        if (not hare) or (not hare.next):
            return False
        else:
            hare = hare.next
        if tortoise == hare:
            return True

虽然兔子向前移动了2步,这意味着它有可能在循环中一遍又一遍地循环,一遍又一遍地接触多个节点,从技术上讲,这一切都发生在一个while循环中.

【讨论】:

以上是关于使用 Hare and Tortoise 方法在链表中检测循环的主要内容,如果未能解决你的问题,请参考以下文章

在单个链表中查找循环

当一个线程到达目的地时其他线程停止

Tortoise SVN使用方法,简易图解

Tortoise SVN Clean up失败的解决方法

JORDAN ECLIPSE “HARE”细节图曝光

在中间使用Tortoise-Hg abort进行克隆[命令返回代码255]