Floyd和Brent判圈算法
今天刷141. Linked List Cycle和142. Linked List Cycle II学到了两个新的判断链表是否存在环的算法 - Floyd龟兔赛跑算法和Brent判圈算法。
Floyd龟兔赛跑算法
这个算法的核心思想是,如果有限状态机、迭代函数或者链表上存在环,那么在某个环上以不同速度前进的两个指针必定在某个时间会相遇。也就是说,对于一个链表,可以分别使用两个指针进行遍历,慢指针一次走一步,快指针一次走两步,如果快慢指针相遇了,那么说明链表中存在环。
当发现有环之后,让快指针停止,慢指针从当前位置继续向前遍历,并计算走过的步数。在慢指针与快指针再次相遇之后,慢指针走过的步数,就是环的长度。
如果要计算环的入口,那么可以将一个指针移动到链表的起点,另一个指针不动,然后使两个指针每次同时向前走一步,当二者再次相遇的时候,指针所在的位置就是环的起点。至于这个操作的原理,原谅我数学很差,想不明白,所以借浅谈判圈算法 - xyZGHio一文中的解释:
这里令起始处为A、环的入口处为B,在判断是否有环阶段时快慢相遇之处为C。并记AB长度为a、记BC长度为b、环的长度为r。且在判断是否有环过程中,快指针每次走2步、慢指针每次走1步。则快、慢指针相遇时,快指针走过的长度是慢指针走过长度的2倍。
此时不难看出,当快、慢指针相遇时,快、慢指针走过的长度均是环长度的整数倍。故如果期望找到环的入口位置,即B处。则只需在两个指针相遇之时,将其中任意一个指针放置到起始处A,而另一个指针依然位于相遇处C。然后两个指针按照每次均走1步的速度向前走,当二者再次相遇之时,即是B处。
原因在于,对于相遇后继续往前走的指针而言,由于其已经走过了若干圈环的长度,此时只需再走a步即可到达环的入口。这个地方换个角度想会更容易理解,如果该指针先走a步再走若干圈环的长度,其必然位于环的入口处;而对于相遇后从起始处A开始走的指针而言,其显然走a步后,必然也会位于环的入口处。故此时两个指针第二次相遇之时,说明他们均已经走完a步。即到达环的入口处。
代码:
1 | /** |
Brent判环算法
Brent算法跟Floyd算法比较起来,其优点是缩短了判断是否有环的耗时(根据Wikipedia的说法,Brent算法的平均耗时比Floyd算法少36%),但是这个算法无法找到环的入口。
这个算法同样会使用快和慢两个指针,判断是否有环的依据仍然是看两个指针是否会相遇,但是快指针和慢指针的走法与Floyd算法不同。这个算法中,快指针每一次会向前2n步(n为从1开始算起的回合数),即第一回合快指针走2步,第二回合走4步,以此类推。回合结束后,慢指针直接传送到快指针所在的位置。在每个回合的快指针移动过程中判断快指针是否与慢指针交会,如果交会,那么就判定存在环。
代码:
1 | /** |
另外讲一个趣闻:这个算法还有个名字叫The Teleporting Turtle(会传送的乌龟),因为套用Floyd龟兔赛跑算法的概念,把快指针看作兔子,慢指针看作乌龟的话,那么乌龟是靠传送而不是行走前进的。
