[算法]判断一个链表是否有环及环开始的位置

这是一道很常见的面试问题,,只用两个变量通过O(n)的时间复杂度就可以解决。
Floyd cycle detection算法,也叫做tortoise and hare算法,龟兔算法吧。
http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare
以下内容为对思路重新梳理了一下,参考了stackoverflow论坛的讨论
http://stackoverflow.com/questions/2936213/explain-how-finding-cycle-start-node-in-cycle-linked-list-work
http://stackoverflow.com/questions/494830/how-to-determine-if-a-linked-list-has-a-cycle-using-only-two-memory-locations
和thestoryofsnow的博客
http://blog.youkuaiyun.com/thestoryofsnow/article/details/6822576
代码来自thefutureisour
http://blog.youkuaiyun.com/thefutureisour/article/details/8174313

问题:如何检测一个链表是否有环,如果有,那么如何确定环的起点.

龟兔解法的基本思想可以用我们跑步的例子来解释,如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。

基于上面的想法,Floyd用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行,从后面的讨论我们可以看出这一点。ps:考虑一个链表A有1000000个结点的环,链表B是有1000000个的非环的结点,然后再加一个只有3个结点的小环,如果兔子跑的更快点,即每次前进多于2步,那么能更快的检测链表A,但链表B就很慢,因为要一直绕圈等乌龟,所以选择2步是一个tradeoff)。如果两者在链表头以外的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾,那么说明没环。

证明如下:

这样做的道理我用下图解释这里写图片描述
假设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i

1) i = m + p * n + k
2) 2i = m + q * n + ki

其中,p和q分别为慢指针和快指针在第一次相遇时转过的圈数。

2 ( m + p * n + k ) = m + q * n + k
=> 2m + 2pn + 2k = m + nq + k
=> m + k = ( q - 2p ) n

只要有一组p,q,k满足这个式子,假设就成立了。
我们假设

p = 0
q = m
k = m n - m

那么我们证明如下

m + k = ( q - 2p ) n
=> m + mn - m = ( m - 2*0) n
=> mn = mn.

这时,i为

i = m + p n + k
=> m + 0 * n + mn - m = mn.
假设成立。

环的检测

环的检测是Floyd解法的第二部分。
一旦乌龟和兔子相遇,将慢指针移到链表起点,快指针还在他们相遇的地方,即离环的起点距离k的地方。然后慢指针和快指针同时移动,每次移动一步,那么两者再次相遇的地方就是环的起点。

证明如下:
让乌龟和兔子都同时移动m+k步,乌龟到了他们最初遇见的地方(远离环起点k的位置)
之前我们得到 m + k = (q - 2p) n
即兔子走了q-2p圈,也到了和乌龟同样的位置
现在我们不让乌龟走 m+k 步,让乌龟只走 m 步,兔子也后退k步,即到了环的起点,这是他们第一次相遇的地方。
当他们相遇时,乌龟走的步数就是环的地点在的位置。

求环的长度的问题,第一次相遇后,再次相遇时,走的距离就是环的长度。

#include <stdio.h>  

typedef struct Node  
{  
    int val;  
    Node *next;  
}Node,*pNode;  



//判断是否有环  
bool isLoop(pNode pHead)  
{  
    pNode fast = pHead;  
    pNode slow = pHead;  
    //如果无环,则fast先走到终点  
    //当链表长度为奇数时,fast->Next为空  
    //当链表长度为偶数时,fast为空  
    while( fast != NULL && fast->next != NULL)  
    {  

        fast = fast->next->next;  
        slow = slow->next;  
        //如果有环,则fast会超过slow一圈  
        if(fast == slow)  
        {  
            break;  
        }  
    }  

    if(fast == NULL || fast->next == NULL  )  
        return false;  
    else  
        return true;  
}  

//计算环的长度  
int loopLength(pNode pHead)  
{  
    if(isLoop(pHead) == false)  
        return 0;  
    pNode fast = pHead;  
    pNode slow = pHead;  
    int length = 0;  
    bool begin = false;  
    bool agian = false;  
    while( fast != NULL && fast->next != NULL)  
    {  
        fast = fast->next->next;  
        slow = slow->next;  
        //超两圈后停止计数,挑出循环  
        if(fast == slow && agian == true)  
            break;  
        //超一圈后开始计数  
        if(fast == slow && agian == false)  
        {             
            begin = true;  
            agian = true;  
        }  

        //计数  
        if(begin == true)  
            ++length;  

    }  
    return length;  
}  


//求出环的入口点  
Node* findLoopEntrance(pNode pHead)  
{  
    pNode fast = pHead;  
    pNode slow = pHead;  
    while( fast != NULL && fast->next != NULL)  
    {  

        fast = fast->next->next;  
        slow = slow->next;  
        //如果有环,则fast会超过slow一圈  
        if(fast == slow)  
        {  
            break;  
        }  
    }  
    if(fast == NULL || fast->next == NULL)  
        return NULL;  
    slow = pHead;  
    while(slow != fast)  
    {  
        slow = slow->next;  
        fast = fast->next;  
    }  

    return slow;  
}  

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值