數(shù)據(jù)結構與算法之鏈表(四)單鏈表尾環(huán)問題全面分析

引言

單鏈表的尾環(huán)問題是一個比較經(jīng)典的問題初厚,它涉及的問題如下:
1>給一個單鏈表茸时,判斷其中是否有環(huán)的存在;
2>如果存在環(huán),找出環(huán)的入口點;
3>如果存在環(huán)椎眯,求出環(huán)上節(jié)點的個數(shù);
4>如果存在環(huán)神帅,求出鏈表的長度;
5>如果存在環(huán)再姑,求出環(huán)上距離任意一個節(jié)點最遠的點(對面節(jié)點);
6>(擴展)如何判斷兩個無環(huán)鏈表是否相交
7>(擴展)如果相交找御,求出第一個相交的節(jié)點.
下面在分析帶環(huán)鏈表的特征的基礎上來一一解決這些問題.

尾環(huán)鏈表特征分析

1.下圖是尾環(huán)鏈表的結構询刹。我們采用快慢指針的方法,很明顯的萎坷,這是一個典型的“跑道追及問題”,倆指針進入“跑道”,速度不一樣沐兰,那么倆指針必然會在某一時刻相遇哆档。


尾環(huán)鏈表(摘自網(wǎng)絡).png

因此判斷鏈表是否有環(huán)的關鍵就是判斷倆指針是否能相遇:

    /**
     * 判斷是否有回環(huán)
     *
     * @return
     */
    public boolean isLoop() {
       if (isEmpty()) {
            return false;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//倆哥們相遇,則返回true
                return true;
            }
        }
        //如果是單鏈表住闯,快指針肯定會走到結尾跳出循環(huán)
        return false;
    }

2.下面分析入口點問題瓜浸。如下圖:

尾環(huán)鏈表環(huán)入口分析(摘自網(wǎng)絡).png

