我知道Tortoise和Hare的会议总结了循环的存在,但是如何将乌龟移动到链接列表的开头,同时又将野兔保留在会场,然后一次移动两个步骤,使它们在循环的起点相遇呢?
我知道Tortoise和Hare的会议总结了循环的存在,但是如何将乌龟移动到链接列表的开头,同时又将野兔保留在会场,然后一次移动两个步骤,使它们在循环的起点相遇呢?
Answers:
这是用于循环检测的弗洛伊德算法。您正在询问算法的第二阶段-找到一个节点是周期的一部分后,如何找到周期的起点?
在弗洛伊德算法的第一部分中,野兔为乌龟的每一步移动了两步。如果乌龟和野兔相遇过,就会有一个循环,并且相遇点是循环的一部分,但不一定是循环中的第一个节点。
当乌龟和野兔相遇时,我们找到了最小的i(乌龟采取的步数),使得X i = X 2i。令mu代表从X 0到循环开始的步数,令lambda代表循环的长度。然后,i = mu +一个lambda,而2i = mu + b lambda,其中a和b是整数,表示乌龟和野兔在循环中跑了多少次。从第二个方程中减去第一个方程可得出i =(ba)* lambda,因此i是lambda的整数倍。 因此,X i + mu = X mu。X i代表乌龟和野兔的交汇点。如果您将乌龟移回起始节点X0,然后让乌龟和野兔以相同的速度继续前进,经过多步,乌龟将达到X mu,野兔将达到X i + mu = X mu,因此第二个相遇点表示乌龟的开始周期。
X_mu
,直到循环的开始(根据mu的定义)。然后,如果您采取更多的步骤(其中i是循环长度的倍数),则最终返回循环开始:X_mu + i
= X_mu
。但是加法是可交换的,因此,这等效于从第i个步骤开始到第一个集合点X_i
,然后再有第i个步骤回到X_mu
循环的开始。
i
位于循环的某个点,因此我认为等式应为i = mu + k + a*lambda
和2i = mu + k + b*lambda
,其中k
从循环开始到汇合点的步数为。将两个方程式相减得出的结果相同。
让我尝试用我自己的话来阐明http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare提供的循环检测算法。
这个怎么运作
让我们用乌龟和野兔(指针的名称)指向带有循环的列表的开头,如上图所示。
假设如果我们一次将乌龟移动1步,而每次又移动2步,它们最终将在某一点相遇。让我们首先证明这个假设是正确的。
该图显示了带有循环的列表。周期的长度为n
,我们最初m
距离周期仅一步之遥。也可以说汇合点k
距离周期开始只有几步之遥,而当乌龟采取i
全部步骤时,乌龟和野兔会合。(届时,哈雷本应采取2i
全部步骤。)。
必须满足以下两个条件:
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步。由于它在周期开始前k步开始,所以兔子必须到达周期开始。
结果,这说明他们将必须在第一次经过一定数量的步骤后才开始在循环中相遇(非常第一次),因为乌龟在经过m步之后才刚到达循环,它永远也看不到已经在其中的野兔了。周期)。
现在我们知道,移动它们直到遇到它们所需要的步数变成了从列表的开始到循环开始的距离m。当然,该算法不需要知道什么是m。它只会一次移动乌龟和野兔一步,直到它们见面为止。集合点必须是循环起点,步数必须是到循环起点的距离(m)。假设我们知道列表的长度,我们还可以计算从列表长度中减去m的循环长度。
m + k = (q - 2p) n
可以进一步简化为m + k = q*n
。这是因为乌龟经过的回圈数始终为零,因为野兔在不满足乌龟的情况下永远不会超越乌龟。想一想。
参考此图片:
慢速指针在会议之前经过的距离 = 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一次移动一个节点,它们的覆盖距离相同。
它们将到达链接列表中循环开始的位置。
老和尚的简单且不恰当的答案解释了当快速奔跑者仅完成一个完整的循环时就找到循环的原因。在这个答案中,我将解释快跑者在慢跑者进入循环之前多次运行循环的情况。
假设快跑者在慢速和快速相遇之前已经运行了m次循环。这意味着:
由于快速运行的速度是慢速运行速度的两倍,并且它们已经运行了相同的时间,这意味着如果将慢速运行的距离增加一倍,则可以得到快速运行的距离。从而,
求解x给出
x =(m-1)(y + z)+ z
在实际情况下,这意味着x = (m-1)个完整的循环运行+额外的距离z。
因此,如果我们将一个指针放在列表的开头,而将另一个指针留在集合点,则以相同的速度移动它们将导致in循环指针完成m-1次循环运行,然后与另一个指针就在循环开始处。
方法:
有两个指针:
如果两个指针相遇,则证明存在循环。一旦它们相遇,其中一个节点将指向头部,然后两个节点都一次处理一个节点。他们将在循环开始时碰面。
理由: 当两个人沿着圆形轨道行走时,其中一个人的速度是另一个人的两倍,他们在哪里见面?正是他们开始的地方。
现在,假设快速奔跑者k
在n
一步圈中领先一步。他们将在哪里见面?恰好n-k
一步一步。当慢跑者掩盖了(n-k)
脚步时,快跑者掩盖了k+2(n-k)
脚步。(即,k+2n-2k
步骤即2n-k
步骤)。即(n-k)
步骤(路径是循环的,我们不关心它们相遇之后的回合数;我们只对它们相遇的位置感兴趣)。
现在,快速跑步者是如何首先k
迈出第一步的?因为慢跑者花了许多步骤才能到达循环的起点。因此,循环的起点距离头节点k步。
注意:两个指针相遇的节点k
距离循环起点(在循环内)仅k
几步之遥,而头节点也距循环起点仅几步之遥。因此,当我们使指针从bot处以等步速度前进时,这些节点将在循环开始时碰面。
我相信这很简单。请让我知道是否有任何歧义。
好吧,让我们假设兔子和乌龟在距周期开始k步的地方相遇,周期开始之前的步数为mu,周期的长度为L。
所以现在在集合点->
乌龟覆盖的距离= mu + a * L + k-公式1
(到达周期开始的步骤+覆盖周期的“ a”次迭代的步骤+从周期开始的k个步骤)(其中a是一些正常数)
野兔覆盖的距离= mu + b * L + k-公式2
(到达循环开始的步骤+覆盖循环的'b'迭代的步骤+从循环开始的k个步骤)(其中b是一些正常数,b> = a)
因此,野兔覆盖的额外距离为=公式2-公式1 =(ba)* L
请注意,该距离也等于乌龟到起点的距离,因为野兔的移动速度比乌龟快2倍。这可以等于“ mu + k”,如果我们不包括循环的多次遍历,这也是会议点到起点的距离。
因此,mu + k =(ba)* L
因此,从这一点开始的m步将导致回到循环的开始(因为已经从循环的开始采取了k步到达汇合点)。这可能发生在同一周期或任何后续周期中。因此,如果现在将乌龟移动到链表的开头,则将需要数μ步才能到达循环的起点,而野兔也需要数μ步才能到达循环的开始,因此它们都将在循环中相遇。周期的起点。
PS:老实说,我脑海中有一个与原始海报相同的问题,我读了第一个答案,他们确实清除了几件事,但我无法清楚地得出最终结果,因此我尝试以自己的方式做到这一点,发现它更容易理解。
调用距离是指针跟随的链接数,时间是算法移动慢速指针一个链接和快速指针两个链接所花费的迭代次数。长度为C的循环之前有N个节点,标记为循环偏移量k = 0至C-1。
要达到循环的开始,慢速需要N个时间和距离。这意味着快速在循环中需要N距离(N到达那里,N旋转)。因此,在时间N处,慢速为周期偏移k = 0,而快速为周期偏移k = N modC。
如果N mod C为零,则现在慢速和快速匹配,并且在时间N和周期位置k = 0处找到周期。
如果N mod C不为零,那么快必须赶上慢,而慢N在时间N是周期中落后C-(N mod C)的距离。
由于每1个慢速运动快速运动2,因此每次迭代将距离减少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;
}
将问题简化为循环问题,然后回到最初的问题
我发现以下解释更加直观。
取两个指针(1 =龟和2从头部(开始=野兔)ø),1具有一个步长1,2具有一个步长2。想一想当1到达该周期的起始节点(A)的时刻。
我们想回答以下问题“ 1在A中时2在哪里?” 。
因此,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
。
例如:if 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
。
现在,我们将问题简化为一个圆圈。问题是“集合点在哪里?” 。那C在哪里?
在每一步,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是终点)。
现在,让我们回到初始模式。
我们知道a = k*n + b
和CA = b
。
我们可以使用2个新的指针1'和1'',其中1'从头(O)开始,而1''从交点(C)开始。
而1'变为从ø到甲,1‘’从进Ç到甲并且继续到结束k
圈。因此,交点为一个。
通过以上所有分析,如果您是一个学习实例的人,我将尝试编写一个简短的分析和示例,以帮助解释其他人都试图解释的数学。开始了!
分析:
如果我们有两个指针,一个指针比另一个指针快,并且将它们一起移动,它们最终将再次相遇以指示一个周期,或者将它们归零以指示没有周期。
为了找到周期的起点,让...
m
从头到周期开始的距离;
d
是循环中的节点数;
p1
是较慢的指针的速度;
p2
是更快的指针的速度,例如。2表示一次跨两个节点。
观察以下迭代:
m = 0, d = 10: p1 = 1: 0 1 2 3 4 5 6 7 8 9 10 // 0 would the start of the cycle p2 = 2: 0 2 4 6 8 10 12 14 16 18 20 m = 1, d = 10: p1 = 1: -1 0 1 2 3 4 5 6 7 8 9 p2 = 2: -1 1 3 5 7 9 11 13 15 17 19 m = 2, d = 10: p1 = 1: -2 -1 0 1 2 3 4 5 6 7 8 p2 = 2: -2 0 2 4 6 8 10 12 14 16 18
从上面的样本数据中,我们可以轻松地发现,只要速度更快的指针和速度较慢的指针相遇,它们就m
距循环开始只有几步之遥。要解决此问题,请将较快的指针放回头部,然后将其速度设置为较慢的指针的速度。当他们再次见面时,节点就是周期的开始。
可以说
N[0] is the node of start of the loop,
m is the number of steps from beginning to N[0].
我们有2个指针A和B,A的运行速度为1倍,B的运行速度为2倍,均从头开始。
当A达到N [0]时,B应该已经在N [m]中。(注意:A使用m步达到N [0],B应该进一步m步)
然后,A再跑k步撞到B,即A在N [k],B在N [m + 2k](注意:B应该从N [m]开始跑2k步)
分别在N [k]和N [m + 2k]处发生碰撞B,这意味着k = m + 2k,因此k = -m
因此,要从N [k]循环回到N [0],我们还需要m个步骤。
简而言之,我们只需要在找到碰撞节点后再执行m个步骤即可。我们可以有一个从头开始运行的指针和一个从碰撞节点运行的指针,它们将在m步后在N [0]处相遇。
因此,伪代码如下:
1) A increase 1 step per loop
2) B increase 2 steps per loop
3) if A & B are the same node, cycle found, then go to 5
4) repeat from 1
5) A reset to head
6) A increase 1 step per loop
7) B increase 1 step per loop
8) if A & B are the same node, start of the cycle found
9) repeat from 6
我不认为当他们见面时才是起点。但是可以,如果另一个指针(F)在之前的集合点,则该指针将在循环的末尾而不是循环的开始,并且指针(S)从列表的开始开始,它将结束于循环的开始。例如:
1->2->3->4->5->6->7->8->9->10->11->12->13->14->15->16->17->18->19->20->21->22->23->24->8
Meet at :16
Start at :8
public Node meetNodeInLoop(){
Node fast=head;
Node slow=head;
fast=fast.next.next;
slow=slow.next;
while(fast!=slow){
fast=fast.next;
fast=fast.next;
if(fast==slow) break;
slow=slow.next;
}
return fast;
}
public Node startOfLoop(Node meet){
Node slow=head;
Node fast=meet;
while(slow!=fast){
fast=fast.next;
if(slow==fast.next) break;
slow=slow.next;
}
return slow;
}
使用高中教授的相对速度的思想进行的简单解释-物理101 /运动学讲座。
假设从链表的起点到圆的起点的距离是x
跳数。让我们将圆的起点称为点X
(以大写字母表示-参见上图)。另外,假设圆的总大小为N个跃点。
野兔速度= 2 *乌龟速度。分别是1 hops/sec
和2 hops/sec
当乌龟到达圆的起点时X
,野兔必须再x
跳到Y
图中的点。(因为野兔的距离是乌龟的两倍)。
因此,剩余弧的长度从X到Y顺时针为N-x
。牛逼他也正好是兔子和乌龟之间被覆盖让他们能够满足的相对距离。假设这个相对距离会及时覆盖,t_m
即会面的时间。相对速度(2 hops/sec - 1 hops/sec)
即1 hops/sec
。因此,使用相对距离=相对速度X时间,我们得到t
= N-x
秒。因此,N-x
要达到乌龟和野兔的汇合点。
现在,以N-x
秒为单位的时间和1 hops/sec
速度,早一点的乌龟X
将遮盖Nx跃点到达交汇点M
。因此,这意味着汇合点M
位于= =的N-x
逆时针跳动处X
(这进一步暗示)=> x
从点M
到X
顺时针的距离还剩下。
但是x
也是X
从链表的开始到点的距离。
现在,我们不在乎x
对应的跳数。如果我们在LinkedList的开始处放一只乌龟,在集合点M
放一只乌龟,让他们跳/走,那么它们将在point会合X
,这是我们需要的一点(或节点)。
-循环前有k步。我们不知道k是什么,也不需要找出。我们只用k就可以抽象地工作。
-经过k步
----- T在循环开始时
----- H是进入循环的k步(他总共跑了2k,因此k进入循环)
**它们现在为loopsize-相距k
(请注意,k == K == mod(loopsize,k)-例如,如果一个节点进入5节点周期为2步,那么它也是7步,12步或392步,因此k的周期不大考虑到。
由于它们的移动速度是另一种的两倍,因此它们以每单位时间1步的速度互相追赶,因此它们会以循环大小-k相遇。
这意味着需要k个节点才能到达循环的开始,因此从头部到循环开始以及碰撞到循环开始的距离是相同的。
因此,现在在第一次碰撞后将T移回头部。如果您以1的速率移动,T和H将在循环启动时相遇。(两者均以k步为单位)
这意味着该算法为:
//通过计算循环长度来处理k = 0或T和H在循环开头相遇的情况
-用计数器在T或H周围移动来计算周期的长度
-将指针T2移动到列表的开头
-移动指针的循环步长
-将另一个指针H2移到头部
-将T2和H2串联移动,直到它们在循环开始时相遇
而已!
我知道已经有一个可以解决此问题的答案,但我仍将尝试以一种流畅的方式回答。假设:
The length of the Path is 'X+B' where 'B' is the length of the looped path and X of the non looped path.
Speed of tortoise : v
Speed of hare : 2*v
Point where both meet is at a distance 'x + b - k' from the starting point.
现在,让兔子和乌龟从开始的时间“ t”开始相遇。
观察结果:
如果,乌龟行进的距离= v * t = x +(bk)(例如)
然后,野兔经过的距离= 2 * v * t = x +(b-k)+ b(因为野兔已经穿过了环状部分)
现在,开会时间相同。
=> x + 2 * b-k = 2 *(x + b-k)
=> x = k
当然,这意味着未循环的路径的长度与循环的起点到两者相遇点的距离相同。
如果您考虑了汇合点后面的数学运算,实际上很容易证明他们俩都将在汇合点相遇。
首先,让m表示链表中循环的起点,n表示循环的长度。然后为了让野兔和乌龟见面,我们有:
( 2*t - m )%n = (t - m) %n, where t = time (at t = 0 , both are at the start)
用数学方式说明一下:
(2*t - m - (t - m) ) = 0 modulo n , which implies , t = 0 modulo n
因此他们将在时间t见面,该时间应为周期长度的倍数。这意味着他们在某个地点相遇
(t-m) modulo n = (0-m) modulo n = (-m) modulo n
。
现在回到问题所在,如果您从链接列表的开头移动一个指针,从交点移动另一个指针,则经过m步后,我们的野兔(在循环内移动)将到达((-m) + m) modulo n = 0 modulo n
因此,我们可以看到,在经过m步之后,它便进入了循环的起点,乌龟将在那里相遇,因为它将 从链表的起点开始经过m步。
顺便说一句,我们还可以这样计算它们的相交时间:条件t = 0 modulo n
告诉我们它们将在一个周期长度的倍数处相遇,并且t应该大于m,因为它们在周期。因此,花费的时间将等于n的第一个倍数,大于m。