[面試專題05]Python全棧日記-判斷連結串列是否存在環
Python的面試題,之前的班考試的時候發的,3道資料結構的題,冒泡之前在學資料結構的時候說過,二叉樹之後會講的,今天說說檢查一個單鏈表是否有環和如何確定環的位置。
首先如何確定有環?
我們給連結串列設定一個指標,當這個指標一直往下走,如果指標最後等於none,則單鏈表無環
如果指標一直走,不會停下,那麼就說明有環。
確定有環很簡單,那如何確定入口呢?也就是環在哪裡開始。
我們給單鏈表的頭設定兩個指標,一個為slow一個為fast。
slow每次前進1格,slow=slow.next
fast每次前進2格,fast=fast.next.next
這是一個數學問題,如果有環那麼fast指標一定會追趕上slow指標
我們假設從head到入口i的長度為a
假設他們在p點相遇
相遇是距離入口為x
環的周長為r
接下來就是通過數學公式的推導來驗證a與·slow和fast移動時的關係。
警告:接下來的內容純為數學推導,如果用正常邏輯進行思考是想不明白為啥會這樣的,而且我感覺只在fast每次移動距離是slow兩倍下成立,我們只需要通過數學推導找出如何求a就行了。
首先:
當slow和fast在p點相遇,因為fast速度是slow的兩倍,所以相遇時fast走過的路程也是slow的兩倍,所以我們設slow走過的路程為S,fast走過的路程為2S
Fast和slow相遇時,fast肯定在環中不斷轉圈,假設轉了n圈,
因為他們經過的a與x相同,所以他們的路程差就為fast多轉的這幾圈
所以:2S – S = nr
又因為:2S – S = S
所以:S = nr 、2S = 2nr
又因為fast走過的路程也可以寫為:
2S = a + x + nr
2S = 2nr
所以:
a + x + nr = 2nr
推出:
a = nr -x = (n-1+1)r – x = (n-1)r +( r – x)
所以最後推匯出
a = (n-1)r +( r – x)
這個式子中:
(r – x) 在圖中就是紫色的這段長度
(n-1)r:就是在環中轉圈
所以就是說,當我們fast和slow相遇在p後,如果把slow扔在head處,
讓slow每次還是前進一格( slow = slow.next )
然後fast從相遇點p開始每次前進一格( fast = fast.next )
那麼他們一定會在迴圈入口i點相遇
因為a = (n-1)r +( r – x)
a 代表slow從head移動到入口i的距離,
*a =(n-1)r +( r – x)*代表fast從相遇點p出發不論在環中繞多少圈,最後都會在i點與slow相遇。
所以通過slow再次與fast相遇時走過的距離就能直到迴圈入口i在何處。
過程就是這樣,你要去想其中的邏輯就會鑽牛角尖,因為這個純為數學推導,並沒有為啥。
接下來通過程式碼實現:
class Node(object): #連結串列指標和內容
def __init__(self,item):
self.item = item #連結串列每個結點的內容
self.next = None #無環的單鏈表的最後節點應該指向空
#我們先建立一個又環的連結串列,這裡就不用類了,面試的時候如果沒要求也可以這樣
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node3 #最後一個節點指向了節點3,創造出環
def findloop(head): #判斷是否有環的函式並返回環入口
slow = head #先把slow和fast都放在head位置
fast = head
loopExist = False #定義一個標記,標記是否存在環
if head == None: #判斷連結串列是否為空
return False
while fast.next!=None and fast.next.next != None: #讓fast和slow開始走,如果fast一直不為空說明有環
fast = fast.next.next #fast每次前進2格
slow = slow.next #slow每次前進1格
if slow == fast: #如果slow和fast的所在節點的值相等就說明相遇了
loopExist=True #這是標記變為true代表有環
break #while迴圈結束,繼續向下
if loopExist == True: #如果有環
slow = head #把slow放在連結串列head處
while slow != fast: #當slow和fast不相等的時候,讓各自前進一步,當相等時不成立,跳出while迴圈
slow = slow.next
fast = fast.next
return slow.item #返回相等時slow的節點值
return False #和最外面的while是一對,如果那個迴圈結束就說明fast的next或者next.next為空,說明沒環,返回false
if findloop(node1): #設定第一個節點為head,如果返回為true
print('存在環')
print(findloop(node1))
else: #如果返回值為false
print("不存在環結構")
結果:
存在環
3
我修改一下我的連結串列,讓他沒環,然後試試能否判斷出來:
#我們先建立一個又環的連結串列,這裡就不用類了,面試的時候如果沒要求也可以這樣
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node5 = Node(5)
node6 = Node(6)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
#node6.next = node3 #最後一個節點指向了節點3,創造出環
結果:
不存在環結構