說明:a為頭到入口的距離,x為入口到相遇點的距離,r為環(huán)長度,鏈表長度為L,設定環(huán)的位置以入口為原點比原,順時針為正方向插佛。這里為了更加清晰的說理,這里特殊到一般分兩種情況分析:
1>環(huán)較大量窘,倆指針在環(huán)內一周之內相遇:當slow到達入口時雇寇,fast走了2a,在環(huán)內的位置為2a-a = a,此時fast在環(huán)上順時針領先slow距離為a,相應的slow在環(huán)上順時針"領先"fast的距離為r-a,因為每一步fast都比slow快一步蚌铜,所以再經(jīng)過r-a步锨侯,fast就可以追上slow了,此時相遇時冬殃,slow共走了a+(r-a)=r步囚痴,那么在環(huán)的位置為r-a,此時有相遇點到入口的順時針距離正好為a,與頭結點到入口的距離相等;
2>環(huán)較小审葬,快指針已經(jīng)在環(huán)里浪了n(n>=1)圈才與慢指針相遇:設頭結點到相遇點的距離為s,那么fast走了2s,另外fast在環(huán)里轉了n圈距離為(nr + s),所以有2s = s+nr得s = nr;又有a+x=s=nr,得a + x = (n-1)r + (L - a),推出a = (n-1)*r+(L-a-x);后者為環(huán)上相遇點到入口的順時針距離.前者代表環(huán)轉圈路程深滚,這個公式表示的現(xiàn)實意義為:設定倆指針,一個指針p從head出發(fā)向入口運動,另外一個指針q從相遇點出發(fā)在環(huán)內順時針運動涣觉,一定步數(shù)后一定會在入口相遇,此結論與情況1一致痴荐。
因此問題2的實現(xiàn)思路為:找到相遇點,然后分配指針分別從頭結點和相遇點出發(fā)向前移動旨枯,如果相遇則相遇點為入口點.代碼如下:

   /**
     * 查找環(huán)入口
     *
     * @return
     */
    public SNode<T> findLoopStartNode() {
      if (isEmpty()) {
            return null;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//倆哥們相遇蹬昌,則返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return null;//沒有環(huán)則返回空
        SNode<T> p = headNode;//從頭節(jié)點出發(fā)
        SNode<T> q = slow;//從相遇點出發(fā)
        while (p != q) {
            p = p.next;
            q = q.next;
        }
        return p;//相遇點為環(huán)入口
    }

3.有了環(huán)入口,那么環(huán)大小就好說了,從相遇點開始遍歷就行:

    /**
     * 獲取環(huán)大小
     * @return
     */
    public int getLoopSize() {
          if (isEmpty()) {
            return 0;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//倆哥們相遇攀隔,則返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return 0;//沒有環(huán)則返0
        int size = 1;//環(huán)size從1開始
        SNode<T> temp = slow.next;//從相遇點出發(fā)
        while (temp != slow) {
            size++;
            temp = temp.next;
        }
        return size;
    }

4.求環(huán)鏈表的長度:環(huán)鏈表的長度為環(huán)大小和入口到頭節(jié)點距離的和:

   /**
     * 獲取頭節(jié)點到入口的距離
     *
     * @return
     */
    public int getLoopEntryDistance() {
        if (isEmpty()) {
            return 0;
        }
        SNode<T> slow = headNode;
        SNode<T> fast = headNode;
        while (slow != null && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {//倆哥們相遇皂贩,則返回true
                break;
            }
        }

        if (slow == null || fast == null || fast.next == null) return size();//沒有環(huán)則返回單鏈表長度
        SNode<T> p = headNode;//從頭節(jié)點出發(fā)
        SNode<T> q = slow;//從相遇點出發(fā)
        int distance = 0;
        while (p != q) {
            distance++;
            p = p.next;
            q = q.next;
        }
        return distance;
    }

   /**
     * 獲得帶環(huán)鏈表大小
     * @return
     */
    public int getLoopLinkedListSize(){
        return getLoopSize() + getLoopEntryDistance();
    }

5.對面節(jié)點問題栖榨。環(huán)上距離節(jié)點最遠的節(jié)點為對面節(jié)點,如圖1和4明刷、2和5婴栽、3和6為對面節(jié)點,且距離最大為環(huán)大小的一半辈末。思路如下:判斷節(jié)點是否在環(huán)上愚争,如果在環(huán)上從當前節(jié)點前移環(huán)大小一半的步數(shù).


對面節(jié)點問題.png
   /**
     * 獲得對面節(jié)點
     *
     * @return
     */
    public SNode<T> getOppositeNode(SNode<T> node) {
        boolean isLoop = false;
        if (isEmpty() || node == null) {
            return null;
        }
        boolean isOnLoop = false;
        //判斷節(jié)點是否在環(huán)上
        SNode<T> startNode = findLoopStartNode();
        if (startNode == null) {//沒有環(huán),返回空
            return null;
        }
        int loopSize = getLoopSize();
        for (int i = 0; i < loopSize; i++) {
            if (node == startNode) {
                isOnLoop = true;
                break;
            }
            startNode = startNode.next;
        }
        SNode<T> temp = node;
        if (isOnLoop) {
             //走環(huán)一半步數(shù)
            for (int i = 0; i < loopSize / 2; i++) {
                temp = temp.next;
            }
        }
        return temp;
    }

6.對于倆擴展問題挤聘,可以這樣理解:加入鏈表A和B有交點轰枝,那么將其中一個鏈表首尾相連,那么另一個鏈表上必然會出現(xiàn)環(huán)组去,問題就轉化成問題一鞍陨,這里不在贅述。
總結:關于單鏈表从隆,最核心的操作就是玩轉節(jié)點的遍歷诚撵、斷開和連接操作,這四篇博客基本涵蓋了單鏈表的大部分操作键闺,為什么花費如此多的精力去學習它呢?因為它是基礎寿烟,包含了節(jié)點操作的基本思想,熟悉了它辛燥,后面的雙向鏈表筛武、棧、隊列就相對容易购桑。當然畅铭,還是那句話:學習數(shù)據(jù)結構就是學習思想。一沙一世界勃蜘,單鏈表也有很多東西可以發(fā)掘硕噩!關于單鏈表的學習暫時告一段落,如有紕漏歡迎指正缭贡!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末炉擅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阳惹,更是在濱河造成了極大的恐慌谍失,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莹汤,死亡現(xiàn)場離奇詭異快鱼,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門抹竹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來线罕,“玉大人,你說我怎么就攤上這事窃判〕ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵袄琳,是天一觀的道長询件。 經(jīng)常有香客問我,道長唆樊,這世上最難降的妖魔是什么宛琅? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮逗旁,結果婚禮上夯秃,老公的妹妹穿的比我還像新娘。我一直安慰自己痢艺,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布介陶。 她就那樣靜靜地躺著堤舒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哺呜。 梳的紋絲不亂的頭發(fā)上舌缤,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音某残,去河邊找鬼国撵。 笑死,一個胖子當著我的面吹牛玻墅,可吹牛的內容都是我干的介牙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼澳厢,長吁一口氣:“原來是場噩夢啊……” “哼环础!你這毒婦竟也來了?” 一聲冷哼從身側響起剩拢,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤线得,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徐伐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贯钩,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了角雷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祸穷。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谓罗,靈堂內的尸體忽然破棺而出粱哼,到底是詐尸還是另有隱情,我是刑警寧澤檩咱,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布揭措,位于F島的核電站,受9級特大地震影響刻蚯,放射性物質發(fā)生泄漏绊含。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一炊汹、第九天 我趴在偏房一處隱蔽的房頂上張望躬充。 院中可真熱鬧,春花似錦讨便、人聲如沸充甚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伴找。三九已至,卻和暖如春废菱,著一層夾襖步出監(jiān)牢的瞬間技矮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工殊轴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衰倦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓旁理,卻偏偏與公主長得像樊零,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子孽文,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355