从 stackoverflow 内部和外部的几篇文章中,我开始知道如何检测链表中的循环,即循环的长度。我还找到了如何检测循环开始的方法。
这里再次提供步骤供参考。
检测循环:
有两个指针,通常称为兔子和乌龟。兔子移动2步,乌龟移动1步。如果它们在某个点相遇,那么肯定存在一个循环,并且相遇点显然在循环内。
查找循环长度:
将一个指针固定在交汇点,同时增加另一个指针,直到它们再次相同。随着您的进行而增加计数器,并且相遇时的计数器值将是周期的长度。
找到循环的开始
将一个指针指向列表的开头,并将另一个指针保留在交汇点。现在将两者都增加 1,交汇点就是循环的开始。我用纸上的一些案例验证了该方法,但我不明白为什么它应该有效。
有人可以提供数学证明来说明为什么这种方法有效吗?
From several posts inside stackoverflow and outside, I have come to know how to detect cycles in a linked list, the length of a cycle. I also found the method on how to detect the start of the loop.
Here are the steps again for reference.
Detecting Loop:
Have two pointers, classically called hare and tortoise. Move hare by 2 steps and tortoise by 1. If they meet at some point, then there is surely a cycle and the meeting point is obviously inside the cycle.
Finding length of Loop:
Keep one pointer fixed at meeting point while increment the other until they are same again. Increment a counter as you go along and the counter value at meet will be the length of cycle.
Find the start of cycle
Take one pointer to start of the list and keep the other at the meeting point. Now increment both by one and the meet point is the start of loop. I verified the method using some cases on paper, but I don't understand why it should work.
Can someone, provide a mathematical proof as to why this method works?
发布评论
评论(5)
如果您通过指向第一个节点的指针 (列表) 来表示列表
检测循环的算法描述如下:
让我们假设这个算法是正确的。
在此方案中,循环情况由下图表示:
请注意除第一个节点外的每个节点如何指向循环的开始,用其目标的编号减一来标记。因此,如果一个节点标记为 i 并且它不是循环的开始,则标记为 i-1 的节点将其指向下一个元素。
上面解释的算法可以被描述为一个循环,因为它的主要步骤(3)是一组重复的子步骤,直到满足退出条件。这迫使我们将 pFast 和 pSlow 表示为算法执行中迭代次数的函数 (t)。
如果列表没有循环,则指针位置将在 t 函数中描述为:
但是,有一个循环开始的节点,并且该函数停止描述指针的演变。假设该指针标记有 m,则循环包含节点(即 和 ),并在 < 时将 t=0 设置为迭代值img src="https://i.sstatic.net/DTxNP.gif" alt="在此处输入图像描述"> 然后:
如果一个指针确实足以使用所描述的算法检测循环,那么它必须至少存在方程的解 。
当且仅当 t 的值使得:
这以一个函数结束,,该函数生成 t 的值,这些值是上述方程的有效解:
由此证明,一个慢指针和一个快指针足以检测链表中的循环情况。
If you represent a list by a pointer to its first node (list)
The algorithm to detect loops is described as follows:
Lets assume that this algorithm is correct.
In this scheme, a loop situation is represented by the following diagram:
Note how every node, except the one pointing to the begining of a loop, is labeled with the number of its target minus one. So, if one node is labeled with i and it is not the begining of a loop, then it is pointed as next element by the node labeled with i-1.
The algorithm explained above can be described as a loop since its main step (3) is a set of sub-steps which repeated until the exit condition is satisfied. That forces us to represent pFast and pSlow in function of the iteration number in the algorithm execution (t).
If the list hadn’t have loops, pointers positions would be described in function of t as:
However there is a node where the loop starts and that function stops describing the evolution of the pointers. Assuming that this pointer is tagged with m, that the loop contains nodes (that is and ), and setting t=0 as iteration value when then:
If one pointer is indeed enough to detect loops using the algorithm described, then it must exist at least a solution to the equation .
This equation is true if and only if there is a value for t that makes:
This ends in a function, which generates values of t that are valid solutions to the equation described above:
Thus It is proved that one slow pointer and one fast pointer are enough to detect loop conditions in a linked list.
如果您不使用交汇点,您可以使“找到周期开始”证明变得更简单。
让第二个指针不是从“交汇点”开始,而是在第一个指针之前
M
步。 (其中M
是循环的长度。)这样,证明就非常明显了。当第一个指针到达循环开始时,第二个指针将正好领先
M
步:也在循环开始处。You can make your 'Find the start of cycle' proof simpler if you don't use meeting point.
Let second pointer start not at the 'meeting point', but
M
steps ahead of first pointer. (WhereM
is the length of the loop.)This way, proof is pretty obvious. When the first pointer reaches start of the loop, second will be exactly
M
steps ahead: also at the start of the loop.SlowPointer 相遇前移动的距离 = x + y
fastPointer 相遇前移动的距离 = (x + y + z) + y = x + 2y + z
由于 fastPointer 的移动速度是 SlowPointer 的两倍,并且当到达集合点。
因此,通过使用简单的速度、时间和距离关系 2(x+y)= x+2y+z => x+2y+z = 2x+2y => x=z
因此,通过将slowPointer移动到链表的开头,并使slowPointer和fastPointer一次移动一个节点,它们都有相同的覆盖距离。
它们将到达链表中循环开始的点。
Distance travelled by slowPointer before meeting = x + y
Distance travelled by fastPointer before meeting = (x + y + z) + y = x + 2y + z
Since fastPointer travels with double the speed of slowPointer, and time is constant for both when the reach the meeting point.
So by using simple speed, time and distance relation 2(x+y)= x+2y+z => x+2y+z = 2x+2y => x=z
Hence by moving slowPointer to start of linked list, and making both slowPointer and fastPointer to move one node at a time, they both have same distance to cover .
They will reach at the point where the loop starts in the linked list.
第二部分指出“要找到循环的开始,只需将一个指针移回列表的开头,然后迭代两者直到它们相遇”是错误的!
仅当快速指针恰好绕了一次循环时,它才是正确的 - 即循环开始之前的部分比循环的长度短 - 但如果它比算法长,那么它是正确的不起作用;你可能会陷入无限循环。
尝试使用包含 11 个元素的链表,其中第 11 个元素指向第 7 个元素:
1 1 -> 3 2 -> 5 3 -> 7 4 -> 9 5 -> 11 6 -> 8 7 -> 10 8 -> 7 9 -> 9 9
-- 检测到循环
将一个移回到开始位置并递增它们:
1 9 -> 1 2 10-> 3 11-> 4 7 -> 5 8 -> 6 9 -> 7 10 --> 8 11 --> 9 7 -> 10 8 -> 11 9 -> 11 7 10 --> 8 11 ->...
The second part, stating that "to find the start of the loop, just move one pointer back to the start of the list then iterate both until they meet" is wrong!
It is correct only if the fast pointer has gone round the loop exactly once - i.e. that the portion before the start of the loop is shorter than the length of the loop - but if it is longer then the algorithm doesn't work; you can get stuck in an infinite loop.
Try it with a linked list with 11 elements, where the 11th points to the 7th:
1 1 -> 3 2 -> 5 3 -> 7 4 -> 9 5 -> 11 6 -> 8 7 -> 10 8 -> 7 9 -> 9 9
-- loop detected
Move one back to start and increment them:
1 9 -> 2 10 -> 3 11 -> 4 7 -> 5 8 -> 6 9 -> 7 10 -> 8 11 -> 9 7 -> 10 8 -> 11 9 -> 7 10 -> 8 11 ->...