1. 程式人生 > >[面試專題05]Python全棧日記-判斷連結串列是否存在環

[面試專題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,創造出環

結果:

不存在環結構