Python鏈表操作
在Python開發的面試中,我們經常會遇到關於鏈表操作的問題。鏈表作為一個非常經典的無序列表結構,也是一個開發工程師必須掌握的數據結構之一。在本文中,我將針對鏈表本身的數據結構特點,以及鏈表的一些常見操作給大家做一個深入淺出的講解,希望本文的讀者能夠掌握鏈表的操作。
1. 什么是鏈表?
簡單地說,鏈表是一種無序的列表。你可以把鏈表里面的數據看成是隨機排列的,元素之間並沒有固定的先后順序。所以,既然是無序的,那么我們就無法像操作list對象一樣簡單地用index來去定位和操作里面的元素,如下圖所示:
圖1
那么針對這種情況,我們怎么才能找到里面的各個元素呢?
其實,在鏈表里面的每個元素其實是一種叫Node的節點對象,每個Node會保存兩個信息,一個是這個節點的值value,另一個是這個節點對應的下一個節點的引用next。這樣,節點與節點之間其實就形成了一種鏈式結構,就像一根鐵鏈一樣,一環扣一環,這也是鏈表這個名字的由來。有了這樣的鏈式結構,只要給我們一個起點,我們就能沿着起點一個節點一個節點地找到所有的節點,這就是鏈表整個數據結構的核心。
2. 如何實現鏈表?
前面已經講過,鏈表結構的關鍵是Node節點對象,所以要實現鏈表之前,我們需要先定義Node節點對象。每個節點中保存着當前節點的數據value,以及當前節點的下一個節點的引用next,所以我們可以這樣來定義一個Node對象:
class Node:
def __init__(self, data):
self.data = data # 保存當前節點的值
self.next = None # 保存當前節點中下一個節點的引用
為了能夠獲取和設置Node里面的信息,我們還需要定義幾個方法,代碼如下:
def __init__(self, data):
self.data = data
self.next = None
# 獲取node里面的數據
def getData(self):
return self.data
# 獲取下一個節點的引用
def getNext(self):
return self.next
# 設置node里面的數據
def setData(self, newdata):
self.data = newdata
# 設置下一個節點的引用
def setNext(self, newnext):
self.next = newnext
這些方法用於存取Node里面的數據,方便在鏈表結構里面去使用。
Node對象定義好之后,接下來我們就可以開始定義鏈表對象了。我們這里講的是單向鏈表,所以英文成為Single Link List。定義鏈表時,最主要的是定義好鏈表的頭(head),因為之前我們說過,我們只要找到了鏈表的頭,就能夠沿着這個頭找到其他所有的node。所以,鏈表的初始定義很簡單,我們只要定義一個head屬性即可,代碼如下:
class MySingleLinkList():
def __init__(self):
# 定義鏈表的頭結點
self.head = None
這里大家要注意一點,鏈表對象本身是不包含任何節點Node對象的,相反,它只包含對鏈接結構中第一個節點的單個引用(self.head),這個head實際上永遠會指向鏈表中的第一個節點。如果head為None,實際上意味着這是一個空的鏈表。鏈表LinkList對象和Node對象從定義上是獨立的,互相並不包含對方。這個基本思想很重要,只要大家記住這個基本原則,那么我們就可以開始接着實現鏈表中的其他方法。
圖2
一般來說,一個鏈表中應該包含的基本操作主要有以下幾個:
- 判斷鏈表是否為空 isEmpty()
def isEmpty(self):
'''
判斷head指向的節點是否為None,如果head指向None,說明該鏈表為空
'''
return self.head == None
- 獲取鏈表的長度 size()
獲取鏈表的長度的關鍵是要遍歷這個鏈表,並對節點數進行計數。遍歷鏈表是鏈表操作中會被頻繁使用到的基本操作,像鏈表節點的查詢、刪除等操作都會涉及到鏈表的遍歷。我們之前說過,遍歷鏈表時,我們必須先找到鏈表的head節點,從鏈表的頭部開始不斷地查找每個節點的next節點,最終直到某一個節點的next指向None,就說明遍歷完成了。
def size(self):
current = self.head # 將鏈表的頭節點賦值給current,代表當前節點
count = 0
while current != None:
count += 1
current = current.getNext() # 計數后,不斷把下一個節點的引用賦值給當前節點,這樣我們就能不斷向后面的節點移動
return count
- 向鏈表中增加一個節點 add()
向鏈表中增加一個節點的時候,我們要考慮兩個問題。第一個問題是,新加入的節點該加到哪里?大家可以想一想,鏈表是一個無序結構,其實把新的節點加到哪里對鏈表本身來說是無所謂的。但加入到鏈表的不同位置,對於我們的代碼操作難度是有區別的。因為我們之前定義的鏈表結構始終只保持對第一個節點的引用,所以從這個角度來看,最簡單的方法就是把新的節點加入到鏈表的頭部,使新節點成為鏈表的第一個節點,然后以前的節點依次后移。第二個問題是,加入新節點的時候,要進行哪些操作?實際上要加入一個新的節點,需要兩個步驟。首先需要把之前的head節點賦值給新節點的下一個節點,也就是新節點的next,然后再把新節點賦值給head節點,讓它成為新的head(如圖3所示)。
圖3
def add(self, val):
temp = Node(val)
temp.next = self.head # 將原來的開始節點設置為新開始節點的下一節點
self.head = temp # 將新加入節點設置為現在的第一個節點
必須要注意temp.next = self.head和self.head=temp這兩個語句的先后順序,如果把self.head=temp寫在前面,則會使得head原來指向的下一個節點的信息全部丟失,這並不是我們想要的結果(如圖4所示)。
圖4
- 查找指定節點是否在鏈表中 search()
要實現查找算法,必然也是要遍歷鏈表的,我們可以設置一個布爾變量作為是否查找到目標元素的標志,然后通過遍歷鏈表中的每個元素,判斷該元素的值是否等於要查找的值,如果是,則將布爾值設置為True,最后返回該布爾值即可。代碼如下:
def search(self, item):
current = self.head
found = False
while current != None and not found:
if current.getData() == item:
found = True
else:
current = current.getNext()
return found
- 移除指定節點 remove()
移除指定節點也是在鏈表中一個常見的操作,在移除指定節點時,除了要先遍歷鏈表找到指定元素外,還需要對這個即將被移除的節點做一些處理,以確保剩下的節點能夠正常工作。在我們找到要被移除的節點時,按照之前寫過的遍歷方法我們知道,current應該是指向要被移除的節點。可問題是怎么才能移除掉該節點呢?為了能夠移除節點,我們需要修改上一個節點中的鏈接,以便使其直接指向當前將被移除節點的下一個節點,使沒有任何其他節點指向這個被移除的節點,以達到移除節點的目的。但這里有個問題,就是當我們循環鏈表到當前節點時,沒法回退回去操作當前節點的前一個節點。所以,為了解決這個問題,在遍歷鏈表時,除了需要記錄當前指向的節點current外,還需要設置一個變量來記錄當前節點的上一個節點previous,每次循環時,如果當前節點不是要被移除的節點,那么就將當前節點的值賦值給previous,而將下一個節點的引用賦值給當前節點,以達到向前移動的目的。同時,在找到了將被移除的節點后,我們會把found設置為true,停止遍歷。
另外,在刪除節點時,可能會有三種情況:
(1)被移除的節點就是鏈表中的開始節點,這時previous一定是None值,我們只需要將current.next賦值給head即可。
(2)被移除的節點是鏈表中最后的節點。
(3)被移除的節點是普通節點(即不是第一個也不是最后一個節點)。
其中第(2)(3)種情況並不需要特殊處理,直接設置previous的next為current的next即可。
def remove(self, item):
current = self.head
previous = None
found = False
# 判斷指定值是否存在於鏈表中
if not self.search(item):
return
while not found:
if current.getData() == item:
found = True
else:
previous = current
current = current.getNext()
if previous == None:
self.head = current.getNext()
else:
previous.setNext(current.getNext())
- 獲取鏈表中所有節點的值 getAllData()
獲取鏈表中的所有節點的值方便我們隨時查看鏈表中究竟有哪些值。由於鏈表並不像普通的list一樣可以直接打印出來看,所以一般我們需要借助於遍歷鏈表把鏈表中的每個節點的值取出來放到一個列表中,然后再打印這個列表,從而取得鏈表中所有節點值的目的。
def getAllData(self): # 得到鏈表中所有的值
data = []
current = self.head
while current:
data.append(current.getData())
current = current.getNext()
return data
這些就是我們在鏈表中常用的操作方法及對應的代碼實現,接下來我們可以嘗試來操作並測試一下我們寫的這些方法對不對。
linkList = MySingleLinkList()
for i in range(10, 50, 5):
linkList.add(i)
print(linkList.size()) # output: 8
print(linkList.getAllData()) # output: [45, 40, 35, 30, 25, 20, 15, 10]
linkList.remove(25)
print(linkList.getAllData()) # output: [45, 40, 35, 30, 20, 15, 10]
linkList.search(25) # output: False
linkList.isEmpyt() # output: False
以上就是我們在面試中經常遇到的python算法中的鏈表的定義和常見操作