前言
- 有Python基礎
- 學過數據結構那自然是最好的
原理
雙向鏈表
雙向鏈表和單鏈表的差別在哪?雙向鏈表的節點和單鏈表的節點是不一樣的。單鏈表的節點有item和next,而雙向鏈表的節點還必須有一個指向前一個節點的prior(別的命名也行,反正必須有個東西指向前一個節點)。prior存在的意義也是爲了更好地查找節點的前驅節點。因此必須對Node進行改造。
實現
節點的定義
class Node(object):
# 初始化時它就只是個孤家寡人,前不着村後不着店,prior和next都是空
def __init__(self, item):
self.item = item
self.prior = None
self.next = None
初始化
- 雙向鏈表的構造函數和單鏈表一樣,只需要設置空的頭節點即可。
class DeLink(object):
def __init__(self):
self.__head = None
- 事實上,雙向鏈表比單鏈表的節點多了一個屬性prior,其他的方法照樣可以沿用。
- 參照博客
https://blog.csdn.net/sf9898/article/details/104946291
- 將單鏈表的代碼拷貝下來,在單鏈表的class Node下按照上面說的那樣子改造,加一個prior屬性,其他的別改,直接運行並沒有毛病。其原因在於增加了一個並沒有使用到的prior屬性,運行的實際上還是單鏈表。
- 正如前面講的,prior存在的意義是爲了查找前一個節點,這也是雙向鏈表的優點。那麼就可以有利用prior屬性查找節點的方法。
- 接下來按照雙向鏈表的原理重新構造一下class DeLink
- size 方法不需要講究,利用原來的單鏈表方法即可,畢竟只是求個數。當然,判空和遍歷也可以沿用。
def isEmpty(self):
return self.__head is None
def size(self):
count = 0
cur = self.__head
while cur:
count += 1
cur = cur.next
return count
def travel(self):
cur = self.__head
while cur:
print(cur.item, end=' ')
cur = cur.next
print('')
插入
- 查找,刪除數值等函數亦是可以沿用,加上了prior屬性之後變化的主要是插入和刪除,其他的沿用即可。接下來重點看看插入和刪除。
- 頭部插入
# 頭部插入
def addFront(self, item):
node = Node(item) # 新的節點只有item,next和prior都是None
node.next = self.__head # 新的節點的next指向當前的第一個節點
if self.__head:
self.__head.prior = node # 不爲空時設置當前第一個節點的prior指向新來的node
self.__head = node # 新來的node成爲新的頭結點
- 尾部插入:尾部插入需要將原來的尾部節點last的next指向新的node(原來的last的next是指向None的),node的prior指向last(這一步要有,這樣通過node的prior就可以找到last了)。爲了減少工作量,直接在原有的單鏈表上面改了。
# 尾部插入
def addBack(self, item):
node = Node(item)
if self.__head is None:
self.__head = node
return
cur = self.__head
pre = None
while cur:
pre = cur
cur = cur.next
# 當cur 爲最後一個節點時帶入,pre更新爲最後一個節點,cur更新爲最後一個節點的下一個節點即爲空,
# 下一次while cur 時會退出循環,此時的pre表示的就是最後一個節點,將node掛到pre的後面即可
pre.next = node # 這裏的pre相當於例子中的last
# 僅加了下面這句
node.prior = pre
- 中間插入:需要注意的是要找到插入位置的前一個節點。如圖,找到pre,之後:
- pre.next = node
- node.prior = pre
- node.next = nd
- nd.prior = node
# 插入節點, 節點在插入後是第 pos 個節點,當然這個函數也可以實現頭部插入和尾部插入的功能
# 故pos取值1時,即頭部插入,取值size+1時是尾部插入。因此取值的合法範圍是[1,size + 1]
def insert(self, pos, item):
if pos > (self.size() + 1) or pos < 1:
return
if pos == 1:
self.addFront(item)
return
node = Node(item)
cur = self.__head
pre = None
for i in range(pos - 1):
pre = cur
cur = cur.next
# 1) pre.next = node
# 2) node.prior = pre
# 3) node.next = nd
# 4) nd.prior = node
pre.next = node
node.prior = pre
node.next = cur
if cur:
cur.prior = node
刪除
- 頭部刪除:更新頭結點爲頭結點的下一個節點,原頭結點的next指向None,現頭結點的prior指向None。
# 刪除頭部結點
def removeFront(self):
cur = self.__head
self.__head = self.__head.next
cur.next = None
self.__head.prior = None
- 刪除尾部結點
# 刪除尾部節點
def removeBack(self):
# 空節點時
if self.__head is None:
return
# 只有一個節點
if self.__head and self.__head.next is None:
self.__head = None
return
# 鏈表節點有兩個及以上
cur = self.__head # 當前節點
pre = None # 前一個節點
cn = cur.next # 後一個節點
# 剛開始cur取到的是第一個節點,cn是第二個
while cn:
pre = cur
cur = cur.next
cn = cur.next
# 僅修改此處
pre.next = None
cur.prior = None
- 中間刪除
# 刪除指定數值的節點
def delete(self, item):
if self.__head is None:
return
if self.__head.item == item:
self.__head = None
return
cur = self.__head.next # 取第二個節點
pre = self.__head # 第一個節點
while cur:
if cur.item == item:
# 僅修改此處
pre.next = cur.next
cur.prior = None
cur.next.prior = pre
cur.next = None
break
else:
pre = cur
cur = cur.next
完整代碼
參照之前單鏈表進行測試,下面是完整代碼,如有錯誤之處還望批評指出。
class Node(object):
# 初始化時它就只是個孤家寡人,前不着村後不着店,prior和next都是空
def __init__(self, item):
self.item = item
self.prior = None
self.next = None
class DeLink(object):
def __init__(self):
self.__head = None
def isEmpty(self):
return self.__head is None
# 頭部插入
def addFront(self, item):
node = Node(item) # 新的節點只有item,next和prior都是None
node.next = self.__head # 新的節點的next指向當前的第一個節點
if self.__head:
self.__head.prior = node # 設置當前第一個節點的prior指向新來的node
self.__head = node # 新來的node成爲新的頭結點
# 尾部插入
def addBack(self, item):
node = Node(item)
if self.__head is None:
self.__head = node
return
cur = self.__head
pre = None
while cur:
pre = cur
cur = cur.next
# 當cur 爲最後一個節點時帶入,pre更新爲最後一個節點,cur更新爲最後一個節點的下一個節點即爲空,
# 下一次while cur 時會退出循環,此時的pre表示的就是最後一個節點,將node掛到pre的後面即可
pre.next = node # 這裏的pre相當於例子中的last
node.prior = pre
def size(self):
count = 0
cur = self.__head
while cur:
count += 1
cur = cur.next
return count
def travel(self):
cur = self.__head
while cur:
print(cur.item, end=' ')
cur = cur.next
print('')
# 刪除頭部節點
def removeFront(self):
cur = self.__head
self.__head = self.__head.next
cur.next = None
self.__head.prior = None
# 刪除尾部節點
def removeBack(self):
# 空節點時
if self.__head is None:
return
# 只有一個節點
if self.__head and self.__head.next is None:
self.__head = None
return
# 鏈表節點有兩個及以上
cur = self.__head # 當前節點
pre = None # 前一個節點
cn = cur.next # 後一個節點
# 剛開始cur取到的是第一個節點,cn是第二個
while cn:
pre = cur
cur = cur.next
cn = cur.next
# 僅修改此處
pre.next = None
cur.prior = None
# 查找鏈表中有沒有item,有返回True,沒有則返回False
def search(self, item):
cur = self.__head
res = False
while cur:
if cur.item == item:
res = True
break
else:
cur = cur.next
return res
# 查找某個數的下標
# def searchIndex(self, item):
# if self.search(item):
# # 存在的話才進行
# cur = self.__head
# index = -1
# while cur:
# index += 1
# if cur.item == item:
# break
# else:
# cur = cur.next
# return index
# else:
# return -1
# 刪除指定數值的節點
def delete(self, item):
if self.__head is None:
return
if self.__head.item == item:
self.__head = None
return
cur = self.__head.next # 取第二個節點
pre = self.__head # 第一個節點
while cur:
if cur.item == item:
pre.next = cur.next
cur.prior = None
cur.next.prior = pre
cur.next = None
break
else:
pre = cur
cur = cur.next
# 插入節點, 節點在插入後是第 pos 個節點,當然這個函數也可以實現頭部插入和尾部插入的功能
# 故pos取值1時,即頭部插入,取值size+1時是尾部插入。因此取值的合法範圍是[1,size + 1]
def insert(self, pos, item):
if pos > (self.size() + 1) or pos < 1:
return
if pos == 1:
self.addFront(item)
return
node = Node(item)
cur = self.__head
pre = None
for i in range(pos - 1):
pre = cur
cur = cur.next
# 1) pre.next = node
# 2) node.prior = pre
# 3) node.next = nd
# 4) nd.prior = node
pre.next = node
node.prior = pre
node.next = cur
if cur:
cur.prior = node
ll = DeLink()
print(ll.isEmpty()) # 初始化時爲空,理應輸出True
for i in range(5):
ll.addFront(i)
print('size:', ll.size()) # 5個數
ll.travel() # 4 3 2 1 0
for i in range(5):
ll.addBack(i)
ll.travel() # 4 3 2 1 0 0 1 2 3 4
ll.removeFront()
ll.travel() # 3 2 1 0 0 1 2 3 4
print('----')
ll.removeBack()
ll.travel() # 3 2 1 0 0 1 2 3
ll.insert(9, 12)
ll.travel() # 3 2 1 0 0 1 2 3 12
print(ll.search(13)) # 沒有,False
print(ll.search(1)) # True
ll.delete(2)
ll.travel() # 3 1 0 0 1 2 3 12
print('------')
# print(ll.searchIndex(12))
ll.insert(8, 100)
ll.travel() # 3 1 0 0 1 2 3 100 12
- 結果
可以發現寫起來代碼量會多一點點,事實上,很多東西用單鏈表就能解決了,雙向鏈表比單鏈表多了一個prior,這一點是雙向鏈表的優點,同時也是它的缺點,畢竟多一個元素就多佔一點空間,因此還是單鏈表用的比較多。