Floyd快慢指针算法

C++

Posted by Youggls on May 26, 2020

介绍

Floyd判圈算法是一种检测图中是否存在环的算法,该算法可以在$O(N)$的时间复杂度,$O(1)$的空间复杂度内完成判断,并且找到环的入口。

算法描述

给定一个图(用链表描述,或者是有向边),图中可能有一个环或没有,给定起始节点,判断图中是否存在环,并且返回环的入口结点。

注意,输入要求从入口一直走下去,不会出现多叉路口。

  1. 设置两个指针(快指针和慢指针),让这两个指针指向图的入口。快指针循环一次移动两个节点,慢指针移动一个节点。如果图存在环,那么必然会在环中的某个位置相遇,记录这个位置,执行2。如果不存在环,快指针则会在$O(N/2)$的时间内到达不能向下移动的位置,算法结束。

    tA2uY8.png

  2. 重新标记两个新指针,分别指向起始位置和标记的相遇位置,让这两个指针同步向后遍历,它们相遇的位置即为图中环的入口。

tARMAx.gif

算法证明

我们约定从图的入口到环的入口之前共有$F$个节点,编号为$-F\sim-1$,约定图中的环的部分共有$N$个节点,编号为$0\sim N-1$。

对于阶段1,由于图中存在环,那么很直观,快慢指针都会进入环,由于两指针一个速度为1,一个速度为2,那么两个指针最终会在环中某个位置相遇。

将阶段1分为两部分看待,前$F$次迭代中,慢指针移动$F$次,恰好指向节点$0$,快指针移动$2F$次,指向环内某个节点$h$,其中$F\equiv h(mod\text{ }N)$,即$F$与$h$同余。继续迭代$N-h$次,慢指针显然会指向$C-h$节点,我们来分析快指针:

\[h+2(N-h)=2N-h\\ 2N-h\equiv N-h(mod \text{ } N)\]

即快指针最终也会指向$C-h$节点。

对阶段2,如下图所示,我们需要证明$F=b$即可。 需要注意的是,对于节点h,是我们利用快慢指针不同速度一定会相遇的条件得到的。那么对于快慢指针的路程有一个二倍关系,即:

\[F+a+b+a=2(F+a)\\ F=b\]

tAfj1O.png

至此,理论证明完毕。

例子

leetcode142

Code:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* faster = head;
        ListNode* slower = head;
        ListNode* target = NULL;
        while (faster && faster->next) {
            faster = faster->next->next;
            slower = slower->next;
            if (faster == slower) {
                target = faster;
                break;
            }
        }
        if (target == NULL) return NULL;
        ListNode* ptr1 = head, *ptr2 = target;
        while (ptr1 != ptr2) {
            ptr1 = ptr1->next;
            ptr2 = ptr2->next;
        }
        return ptr1;
    }
};