問題描述:
一個單向鏈表的結構,其中一個節點通常爲保存一個數據的容器object(data)和一個指向下一個保存數據的容器的地址next組成。
什麼叫帶環,或者有迴環呢?
比如當前節點的next指向當前節點之前的任意一個節點的地址,那麼我們說這個單向鏈表有迴環。或者帶環。
1.那麼如何判斷一個單向鏈表有環呢?
雖然網上有很多博客都說教式的闡述瞭如何判斷是否有環,但我覺得大多都寫得很爛。所以我決定自己寫一篇。讓自己深刻一遍。
我們先思考怎樣纔算有環,或者有環的條件?
我們大腦裏要有一個有環的單向鏈表結構。這裏我們看當一個單向鏈表中只要有兩個節點的next指向同一個節點的地址時,是不是就是有環了。如何理解這句話,如下圖,綠色線代表一個迴環,P節點爲連接點,我們看是不是a1和a2指向同一個P節點時,這個單向鏈表就是有迴環的。
當我們找出了問題的根本,再來看其他的博客時,就會好理解得多了。
2.爲什麼其他博客中會說到一個走的快的指針和一個走的慢的指針,當他們指向同一個節點時,就代表這個單向鏈表有環了?
操場跑道是一個環對吧。當從學校宿舍到跑道的路程當做還未進入環的鏈表節點,跑道就是鏈表的迴環,一個跑的快,一個跑的慢,跑的慢的將永遠追不上跑的慢的,但是一旦進入的跑道,或者鏈表迴環,那麼一旦跑的快的超過慢的一個迴環的路程後,他們就相遇了。這就能證明這個鏈表有迴環。速度不同,相遇<===>有迴環 , 速度不同,不相遇<===>無迴環。因爲我們先確定兩個指針速度不同,那麼根據是否相遇 就能確定是否有迴環了。這是結論。
3.重中之重!爲什麼要規定慢的走一步,而快的走兩步?而不是慢的一步,快的三步,或者其他?
這算是判斷鏈表是否有環的問題最重要的一個規則了,百分之99的博客只說了結“慢的走一步,而快的走兩步”,並未說爲什麼,這裏我們來思考爲什麼。如下圖
有6個節點的一個迴環。A快指針每次走3步,B指針每次都一步。
當B慢指針進入迴環的1位置時,A指針在4位置。
當B慢指針在2位置時,A快指針在1位置。(這就是上圖示例的位置)
當B慢指針在3位置時,A快指針在4位置。
當B慢指針在4位置時,A快指針在1位置。
當B慢指針在5位置時,A快指針在4位置。
當B慢指針在6位置時,A快指針在1位置。
當B慢指針在1位置時,A快指針在4位置。(此時已經和剛進入圈的時候位置一樣了)
當B慢指針在2位置時,A快指針在1位置。
當B慢指針在3位置時,A快指針在4位置。
如果你細心發現他們永遠不會相遇,A與B指針永遠不會在同一個位置。
那麼爲什麼呢???
我們還是來思考兩個指針走過的節點數相差的節點數量。是不是隻有兩個指針相差0個節點,6個節點,12個節點,18個....節點的時候他們纔會相遇。想象操場,快的比慢的超過1圈的距離,2圈的距離,3圈的距離時,纔會相遇。那麼如果快的和慢的一個走3步一個走一步,如果開始位置不同,一個1一個2,那麼他們永遠是(2,5,)(3,8)(4,11)(5,14)(6,17)
相差的數量爲:3,5,7,9,11,13,14....大於6的減去迴環6,那麼他們永遠相差3,5,1,3,5,1.....永遠不會相差6的倍數個。
4.那麼快的走2步,慢的走1步有什麼不同?
還是從相差的節點數量來考慮。不論開始相差幾個,比如開始進入迴環的時候,一個是1一個是5,
那麼情況是(1,5,)(2,7)(3,9)(4,11)(5,13)...
相差位置爲4,5,6,7,8.....
相信聰明的你已經知道爲什麼要求快的走2步,慢的走一步了?因爲只有這樣,不論你的迴環是多少節點組成,兩個節點相差的距離中間只要不漏掉任何一個相差數額,最後總會等於迴環的節點數,而當相差的距離等於迴環數的時候就是他們相遇的時刻。就是快的超過慢的一圈或者n圈的時刻。
這就是爲什麼要求快的走2步,慢的走1步的原因。
其他那些博客寫的啥玩意,看也看不懂一堆公式扔給你也不告訴你爲什麼。氣 _(:з」∠)_
既然知道里原理,實現代碼就好寫了。
Java代碼實現:
public class test{
public boolean hasCycle(ListNode head){
ListNode slow,fast;//定義快慢指針
slow=fast=head;//同一起跑線
while(fast!=null&&fast.next!=null){//當有指針指向null時,說明到尾結點了,說明此鏈表無環
slow=slow.next;//走一步
fast=fast.next.next;//走兩步
if(fast==slow){//相遇
return ture;
}
}
return false;
}
}
//單向鏈表的一個節點
public class ListNode{
int value;//博客裏說裝數據的地方
ListNode next;//下一個節點的地址
ListNode(int x){//本節點構造器
value=x;//頭節點
next=null;
}
}