用Python實現二叉樹的增、刪、查
日拱一卒,每週為你帶點 Python 乾貨。
學過資料結構的同學一定這種資料結構非常熟悉了,樹是一種非常高效的非線性儲存結構,學好樹對理解一些複雜的演算法非常有幫助。樹有以下內容需要掌握:
- 樹、二叉樹
- 二叉查詢樹
- 平衡二叉查詢樹、紅黑樹
- 遞迴樹
作為一名 Python 程式設計師,如果把基礎的資料結構與演算法都自己親自實現一遍,那麼你已經比 90% 的 Python 程式設計師更優秀了。
今天的我們的目標是使用 Python 來實現一棵二叉樹。 二叉查詢樹、平衡二叉查詢樹、紅黑樹、遞迴樹後面也會實現,請保持關注。
先來梳理下概念:樹,可以很形象的理解,有根,有葉子,對應在資料結構中就是根節點、葉子節點,同一層的葉子叫兄弟節點,鄰近不同層的叫父子節點,非常好理解。
二叉樹,就是每個節點都至多有二個子節點的樹。
滿二叉樹,就是除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹叫做滿二叉樹。
完全二叉樹,就是葉子節點都在最底下兩層,最後一層葉子節都靠左排列,並且除了最後一層,其他層的節點個數都要達到最大,這種二叉樹叫做完全二叉樹。
二叉樹即可以使用鏈式儲存,也可以使用陣列來儲存,而完全二叉樹是使用資料存最省記憶體的一種結構。
接下來我們使用 Python 實現鏈式儲存的二叉樹。
思路:
1、先定義一個節點 node 類,儲存資料 item 和左右節點指標
2、再實現二叉樹 binary_tree 的類,類應至少有以下屬性和方法:
屬性:有一個根節點(root) , 它是 node 類。
方法:
- 插入一個元素(逐層向下插入)。
- 刪除一個元素(按照二叉查詢樹的方式刪除,先查詢待刪除節點的父節點。 如果父節點不為空, 判斷 item 的左右子樹,如果左子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的右子樹,反之將父節點的右指標指向 item 的右子樹,如果右子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的左子樹,反之將父節點的右指標指向 item 的左子樹,如果左右子樹均不為空,尋找右子樹中的最左葉子節點 x ,將 x 替代要刪除的節點。 ,如果父節點為空,說明不存在該元素 ,刪除元素,返回 True ,未刪除元素, 返回 False。
- 獲取一個元素的父節點。
- 層序遍歷:按層輸出二叉樹的元素
- 中序遍歷:先左子樹,再根節點,最後右子樹
- 前序遍歷:先根節點,再左子樹,最後右子樹
- 後序遍歷:先左子樹,再右子樹,最後根節點。
按下來就是程式設計實現了,請動手自己實現一把。
先實現一個 node 類:
class Node(object):
def __init__(self, item):
self.item = item
self.left = None
self.right = None
def __str__(self):
return str(self.item)
這裡的 __str__ 方法是為了方便列印。在 print 一個 Node 類時會列印 __str__ 的返回值,例如:print(Node(5)) 就會列印出字串 5 。
實現一個二叉樹類:
class Tree(object):
def __init__(self):
# 根節點定義為 root 永不刪除,做為哨兵使用。
self.root = Node('root')
def add(self, item):
node = Node(item)
if self.root is None:
self.root = node
else:
q = [self.root]
while True:
pop_node = q.pop(0)
if pop_node.left is None:
pop_node.left = node
return
elif pop_node.right is None:
pop_node.right = node
return
else:
q.append(pop_node.left)
q.append(pop_node.right)
def get_parent(self, item):
'''
找到 Item 的父節點
'''
if self.root.item == item:
return None # 根節點沒有父節點
tmp = [self.root]
while tmp:
pop_node = tmp.pop(0)
if pop_node.left and pop_node.left.item == item:
return pop_node
if pop_node.right and pop_node.right.item == item:
return pop_node
if pop_node.left is not None:
tmp.append(pop_node.left)
if pop_node.right is not None:
tmp.append(pop_node.right)
return None
def delete(self, item):
'''
從二叉樹中刪除一個元素
先獲取 待刪除節點 item 的父節點
如果父節點不為空,
判斷 item 的左右子樹
如果左子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的右子樹,反之將父節點的右指標指向 item 的右子樹
如果右子樹為空,那麼判斷 item 是父節點的左孩子,還是右孩子,如果是左孩子,將父節點的左指標指向 item 的左子樹,反之將父節點的右指標指向 item 的左子樹
如果左右子樹均不為空,尋找右子樹中的最左葉子節點 x ,將 x 替代要刪除的節點。
刪除成功,返回 True
刪除失敗, 返回 False
'''
if self.root is None: # 如果根為空,就什麼也不做
return False
parent = self.get_parent(item)
if parent:
del_node = parent.left if parent.left.item == item else parent.right # 待刪除節點
if del_node.left is None:
if parent.left.item == item:
parent.left = del_node.right
else:
parent.right = del_node.right
del del_node
return True
elif del_node.right is None:
if parent.left.item == item:
parent.left = del_node.left
else:
parent.right = del_node.left
del del_node
return True
else: # 左右子樹都不為空
tmp_pre = del_node
tmp_next = del_node.right
if tmp_next.left is None:
# 替代
tmp_pre.right = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
else:
while tmp_next.left: # 讓tmp指向右子樹的最後一個葉子
tmp_pre = tmp_next
tmp_next = tmp_next.left
# 替代
tmp_pre.left = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
if parent.left.item == item:
parent.left = tmp_next
else:
parent.right = tmp_next
del del_node
return True
else:
return False
def traverse(self): # 層次遍歷
if self.root is None:
return None
q = [self.root]
res = [self.root.item]
while q != []:
pop_node = q.pop(0)
if pop_node.left is not None:
q.append(pop_node.left)
res.append(pop_node.left.item)
if pop_node.right is not None:
q.append(pop_node.right)
res.append(pop_node.right.item)
return res
def preorder(self, root): # 先序遍歷
if root is None:
return []
result = [root.item]
left_item = self.preorder(root.left)
right_item = self.preorder(root.right)
return result + left_item + right_item
def inorder(self, root): # 中序序遍歷
if root is None:
return []
result = [root.item]
left_item = self.inorder(root.left)
right_item = self.inorder(root.right)
return left_item + result + right_item
def postorder(self, root): # 後序遍歷
if root is None:
return []
result = [root.item]
left_item = self.postorder(root.left)
right_item = self.postorder(root.right)
return left_item + right_item + result
執行測試:
if __name__ == '__main__':
t = Tree()
for i in range(10):
t.add(i)
print('層序遍歷:', t.traverse())
print('先序遍歷:', t.preorder(t.root))
print('中序遍歷:', t.inorder(t.root))
print('後序遍歷:', t.postorder(t.root))
for i in range(10):
print(i, " 的父親", t.get_parent(i))
for i in range(0, 15, 3):
print(f"刪除 {i}", '成功' if t.delete(i) else '失敗')
print('層序遍歷:', t.traverse())
print('先序遍歷:', t.preorder(t.root))
print('中序遍歷:', t.inorder(t.root))
print('後序遍歷:', t.postorder(t.root))
注意這裡使用了根節點 root 作為哨兵,永不刪除,簡化了 delete 函式的實現,這樣也最不容易出錯,否則還要判斷待刪除的節點是否是根節點。
執行結果如下:
層序遍歷: ['root', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
先序遍歷: ['root', 0, 2, 6, 7, 3, 8, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 0, 8, 3, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 8, 9, 3, 0, 4, 5, 1, 'root']
0 的父親 root
1 的父親 root
2 的父親 0
3 的父親 0
4 的父親 1
5 的父親 1
6 的父親 2
7 的父親 2
8 的父親 3
9 的父親 3
刪除 0 成功
層序遍歷: ['root', 8, 1, 2, 3, 4, 5, 6, 7, 9]
先序遍歷: ['root', 8, 2, 6, 7, 3, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 8, 3, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 9, 3, 8, 4, 5, 1, 'root']
刪除 3 成功
層序遍歷: ['root', 8, 1, 2, 9, 4, 5, 6, 7]
先序遍歷: ['root', 8, 2, 6, 7, 9, 1, 4, 5]
中序遍歷: [6, 2, 7, 8, 9, 'root', 4, 1, 5]
後序遍歷: [6, 7, 2, 9, 8, 4, 5, 1, 'root']
刪除 6 成功
層序遍歷: ['root', 8, 1, 2, 9, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 9, 1, 4, 5]
中序遍歷: [2, 7, 8, 9, 'root', 4, 1, 5]
後序遍歷: [7, 2, 9, 8, 4, 5, 1, 'root']
刪除 9 成功
層序遍歷: ['root', 8, 1, 2, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 1, 4, 5]
中序遍歷: [2, 7, 8, 'root', 4, 1, 5]
後序遍歷: [7, 2, 8, 4, 5, 1, 'root']
刪除 12 失敗
層序遍歷: ['root', 8, 1, 2, 4, 5, 7]
先序遍歷: ['root', 8, 2, 7, 1, 4, 5]
中序遍歷: [2, 7, 8, 'root', 4, 1, 5]
後序遍歷: [7, 2, 8, 4, 5, 1, 'root']
相關文章
- Python實現二叉樹的增、刪、查Python二叉樹
- 運用layui實現增刪改查UI
- 實現二叉搜尋樹的新增,查詢和刪除(JAVA)Java
- 二叉查詢樹的插入刪除查詢
- 二叉查詢樹的實現——C++C++
- 關於ToDolist 的增刪改查 用jQuery來實現jQuery
- 用jsp實現資料庫的增刪改查JS資料庫
- js實現表格的增刪改查JS
- 二叉查詢樹概念及實現
- C#實現二叉查詢樹C#
- 二叉搜尋樹的python實現Python
- Java實現二叉搜尋樹的插入、刪除Java
- python 連線mongodb實現增刪改查例項PythonMongoDB
- Go實現對MySQL的增刪改查GoMySql
- FMDB | 實現資料的增刪改查
- 二叉排序樹查詢,插入,刪除排序
- python實現非平衡二叉樹Python二叉樹
- JavaScript實現簡單二叉查詢樹JavaScript
- Java實現簡單的增刪改查操作Java
- jQuery實現購物車的增刪改查jQuery
- 第 34 題:如何實現二叉查詢樹?
- 二叉樹實現二叉樹
- 二叉樹的插入和搜尋–python實現二叉樹Python
- Python Web實戰:Python+Django+MySQL實現基於Web版的增刪改查PythonWebDjangoMySql
- 二叉排序樹的實現排序
- 單連結串列實現增刪改查
- 封裝模組實現商品增刪改查封裝
- 看Zepto如何實現增刪改查DOM
- 二叉查詢樹(查詢、插入、刪除)——C語言C語言
- 刷題系列 - Python用遞迴實現求二叉樹深度Python遞迴二叉樹
- 二叉樹 & 二叉查詢樹二叉樹
- mybatis實現MySQL資料庫的增刪改查MyBatisMySql資料庫
- 霍夫曼樹(最優二叉樹)的實現二叉樹
- python資料結構之二叉樹的實現Python資料結構二叉樹
- python實現二叉樹和它的七種遍歷Python二叉樹
- 二叉樹java實現二叉樹Java
- 二叉查詢樹(二叉排序樹)排序
- Node.js+Express+Mysql 實現增刪改查Node.jsExpressMySql