檢測鏈表有環

題目:如何判斷一個單鏈表是否有環?若有環,如何找出環的入口節點。

一、單鏈表是否有環

思路分析:

單鏈表有環,是指單鏈表中某個節點的next指針域指向的是鏈表中在它之前的某一個節點,這樣在鏈表的尾部形成一個環形結構。判斷鏈表是否有環,有以下幾種方法。

// 鏈表的節點結構如下
typedef struct node
{
    int data;
    struct node *next;
} NODE;

(1)最常用方法:定義兩個指針,同時從鏈表的頭節點出發,一個指針一次走一步,另一個指針一次走兩步。如果走得快的指針追上了走得慢的指針,那么鏈表就是環形鏈表;如果走得快的指針走到了鏈表的末尾(next指向 NULL)都沒有追上第一個指針,那么鏈表就不是環形鏈表。

// 判斷鏈表是否有環
bool IsLoop(NODE *head) // 假設為帶頭節點的單鏈表
{
    if (head == NULL)
        return false;

    NODE *slow = head->next; // 初始時,慢指針從頭節點開始走1步
    if (slow == NULL)
        return false;

    NODE *fast = slow->next; // 初始時,快指針從頭節點開始走2步
    while (fast != NULL && slow != NULL) // 當單鏈表沒有環時,循環到鏈表尾結束
    {
        if (fast == slow)
            return true;

        slow = slow->next; // 慢指針每次走一步

        fast = fast->next;
        if (fast != NULL)
            fast = fast->next;
    }

    return false;
}

(2)通過使用STL庫中的map表進行映射。首先定義 map<NODE *, int> m; 將一個 NODE * 指針映射成數組的下標,并賦值為一個 int 類型的數值。然后從鏈表的頭指針開始往后遍歷,每次遇到一個指針p,就判斷 m[p] 是否為0。如果為0,則將m[p]賦值為1,表示該節點第一次訪問;而如果m[p]的值為1,則說明這個節點已經被訪問過一次了,于是就形成了環。

#include <iostream>
using namespace std;
#include <map>

// 使用STL中的map來判斷單鏈表中是否有環
map<NODE *, int> m;
bool IsLoop_2(NODE *head)
{
    if (head == NULL)
        return false;

    NODE *p = head;

    while (p)
    {
        if (m[p] == 0) // 一般默認值都是0
            m[p] = 1;
        else if (m[p] == 1)
            return true;

        p = p->next;
    }

     return false;
}

(3)不推薦使用:定義一個指針數組,初始化為空指針,從鏈表的頭指針開始往后遍歷,每次遇到一個指針就跟指針數組中的指針相比較,若沒有找到相同的指針,說明這個節點是第一次訪問,還沒有形成環,將這個指針添加到指針數組中去。若在指針數組中找到了同樣的指針,說明這個節點已經訪問過了,于是就形成了環。

二、若單鏈表有環,如何找出環的入口節點。

步驟:

<1> 定義兩個指針p1和p2,在初始化時都指向鏈表的頭節點。

<2> 如果鏈表中的環有n個節點,指針p1先在鏈表上向前移動n步。

<3> 然后指針p1和p2以相同的速度在鏈表上向前移動直到它們相遇。

<4> 它們相遇的節點就是環的入口節點。

那么如何得到環中的節點數目?可使用上述方法(1),即通過一快一慢兩個指針來解決這個問題。當兩個指針相遇時,表明鏈表中存在環。兩個指針相遇的節點一定是在環中。可以從這個節點出發,一邊繼續向前移動一邊計數,當再次回到這個節點時,即可得到環中的節點數了。

// 1、先求出環中的任一節點
NODE *MeetingNode(NODE *head) // 假設為帶頭節點的單鏈表
{
    if (head == NULL)
        return NULL;

    NODE *slow = head->next; // 初始時,慢指針從頭節點開始走1步
    if (slow == NULL)
        return NULL;

    NODE *fast = slow->next; // 初始時,快指針從頭節點開始走2步
    while (fast != NULL && slow != NULL) // 當單鏈表沒有環時,循環到鏈表尾結束
    {
        if (fast == slow)
            return fast;

        slow = slow->next; // 慢指針每次走一步

        fast = fast->next;
        if (fast != NULL)
            fast = fast->next;
    }

    return NULL;
}

// 2、從已找到的那個環中節點出發,一邊繼續向前移動,一邊計數,當再次回到這個節點時,就可得到環中的節點數了。
NODE *EntryNodeOfLoop(NODE *head)
{
    NODE *meetingNode = MeetingNode(head); // 先找出環中的任一節點
    if (meetingNode == NULL)
        return NULL;

    int count = 1; // 計算環中的節點數
    NODE *p = meetingNode;
    while (p != meetingNode)
    {
        p = p->next;
        ++count;
    }

    p = head;
    for (int i = 0; i < count; i++) // 讓p從頭節點開始,先在鏈表上向前移動count步
        p = p->next;

    NODE *q = head; // q從頭節點開始
    while (q != p) // p和q以相同的速度向前移動,當q指向環的入口節點時,p已經圍繞著環走了一圈又回到了入口節點。
    {
        q = q->next;
        p = p->next;
    }

    return p;
}

備注:在MeetingNode方法中,當快慢指針(slow、fast)相遇時,slow指針肯定沒有遍歷完鏈表,而fast指針已經在環內循環了n(n>=1)圈。假設slow指針走了s步,則fast指針走了2s步。同時,fast指針的步數還等于s加上在環上多轉的n圈,設環長為r,則滿足如下關系表達式:

2s = s + nr;

所以可知:s = nr;

假設鏈表的頭節點到“環的尾節點“的長度為L(注意,L不一定是鏈表長度),環的入口節點與相遇點的距離為x,鏈表的頭節點到環入口的距離為a,則滿足如下關系表達式:

a + x = s = nr;

可得:a + x = (n - 1)r + r = (n - 1)r + (L - a)

進一步得:a = (n - 1)r + (L -a - x)

結論:

<1> (L - a -x)為相遇點到環入口節點的距離,即從相遇點開始向前移動(L -a -x)步后,會再次到達環入口節點。

<2> 從鏈表的頭節點到環入口節點的距離 = (n - 1) * 環內循環 + 相遇點到環入口點的距離。

<3> 于是從鏈表頭與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環入口點。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 搞懂單鏈表常見面試題 Hello 繼上次的 搞懂基本排序算法,這個一星期,我總結了,我所學習和思考的單鏈表基礎知識...
    醒著的碼者閱讀 4,608評論 1 45
  • 推薦大家看下《編程之美》、《程序員面試金典》 還有編程相關網站:leetcode老師講的很多題其實就是這些書和網...
    偷天神貓閱讀 1,282評論 0 6
  • 昕鷺閱讀 97評論 1 1
  • 關于啟蒙數學的目的,已經講了很多講了,其中反復提到過“目前有很多家長,并沒有意識到啟蒙孩子數學的目的關鍵是啟蒙孩子...
    車成子閱讀 337評論 0 2
  • 從去年開始,沈霜把頭發剪短染白。 都說她是模仿斯特里普在《穿PRADA的魔鬼》里的造型,其實除了這一把銀色短發,與...
    耀塔創作組閱讀 851評論 0 3