概念介紹
在計算機科學中,鏈表代表着一種多個數據元素的線性集合。鏈表的順序不由其在內存中的物理位置決定,而是通過每一個元素指向另一個元素來實現。鏈表中,一個實體對象為一個節點(Node),每個節點同時保存其數據(data)和一個引用(reference)指向另一個節點。特別需要說明的是,鏈表這種數據類型必須有一個元素為鏈首元素(空鏈表除外)。
由於沒有物理位置上的先后順序(在內存中隨機存儲),鏈表與其他數據結構相比,隨機讀寫(random access)更低效。而修改或刪除鏈表中的節點卻更高效(修改節點之間的指向)。
鏈表節點實現
首先是實現其節點數據類型,每個節點初始化時,傳入數據屬性(data),而next屬性為指向下一個節點(默認為空)。然后實現每個屬性的get和set方法。
class Node:
def __init__(self, init_data):
self.data = init_data
self.next = None
def get_data(self):
return self.data
def get_next(self):
return self.next
def set_data(self, new_data):
self.data = new_data
def set_next(self, new_next):
self.next = new_next
## 鏈表實現 ## 下面一一介紹鏈表的初始化,打印方法(print),插入,刪除,增加,獲取鏈表長度,檢查是否為空等類方法。記住,鏈表中的每個元素為上面介紹的節點類。特別注意的是,該鏈表所代表的節點是無序的,即每個節點的數據成員沒有按照大小順序排序。下一篇博文將介紹有順序的節點鏈表。 初始化構造方法構造一個空的鏈表,其鏈首節點為空。 ```Python class UnorderedList: def __init__(self): self.head = None ```
__str__方法的實現方式是:將鏈表中的節點從鏈首開始遍歷,每個節點的數據成員(data)append進一個list,最后返回str(list)。 ```Python def __str__(self): print_list = [] current = self.head while current is not None: print_list.append(current.get_data()) current = current.get_next() return str(print_list) ```
查看是否為空時,直接檢查鏈首節點是否為空即可。 ```Python def is_empty(self): return self.head is None ```
size方法同樣是遍歷鏈表,並使用count變量計數。 ```Python def size(self): current = self.head count = 0 while current is not None: count += 1 current = current.get_next() return count ```
以上是基本的查詢大小,初始化,查看是否為空,打印等操作,不涉及到增刪改查。下面來重點介紹鏈表的增刪改查。方法有add(self, item):item為數據元素,可以用來構造Node類實例;remove(self, item):刪除數據成員為item的指定節點;search(self, item):查找數據成員為item的指定節點;append(self, item):將數據成員為item的節點添加到鏈表尾部;index(self, item):類比Python的List對象,給鏈表中的節點標記索引;insert(self, pos, item):在指定位置插入數據成員為item的節點,pos同index方法的返回值;pop(self, index=None):參考List的pop方法,移除指定位置的節點並返回其數據成員。下面依次實現其API方法。
add方法很簡單,構造以item為數據成員的Node實例,並將其指向鏈首節點temp.set_next(self.head),然后將temp節點賦值給鏈首節點。
def add(self, item):
temp = Node(item)
temp.set_next(self.head)
self.head = temp
remove方法的實現就有點復雜,比如有一個鏈表是如下圖所示,我們要找到數據元素為17的節點,並將它刪除,然后將剩下的鏈表重新連接起來。
我們需要依次遍歷每個節點,直到找到數據元素為17的節點,然后將17之前的節點和17之后的節點連接起來。17之后的節點可以用current.get_next()(current為當前節點,即為17所在的節點)方法,但17之前的節點卻沒有方法獲取,因為我們沒有set_previous的方法,且此時鏈表是單向的,從鏈首直到鏈尾。因此,可以用一個變量(previous)保存當前鏈表節點的前一個節點。每次移動當前鏈表時,當前鏈表的上一個鏈表也隨之移動。如下圖。
因此,實現方法如下面的代碼,首先查找指定要移除的節點。若當前節點的上一個節點為None,則當前節點為鏈首節點,移除鏈首節點時,只需將鏈首節點賦值為當前節點的下一個節點,self.head = current.get_next()。
若當前節點的上一個節點(previous)不為空,則直接將previous的下一個節點設置為當前節點的下一個節點,改變previous的指向,則當前節點被移除,previous.set_next(current.get_next())。
def remove(self, item):
current = self.head
previous = None
found = False
while not found:
if current.get_data() == item:
found = True
else:
previous = current
current = current.get_next()
if previous is None:
self.head = current.get_next()
else:
previous.set_next(current.get_next())
search方法很簡單,依次遍歷鏈表各個節點,找到則返回True,否則為False。
def search(self, item):
current = self.head
while current is not None:
if current.get_data() == item:
return True
current = current.get_next()
return False
append方法是將指定數據成員為item的節點添加到鏈表尾部,若鏈表首部為空,則將self.head直接賦值為node;否則依次遍歷各個節點,直至找到當前鏈表的尾部節點,將當前節點的下一個節點賦值為node,current.set_next(node)。
def append(self, item):
node = Node(item)
current = self.head
if current is None:
self.head = node
while current.get_next() is not None:
current = current.get_next()
current.set_next(node)
index方法是參考Python內置List的index,查找指定數據成員為item的節點所在鏈表的位置(僅作為標記使用,不表示內存中存儲的先后關系)。用index變量保存索引值,每次遍歷節點時,若未找到指定節點,則索引值+1,直到找到便返回。
def index(self, item):
index = 0
current = self.head
while current is not None:
if current.get_data() == item:
return index
current = current.get_next()
index += 1
return -1
insert方法同樣參考List,在指定索引值處插入數據成員為item的節點。值得注意的是,若鏈表中有n個節點,則可插入的位置為n+1。同樣是遍歷鏈表,若pos == 0,則在鏈首位置插入節點,node.set_next(self.head), self.head = node實現其功能。若pos != 0,則需要引入當前節點的上一個節點,用變量previous代表(同remove)。實現方式類似remove,不再過多解釋。
def insert(self, pos, item):
node = Node(item)
if pos == 0:
node.set_next(self.head)
self.head = node
else:
current = self.head
previous = None
while self.index(current.get_data()) != pos:
previous = current
current = current.get_next()
if current is None:
break
previous.set_next(node)
node.set_next(current)
pop的實現則更為復雜,index默認不給參數時,pop方法默認移除鏈表尾部節點。其次需要考慮index值為負數的情況(同List中的index效果),以及index out of range的情況。若index超出范圍,則直接拋出異常。首先若index == None,則將index的值賦值為self.size() - 1(代表移除最后一個元素)。若index為負數,則將其轉換為正數代表的索引位置。最后檢查index是否在范圍內,不在就拋出IndexError。
確定完index的有效值后,開始遍歷節點,同樣需要考慮只有鏈首節點的情況。
def pop(self, index=None):
if index is None:
index = self.size() - 1
if index < 0:
index = self.size() - abs(index)
if index < 0 or (index >= self.size()):
raise IndexError
current = self.head
previous = None
while self.index(current.get_data()) != index:
previous = current
current = current.get_next()
item = current.get_data()
if previous is None:
self.head = current.get_next()
else:
previous.set_next(current.get_next())
return item
總結:通過實現鏈表的增刪改查,可以了解基礎數據類型的實現方式,以及其在各個方面的優勢和劣勢,如增刪以及random access等。值得注意的是,雖然鏈表在增刪操作上有優勢,但要查找到指定元素,仍然沒有普通的List快。查找需要依次遍歷各個節點。