「Floyd 判圈算法」(又称龟兔赛跑算法)



算法描述


是否存在环路

结论:如果链表里有环,设有快慢指针从链表的头结点开始移动,快指针fast一次移动2步,慢指针slow一次移动一步。只要一直走下去,fast指针一定会和slow指针在环内相遇。反之,如果没环,那么快指针永远不会和慢指针相遇,快指针会走到末尾。

计算环的长度

方法:当快慢指针第一次相遇时,让快指针不动,慢指针再走一圈,等慢指针再次回到相遇点时,慢指针刚好走了环的长度。只要增加一个length变量,慢指针每走一步,length+1,等慢指针到相遇点时,length的值就是环的长度。

计算环的起点

方法:快慢指针第一次相遇后,让慢指针slow1留在相遇点。重新定义一个新的慢指针slow2,让它刚开始指向链表的头结点(链表的起点)。让两个慢指针slow1和slow2一起移动,每次移动一步。等两个慢指针刚相遇时,相遇点刚好是环的起点。



算法证明


证明快慢指针相遇和链表有环互为充要条件

首先,最简单的情况是,fast指针和slow指针移动速度相差1,也就是|v(fast)-v(slow)| = 1。我们可以这样理解,fast和slow指针的相对速度为1。假如有环,快指针会先进入环内,慢指针会后进入环内。当快慢指针都进入环后,从相对运动的角度考虑,可以理解为慢指针不动,快指针一次移动一位,那么快指针和慢指针的距离每次减少1,那么快指针迟早会追上"不动"的慢指针。

而对于|v(fast)-v(slow)| > 1的情况。假设快慢指针刚开始不从同一起点移动,可能会出现巧合的情况,快指针在环内永远没办法和慢指针相遇。 如下图所示:
在这里插入图片描述
快指针每次走4步,v(fast) = 4,而慢指针每次走1步,v(slow) = 1。当slow指针进入环的起点3时,快指针走4步到5,相当于比慢指针慢1步。之后快指针每次走4步,相当于绕一圈再多走1步,和慢指针每次走1步相当于同速。那样快慢指针永远没办法相遇。

可是,如果快慢指针刚开始从同一起点移动,假设快慢指针速度差距大于1。似乎里面存在复杂的数学问题,我没有举出快慢指针无法相遇的例子(如果懂的朋友希望可以在评论区指点我)。

总之,最好假设fast指针一次走2步,slow指针一次走1步,这样更好证明。

证明两个慢指针第二次相遇时,该结点就是环的起点

再看看找环的起点的方法

方法:快慢指针第一次相遇后,让慢指针slow1留在相遇点。重新定义一个新的慢指针slow2,让它刚开始指向链表的头结点(链表的起点)。让两个慢指针slow1和slow2一起移动,每次移动一步。等两个慢指针刚相遇时,相遇点刚好是环的起点。
需要2步。
1.先证明两个同速的慢指针必定会相遇。

假设快指针和慢指针第一次相遇时,慢指针需要走n步,快指针速度是慢指针的2倍,则快指针需要走2n步。
可以这样理解,从头结点开始移动的慢指针slow2,相当于刚开始的慢指针。当它走n步时,就刚好走到快慢指针第一次相遇的相遇点。
而原来的慢指针slow1是从相遇点开始走,你可以理解为,慢指针slow1相对于慢指针slow2,它先走了n步,走到相遇点,之后它们才一起走。等它再走n步时,相当于它一共走了2n步,一共走了快指针的步数。那么它一定会和快指针一样,再一次走到快慢指针的相遇点。
所以,slow1和slow2走n步后,2个慢指针slow1和slow2一定会在相遇点相遇。

2.再证明两个同速慢指针第一次相遇的结点是环的起点。
而由于2个慢指针slow1和slow2速度一样,那么它们一旦相遇,就会一直重合。那么倒推回去,它们必定是从环的起点就相遇,之后一直就重合,直到走到快慢指针的相遇点。
环的起点一定是2个慢指针首次相遇的结点,否则2个同速慢指针就永远不可能相遇。

从而证明了,只需要返回2个慢指针首次相遇的结点,即为环的起点。



代码实现


1.判断链表是否有环

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    // 如果快指针走到末尾,结束循环
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)			// 如果快慢指针相遇,返回true
            return true;
    }
    return false;					// 如果跳出循环,说明fast到末尾,返回false
}

2.求链表的环的起点

struct ListNode *detectCycle(struct ListNode *head) {
	// 如果链表是空表或者只有一个结点(且该节点的next不指向自身),直接返回false
     if (head == NULL || head->next == NULL)
        return false;
    // 快慢指针初始都指向头结点
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    do
    {
    	// 快指针每次移动2步,慢指针每次移动一步
        fast = fast->next->next;
        slow = slow->next;
        // 如果fast能走到末尾,说明没环,返回false
        if (fast == NULL || fast->next == NULL)
            return false;
    }
    while (fast != slow);				// 如果fast == slow,说明走到快慢指针相遇点
    struct ListNode* newSlow = head;	// 让新的慢指针指向链表头结点
    // 假如链表头结点也是环的起点,两个慢指针一开始就相遇,程序不需要进入循环,直接返回该结点
    // 假如链表头结点不是环的起点,两个慢指针首次相遇的结点,就是环的起点
    while (slow != newSlow)
    {
        slow = slow->next;
        newSlow = newSlow->next;
    }
    return slow;			// 首次相遇的结点位置,就是环的起点
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Floyd判圈算法又称龟兔赛跑算法,是用来判断链表是否有环、计算环的长度以及寻找环的起点的一种算法。该算法通过使用两个指针,一个慢指针和一个快指针,在链表中进行遍历。慢指针每次移动一步,而快指针每次移动两步。如果链表中存在环,那么快指针最终会追上慢指针,两个指针会相遇。这可以通过两个条件来判断:当快指针和慢指针相遇时,且快指针不为null且快指针的下一个节点也不为null。如果两个指针相遇,那么说明链表中存在环。接下来,我们可以从相遇点开始,用一个指针再次遍历链表,直到回到相遇点,记录遍历过程中的节点数。这个节点数就是环的长度。 如果慢指针和快指针相遇,那么我们可以进一步判断是否有环存在。如果相遇点是链表中的某个节点,那么从相遇点开始遍历链表,再次回到相遇点的位置一定是环的起点。这是因为从相遇点继续遍历,最终会再次回到相遇点,而在这个过程中,慢指针每次移动一步,快指针每次移动两步,所以在某一时刻,慢指针和快指针会在环的起点处相遇。 然而,如果慢指针和快指针从环的不同起点出发,那么它们再次相遇的位置会发生偏移,不会是环的起点。同样地,如果慢指针和快指针从环外的同一起点出发,它们在进入环之前还有一段距离,因此再次相遇的位置也不一定是环的起点。 综上所述,Floyd判圈算法通过两个指针的移动,可以判断链表是否有环,并且计算出环的长度和寻找环的起点。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Floyd判圈算法](https://blog.csdn.net/qq_26012495/article/details/117407011)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [cjing-interview:C ++面试; 基本算法数据结构提示](https://download.csdn.net/download/weixin_42144707/16474761)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值