JAVA怎麼判斷鏈表成環(詳解)

第一次旅行

疫情期間在家憋了三個月,長胖了二十斤,我已然變成了個胖子,女朋友對我的身材管理很不滿意,於是打着程序員容易猝死的旗號讓我陪她出去旅行(逼我出去運動)。
那麼怎麼個運動法呢?騎車!地圖如下:
騎車地圖1
女朋友小好要我去騎車,她說她坐順風車監督我。爲了能有效地督促我跑步,她讓我先出發,一會打車追我。
貓抓老鼠於是我心(ji)甘(bu)情(qing)願(yuan)地就出發了,

騎車地圖2
我終於抵達五棵松體育場,正當我準備去華熙live買冰激凌的時候,女朋友來電話對我說她要出發了,如果她追到我的時候還沒抵達復興門我就涼涼了。於是我立刻騎上了風火輪繼續前行。
騎車地圖3
終於廢了九牛二虎之力,我抵達了復興門,好奇女朋友怎麼還沒追到我,結果得知她正在五棵松喫冰激凌??心理極度不平衡的我決定在金融街打遊戲,等她過來。
2000 years later…

騎車地圖4
終於,和女朋友在復興門相遇了,由於順風車不能停,她準備繼續坐車在後面等我。
驚不驚喜、意不意外,女朋友就這樣一直到了四惠。而我謊稱到了天安門累了就騎回家休息去了。開心地吹着空調、蓋着被子、喫着西瓜、看着電視、玩着手機。
後來,女朋友坐地鐵到了家,指責我放她鴿子,我一臉無辜的樣子讓他無可奈何。

第二次旅行

第二天女朋友埋怨我昨天沒怎麼陪她,又提議出去玩。這次她定了新的路線,而且還是讓我先行:第二次騎車地圖1
我依舊先到了五棵松,然後考慮到打車太貴,我提議讓她坐公交。於是她答應了我依舊叫了順風車- -。
第二次騎車地圖2
一切都和昨天一樣,她依舊買了冰激凌,和我相遇後依舊準備在後面等我。而我也準備故技重施,在復興門休息。
第二次騎車地圖3
結果往往沒想到,我正想偷偷回家的時候女朋友竟然從南站大廈公園繞到了復興門!
被女朋友一頓訓斥後腦我又向前出發。結果開始了一圈又一圈的騎行…
不知道過了多久,最後在國貿相遇的時候我精疲力盡,表示再也不陪她玩了!
第二次騎車地圖4

反思

我這兩次的騎行路線,換成數據結構應該是這樣:
數據結構1
以及
數據結構2
因爲我先出發,而女朋友速度比我快,所以在第一天去無環圖旅行的時候我們會相遇一次。而第二天局部有環時則會相遇無數次。
這麼看來,判斷有環無環就可以通過相遇次數解決,如果兩個人速度不同(忽略變速運動),那麼他們沒相遇或者僅相遇一次證明是無環鏈表,如果大於一次則存在環路

女朋友的嘲諷

“一個旅遊路線有沒有環還要這麼複雜去判斷?一眼不就看出來了?”

於是,我開始給她科普:

這個路線以鏈表存儲在計算機是這樣的,我們僅知道start的位置,以及後續的連接指向。如果想知道是否有環,需要從頭到尾捋一遍線路,然後將已經捋過的地方做好標記記在本子上。每一次到達一個新地方都要去查小本本是否已經記錄過了。然而這種模式效率過低,對系統內存消耗極大,所以在程序採用這種方法很不划算!
計算機鏈表
女朋友似(bu)懂(xiang)非(li)懂(wo)地點了點頭。然後追問道“你說的意思我明白了,能把代碼敲出來嗎,我看(qu)看(wan)你(wang)咋(zhe)實(rong)現(yao)的(le)“。

實現過程

我們已知結論:速度不同的兩個遊標對鏈表做勻速遍歷,如果相遇大於1次,則爲有環鏈表。
我們不妨把情況改的簡單一點,如果保證兩個遊標同時出發做速度不同的勻速運動,那麼只要他們相遇了就證明鏈表有環
爲了方便理解,我們可以先想象成 慢的人速度爲1、快的人速度爲2。
開始寫代碼:

Node p1 = head;//先都指向頭結點
Node p2 = head;
int times = 0;//相遇0次
while(p2!=null&&p2.next!=null)//如果快遊標到結尾就退出循環
{
	p1=p1.next;//一次走一步
	p2=p2.next.next;//一次走兩步
	if(p1==p2)
	{
		return isCycle;//相遇即證明有環
	}
}

疑點解釋

有沒有可能在循環內慢速的遊標會始終和快速遊標無法相遇

如果你沒有這個疑惑,就不用看了,如果你有,那麼我跟你講下爲什麼:
你的邏輯應該是這麼想的:高速遊標一直循環,低速遊標也一直循環,但是由於高速遊標步距爲2,所以有可能導致慢遊標始終無法相遇於在高速整點位置。
先放結論:不可能
公式推導(感謝評論區小夥伴)
前提:快慢遊標進入循環後一定會先後落腳於同一個交點。
如下圖所示,我們設圓爲鏈表的循環部分,設其有8個循環結點。其中快遊標的步距爲2(速度爲2),慢遊標步距爲1(速度爲1)。故快遊標的遍歷路線爲順時針的綠色虛線,慢遊標的遍歷路線爲順時針橙色虛線。
不難看出快慢遊標都會先後駐足於綠色點。
循環鏈表
爲了增加說服力,我們將慢遊標步距設爲2,快遊標步距設爲3, 還是存在不同時間的公共駐足點:
循環鏈表2
所以假設快慢遊標先後落腳於循環結點x,如下圖,此時快遊標先抵達x,此時慢遊標一定在它之前的某個結點:

快遊標抵達
然後慢遊標抵達x結點,此時快遊標可以在循環結點中的任何位置。
慢遊標抵達
都進入循環鏈表後,我們希望會出現一下場景:快慢遊標同時相遇於x結點。
相遇
假設
快慢遊標相遇於曾先後駐足的x結點。
已知
慢遊標步距分別爲xA、xB;
鏈表中循環部分長度爲s;
慢遊標從進入循環結點x到兩個遊標相遇的時間爲t;
推理
(xA-xB)* t則爲快遊標在循環中比慢遊標多走的距離。
只要滿足:[(xA-xB)* t]%s=0即可證明它們會在循環中相遇於結點x。
故只要存在一個正整數t使得(xA-xB)*t = ns成立即可證明我們的假設。
因爲xA!=xB,可得 t=ns/(xA-xB)不難看出(xA-xB)必爲正整數,n爲任意正整數,故一定存在一個正整數t使得等式成立。
結論:
綜上所述,可得出,當快慢遊標步距不相同的情況下,不論鏈表中循環結點的個數有多少,必定存在一個時刻t使得兩個遊標會在同一結點x相遇。

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