我已经看过question已经讨论了在链表中找到循环的算法。我已阅读Floyd's cycle-finding algorithm解决方案,在很多地方都提到我们必须采取两个指针。一个指针(慢/乌龟)增加一个,其他指针(更快/野兔)增加2.当它们相等时我们找到循环,如果更快的指针到达null,则链表中没有循环。
现在我的问题是为什么我们将指针增加更快2.为什么不是别的呢?增加2是必要的,或者我们可以将它增加X来得到结果。如果我们将指针增加2,或者可能存在需要增加3或5或x的情况,我们是否有必要找到一个循环。
答案 0 :(得分:48)
没有理由需要使用二号。任何步长选择都可行(当然除了一个)。
要了解其工作原理,我们先来看看为什么Floyd的算法起作用。想法是考虑序列x 0 ,x 1 ,x 2 ,...,x n ,...链接列表中的元素,如果从列表的开头开始,然后继续向下走,直到结束。如果列表不包含循环,则所有这些值都是不同的。但是,如果它确实包含一个循环,那么这个序列将无休止地重复。
以下是使Floyd算法有效的定理:
当且仅当存在正整数j时,链表包含一个循环,对于任何正整数k,x j = x jk 。
我们去证明这一点;这并不难。对于“if”情况,如果存在这样的aj,则选择k = 2.然后我们得到一些正j,x j = x 2j 且j≠2j,所以列表包含一个循环。
对于另一个方向,假设列表包含从位置s开始的长度为l的循环。设j是l大于s的最小倍数。那么对于任何k,如果我们考虑x j 和x jk ,因为j是循环长度的倍数,我们可以想到x jk 作为通过从列表中的位置j开始形成的元素,然后采取j步骤k-1次。但是每次你采取j步,你最后都会回到你在列表中开始的位置,因为j是循环长度的倍数。因此,x j = x jk 。
这个证明可以保证,如果你在每次迭代中采用任意数量的步骤,你确实会遇到慢速指针。更确切地说,如果你在每次迭代中采取k步,那么你最终将找到点x j 和x kj 并且将检测循环。直觉上,人们倾向于选择k = 2来最小化运行时间,因为每次迭代都采用最少的步数。
我们可以更正式地分析运行时,如下所示。如果列表不包含循环,则快速指针将在n(n)次的n步之后到达列表的末尾,其中n是列表中的元素数。否则,在慢速指针采取j步之后,两个指针将会相遇。请记住,j是l大于s的最小倍数。如果s≤l,则j = 1;否则如果s> l,则j最多为2s,因此j的值为O(s + l)。由于l和s可以不大于列表中元素的数量,这意味着比j = O(n)。但是,在慢速指针经过j步之后,快速指针将对较慢指针所采用的每个j步进行k步,因此它将采取O(kj)步。由于j = O(n),净运行时间最多为O(nk)。请注意,这表示我们使用快速指针执行的步骤越多,算法完成所需的时间越长(尽管只是按比例)。因此,选择k = 2可以最小化算法的整体运行时间。
希望这有帮助!
答案 1 :(得分:32)
让我们假设不包含循环的列表长度为s
,循环长度为t
,fast_pointer_speed
与slow_pointer_speed
之比为k
。
让两个指针在距离循环开始的距离j
处相遇。
因此,距离慢指针移动= s + j
。快速指针行进的距离= s + j + m * t
(其中m
是快速指针完成循环的次数)。但是,快速指针也会移动距离k * (s + j)
(慢速指针距离的k
倍)。
因此,我们得到k * (s + j) = s + j + m * t
。
s + j = (m / k-1)t
。
因此,根据上式,慢指针行进的长度是循环长度的整数倍。
为了获得最高效率,(m / k-1) = 1
(慢指针不应该多次遍历循环。)
因此,m = k - 1 => k = m + 1
由于m
是快速指针完成循环的次数m >= 1
。
为了获得最高效率,m = 1
。
因此k = 2
。
如果我们取值k > 2
,则两个指针必须移动的距离越多。
希望以上解释有所帮助。
答案 2 :(得分:5)
考虑大小为L的循环,意味着在第k个元素是循环所在的位置:x k - &gt; x k + 1 - &gt; ... - &gt; x k + L-1 - &gt; X <子>ķ子>。假设一个指针以r 1 = 1的速率运行而另一个指针以r 2 运行。当第一个指针到达x k 时,第二个指针已经在某个元素x k + s 的循环中,其中0 <= s&lt; L.在m进一步指针递增后,第一个指针位于x k +(m mod L),第二个指针位于x k +((m * r 2 + s)mod L)。因此,两个指针碰撞的条件可以表达为满足一致性的m的存在
m = m * r 2 + s(mod L)
这可以通过以下步骤简化
m(1-r 2 )= s(mod L)
m(L + 1-r 2 )= s(mod L)
这是线性同余的形式。如果s可被gcd整除(L + 1-r 2 ,L),则它有一个解m。如果gcd(L + 1-r 2 ,L)= 1,情况肯定会如此。如果r 2 = 2则gcd(L + 1-r 2 ,L)= gcd(L-1,L)= 1并且始终存在解m。 / p>
因此r 2 = 2具有良好的性质,对于任何循环大小L,它满足gcd(L + 1-r 2 ,L)= 1因此即使两个指针在不同的位置开始,也能保证指针最终会发生碰撞。 r 2 的其他值没有此属性。
答案 3 :(得分:2)
如果快速指针移动1
步并在2
步慢速指针,则无法保证两个指针在包含偶数节点的周期中相遇。但是,如果慢速指针以H
步移动,则会保证会议。
一般情况下,如果野兔以T
步移动,而乌龟移动H = T + 1
步,则保证在{f {1}}的周期内相遇。
考虑野兔相对于乌龟的移动。
H - T
个节点。给定一个长度为N =(H - T) * k
的周期,其中k
为正数
整数,野兔会跳过每个H - T - 1
个节点(再次,相对
对乌龟来说,如果他们不可能见面
乌龟在任何一个节点中。
保证会议的唯一可能性是H - T - 1 = 0
。
因此,只要慢速指针增加x
,就允许将快速指针增加x - 1
。
答案 4 :(得分:2)
这是一种直观的非数学方式来理解这一点:
如果快速指针从列表末尾溢出,显然没有循环。
忽略指针在列表的初始非循环部分中的初始部分,我们只需要使它们进入循环即可。慢指针最终到达周期时,快指针在周期中的哪个位置都没有关系。
一旦它们都处于循环中,它们便在循环中但在不同点处盘旋。想象一下他们是否每次都移动一个。然后它们将绕着周期盘旋,但保持相同的距离。换句话说,进行相同的循环但异相。现在,通过将快速指针每移动两步,它们彼此之间就改变了相位。每一步将它们的距离减小一倍。快速指针将追上慢速指针,我们可以检测到循环。
为了证明这是真的,它们会彼此碰面,而快速指针不会以某种方式超越并跳过慢速指针,只需要模拟快速指针落后慢速指针三步时发生的情况,然后模拟当快速指针落后于慢速指针时发生的情况快指针比慢指针落后两步,然后快指针仅比慢指针落后一步。在每种情况下,它们都在同一节点相遇。更大的距离最终将变成三,二或一的距离。
答案 5 :(得分:0)
如果存在一个循环(n个节点),则一旦指针进入该循环,它将永远存在。因此我们可以及时向前移动,直到两个指针都处于循环中。从这里开始,指针可以用初始值为a和b的整数n模表示。那么t步之后他们满足的条件是
a +t≡b+ 2t mod n 的解t = a−b mod n。
只要速度之间的差异不与n共享任何素数,这将起作用。
对速度的唯一限制是它们的差异应与循环的长度相同。
答案 6 :(得分:0)
假设我们使用两个引用Rp和Rq,它们在每次迭代中分别执行p和q步; p> q在弗洛伊德(Floyd)算法中,p = 2,q = 1。
我们知道,在某些迭代之后,Rp和Rq都将位于循环的某些元素上。然后,说Rp比Rq提前x步。也就是说,从Rq的元素开始,我们可以采取x步达到Rp的元素。
说,循环有n个元素。经过t次进一步的迭代后,Rp将比(q +(p-q)* t)步长领先于Rq。因此,只有在以下情况下,他们才能在t次迭代后相遇:
可以写成:
由于模运算,只有在以下情况下才可能实现:GCD(p-q,n)| x。
但是我们不知道x。但是,如果GCD为1,它将除以任何x。将GCD设置为1:
在this article中围绕此算法的通用p和q进行了一些讨论。
答案 7 :(得分:-1)
如果链接列表有一个循环,那么增量为2的快速指针将更好地工作,然后增加3或4或更多,因为它确保一旦我们进入循环,指针肯定会发生碰撞而没有超车。
例如,如果我们取3的增量并且在循环内部假设
fast pointer --> i
slow --> i+1
the next iteration
fast pointer --> i+3
slow --> i+2
而这种情况永远不会以2的增量发生。
此外,如果您真的不走运,那么您可能会遇到循环长度为L
并且您将快速指针递增L+1
的情况。然后你将被无限地卡住,因为移动快速和慢速指针的差异始终是L
。
我希望我能说清楚。