Floyd's Cycle Detection Algorithm

Floyd判圈算法


1. 什么是Floyd判圈算法?

Floyd判圈算法(Floyd Cycle Detection Algorithm),又称龟兔赛跑算法(Tortoise and Hare Algorithm),是一个可以在有限状态机、迭代函数或者链表上判断是否存在环,求出该环的起点与长度的算法。


2. 算法描述

如果有限状态机、迭代函数或者链表上存在环,那么在某个环上以不同速度前进的2个指针必定会在某个时刻相遇。同时显然地,如果从同一个起点(即使这个起点不在某个环上)同时开始以不同速度前进的2个指针最终相遇,那么可以判定存在一个环,且可以求出2者相遇处所在的环的起点与长度。

Floyd Cycle Detection Algorithm主要解决三个问题:

  1. 检测是否有环;
  2. 如果有环,求环的起点;
  3. 如果有环,求环的长度;

(1)检测是否有环

基本思想:
这个可以用跑步来解释,假设两个人从同一起点出发(不从同一起点也可以),以不同的速度向前跑,最终快的人一定会追上慢的人(套圈)。可以将速度快的人换做兔子,速度慢的人换做乌龟,就变成龟兔赛跑了,23333…….

基于上述思想,我们可以这样检测是否有环:
初始状态下,假设起点为S。现设两个指针t和h,将它们均指向S。接着,让t和h同时以不同的速度向前推进:t速度为v,h速度为2v。当h无法向前推进时,即可确定没有环;如果t与h相遇,则可以确定有环。(注意,起点不一定在环上。)

(2)如果有环,求环的起点;

基本思想:
在上述算法判断出存在环时,显然t和h在同一位置。此时,只要令h仍位于原来的位置M,而令t返回起点S,此时h与t之间距为环C长度的整数倍。随后,同时让t和h以相同的速度往前推进:即t每前进1步,h前进1步。持续该过程直至t与h再一次相遇,此相遇地点即为环C的一个起点P。

很多小伙伴看到这儿会很困惑,为毛是这样呢?我们可以用图来解释一下。
这里写图片描述

已经确定有环,设起点到环的起点距离为m,环的周长为n,第一次相遇时距离环的起点的距离为k,第一次相遇时慢指针在环上转了a圈,快指针在环上转了b圈。(这里假定h的速度是t速度的2倍)
两者第一次相遇时,慢指针移动的距离i为: i = m + a*n + k; 快指针速度是慢指针速度的2倍,故快指针移动的距离2i为: 2i = m + b*n + k。两者相减得,i = (b-a)*n,即i是环长度的倍数。此时,按上述算法,令慢指针返回起点,两个指针均以慢指针的速度同时向前推进。当慢指针推进m时,会到达环的起点,此时快指针移动的总距离为 2i+m。考虑这个 2i+m,可以理解为从起点走m,到达环起点,然后走了整数倍的环长度,故最终快指针也会到达环起点(即快慢指指针在环起点相遇)。

(3)如果有环,求环的长度;

基本思想:
这个相对来说比较简单。只需要在快慢指针相遇时,保持一个指针不动,让另外一个指针向前推进,记录其步数。当两个指针再次相遇时,第二个指针推进的步数,即为环的长度。


3. 算法实现

这里引用一个维基上的python的算法实现,其他语言的也类似。

def floyd(f, x0):
    # Main phase of algorithm: finding a repetition x_i = x_2i.
    # The hare moves twice as quickly as the tortoise and
    # the distance between them increases by 1 at each step.
    # Eventually they will both be inside the cycle and then,
    # at some point, the distance between them will be
    # divisible by the period λ.
    tortoise = f(x0) # f(x0) is the element/node next to x0.
    hare = f(f(x0))
    while tortoise != hare:
        tortoise = f(tortoise)
        hare = f(f(hare))

    # At this point the tortoise position, ν, which is also equal
    # to the distance between hare and tortoise, is divisible by
    # the period λ. So hare moving in circle one step at a time, 
    # and tortoise (reset to x0) moving towards the circle, will 
    # intersect at the beginning of the circle. Because the 
    # distance between them is constant at 2ν, a multiple of λ,
    # they will agree as soon as the tortoise reaches index μ.

    # Find the position μ of first repetition.    
    mu = 0
    tortoise = x0
    while tortoise != hare:
        tortoise = f(tortoise)
        hare = f(hare)   # Hare and tortoise move at same speed
        mu += 1

    # Find the length of the shortest cycle starting from x_μ
    # The hare moves one step at a time while tortoise is still.
    # lam is incremented until λ is found.
    lam = 1
    hare = f(tortoise)
    while tortoise != hare:
        hare = f(hare)
        lam += 1

    return lam, mu

4. 算法复杂度

时间复杂度:

注意到当指针t到达环C的起点时(此时指针h显然在环C上),之后指针t最多仅可能走1圈。若设起点S到环起点P距离为 m,环C的长度为 n,则时间复杂度为O(m+n),是线性时间的算法。

空间复杂度:

仅需要创立指针t、指针h,保存环长n、环的一个起点P。空间复杂度为 O(1),是常数空间的算法。

最后,说一下,大多数情况下,判断是否有环的问题,也可以用HashSet来实现,即每次用HashSet记录出现过的节点,当一个节点重复出现时,即可判断存在环。HashSet这种方法的空间复杂度为O(n), 故从空间复杂度的角度考虑,Floyd判圈算法要优于HashSet这种方法。


参考资料:
1. https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare
2. https://zh.wikipedia.org/wiki/Floyd%E5%88%A4%E5%9C%88%E7%AE%97%E6%B3%95
3. https://stackoverflow.com/questions/2936213/explain-how-finding-cycle-start-node-in-cycle-linked-list-work

猜你喜欢

转载自blog.csdn.net/l947069962/article/details/77774737