帶環單鏈表的環入口點

判斷一個單鏈表是否帶環,我們可以設置兩個指針,一快一慢,慢的指針每次走一步,快的指針每次走兩步,如果在遍歷過程中,快慢指針相遇,則表明鏈表有環,否則無環;而,如何找到環的入口點呢?

給出一個定理:快慢指針的相遇點到環入口點的距離等於頭結點到環入口點的距離;

LinkList Node {
    int value;
    LinkList next;
};

LinkList findCircleNode(LinkList *head) {
    LinkList *slow = head;
    LinkList *fast = head;
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast) {
            break;//找到相遇點
        }
    } 
    if(fast == NULL || fast->next == NULL) {
        return NULL;
    }
    slow =head;
    while(slow != fast) {
        slow = slow->next;
    }
    return slow;
}

假設鏈表在(甩尾)環外長度爲 a(結點個數),環內長度爲 b 。
則總長度(也是總結點數)爲 a+b 。
從頭開始,0 base 編號。
將第 i 步訪問的結點用 S(i) 表示。i = 0, 1 …
當 i<a 時,S(i)=i ;
當 i≥a 時,S(i)=a+(i-a)%b 。
分析追趕過程:
兩個指針分別前進,假定經過 x 步後,相遇。則有:S(x)=S(2x)
由環的週期性有:2x=tb+x 。得到 x=tb 。
另外,相遇時,必須在環內,不可能在甩尾段,有 x>=a 。
連接點爲從起點走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到結論:從碰撞點 x 前進 a 步即爲連接點。
根據假設易知 S(a-1) 在甩尾段,S(a) 在環上,而 S(x+a) 必然在環上。所以可以發生碰撞。
而,同爲前進 a 步,同爲連接點,所以必然發生碰撞。
綜上,從 x 點和從起點同步前進,第一個碰撞點就是連接點。
/////////////////////////////////////////////////////////////
假設單鏈表的總長度爲L,頭結點到環入口的距離爲a,環入口到快慢指針相遇的結點距離爲x,環的長度爲r,慢指針總共走了s步,則快指針走了2s步。另外,快指針要追上慢指針的話快指針至少要在環裏面轉了一圈多(假設轉了n圈加x的距離),得到以下關係:
s = a + x;
2s = a + nr + x;
=>a + x = nr;
=>a = nr - x;
由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後一定相遇在環入口節點;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章