連結串列的變形與操作
1 單鏈表的簡單變形
前言: 連結串列的缺點是尾端加入元素的操作效率很低,因為這時只能從表頭開始查詢,直到找到表單最後一個結點,而後才能連結新結點 我們可以改進一下,提高尾端插入元素的效率。 下圖給了一種可行的設計,表物件增加一個尾結點引用域。有個這個域,只需常量時間就能找到尾結點,在表尾加入新元素的操作就可能做到O(1) 初始化和變動操作 我們用類LList1() 繼承前面講的LList(),在LList1的初始化函式中,應該首先初始化LList物件的那些資料域。最合理且方便的方式是self物件呼叫LList類的初始化函式。 現在還要初始化一個尾結點的引用域。由於是作為內部資料域,用_rear作為域名,也初始化為None
def __init__(self):
LList.__init__(self):
self._rear = None
前端插入操作: 操作方式和普通連結串列一樣,但是現在要考慮尾結點的引用域的設定:如果原來是空表,新加入的第一個結點也是最後一個結點。
def prepend(self, elem):
if self._head is None:
self._head = LNode(elem, self._head)
self._rear = self._head
else:
self._head = LNode(elem, self._head)
後端插入操作: 因為增加了尾結點引用域,可以直接找到尾結點。
def append(self.elem):
if self._head is None: # 是空表
self._head = LNode(elem, self._head)
self._head = self._rear
else:
# 給末尾新增一個元素,
# 就是讓原先的最後的結點的指標域,指向新的元素
self._rear.next = LNode(elem)
# 現在的尾結點的引用域是新加入的結點的尾指標域
# 通過上一個結點的指標域的指向可以找到
self._rear = self._rear.next
彈出末元素的操作: 現在刪除了尾結點之後還要更新_rear,由於統一確定了用_head的值判斷表空,刪除最後一個結點使表變空時,不需要給_rear 賦值為None
def pop_last(self):
if self._head is None: # 空表
raise LinkedListUnderflow('in pop_last')
# 用p指向頭指標
p = self._head
if p.next is None: # 只有一個元素
e = p.elem
# 表中只有一個元素,移除後沒了,用self._head指向None,表示刪除連結串列
self._head = None
return e
# 這個判斷條件一直找到倒數第二個元素
while p.next.next is not None:
p = p.next
# 上面的迴圈完時,p 時倒數第二個元素
# 這一步,p.next.elem 表示最後一個元素的資料域
e = p.next.elem
# 倒數第二個元素指向None,與最後一個元素失去連結關係
p.next = None
# 尾指標域指向p, 因為p 現在成了最後一個元素
self._rear = p
return e
迴圈單鏈表
概念: 最後一個結點的next 域不用None,而是指向表的第一個結點,如圖所示。 但是如果仔細考慮的話,發現在連結串列物件裡紀錄表尾結點更合適。這樣可以同時支援O(1)時間的表頭/表尾插入,O(1)時間的表頭刪除, (表尾刪除因為要找到倒數第二個,倒數第三個…所以還是O(n)時間操作) 當然,由於迴圈表裡的結點連結成一個圈,哪個結點算是表頭或表尾,主要是概念問題,從表的內部形態上無法區分。 迴圈單鏈表的操作與普通的單鏈表的差異就在於掃描迴圈的結束控制 這種表物件只需一個數據域_rear,它在邏輯上始終引用這表單尾結點。 前端加入結點: 就是在尾結點和首節點之間加入新的結點,尾結點引用不變 尾端加入結點: 也是在原尾結點之後插入新節點,只是插入後要把它作為新的尾結點,因此需要更新尾結點的引用
class LCList: # 迴圈單鏈表類
def __init__(self):
self._rear = None
def is_empty(self):
return self._rear is None
def prepend(self, elem): # 前端插入
p = LNode(elem)
if self._rear is None: # 是空表
p.next = p # 建立一個結點的環
self._rear = p
else:
# 首端插入,其實是在尾端和首端之間插入
# 所以這裡p.next 指向了第一個結點
p.next = self._rear.next
# 上一個尾結點的指標域指向p
# 現在p 是最後一個結點
self._rear.next = p
def append(self):
self.prepend(elem)
self._rear = self._rear.next
def pop(self): # 前端彈出
if self._rear is None: # 空表
raise LinkedListUderflow('in pop of CLList')
# 用p 指向表中第一個元素
p = self._rear.next
if self._rear is p: # 表中只有一個元素
self._rear = None # 指向空,表示刪除連結串列
else:
self._rear.next = p.next
return p.elem
def printall(self): # 輸入所以元素
if self.is_empty():
return
p = self._rear.next
while True:
print(p.elem)
if p is self._rear:
break
p = p.next