接著來進入圖論的重點之一,Tree與Binary Tree。
二元樹(Binary Tree)是一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點和右子節點。這些子節點可以是實體節點 或 空節點。
二元樹是其他進階樹的基礎,可延伸推廣到Binary Search Tree二元搜索樹、Heap堆、Huffman Tree霍夫曼樹...等等。
二元樹一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點和右子節點。這些子節點可以是實體節點 或 空節點。
一開始,初始化時是一顆空樹,隨著資料的增加而成為一顆擁有實體節點的二元樹。
除了最後一層外,每一層的節點都是滿的,且最後一層的節點從左到右依次排列。
所有內部節點都有兩個子節點,最後一層皆為葉子節點。
1.可以根據需求動態延伸或縮短,有效利用記憶體空間。
1.不支援random acess,不支援BinaryTree[ index ] 這種操作。
2.如果要搜尋或者訪問某個元素,一定要從根節點開始拜訪(或搜尋)整顆樹。
run-time成本相當昂貴。
3.只能單向訪問,相當於單行道,只能從上到下(從根節點到葉子節點)訪問。
class Node:
def __init__(self, data):
# 資料欄位
self.data = data
# 左子節點
self.left = None
# 右子節點
self.right = None
class BinaryTree:
def __init__(self):
# 初始化時是一顆空樹
self.root = None
如果當下節點為空,則放入當下的位置。
如果左子節點為空,則放入左子節點。
如果右子節點為空,則放入右子節點。
如果左子節點、右子節點都已經使用,則往下一層尋找空位。
(補充: Binary Tree並無硬性規定插入方式,讀者可自行定義)
時間複雜度: O(n) 線性時間
def insert(self, data):
if self.root == None:
self.root = Node(data)
else:
self._insert(self.root, data)
return
def _insert(self, cur_node, data):
if cur_node == None:
# 如果當下節點為空,則放入當下的位置。
cur_node = Node(data)
elif cur_node.left == None:
# 如果左子節點為空,則放入左子節點。
cur_node.left = Node(data)
elif cur_node.right == None:
# 如果右子節點為空,則放入右子節點。
cur_node.right = Node(data)
# 如果左子節點、右子節點都已經使用,則往下一層尋找空位。
elif cur_node.left.left is None or cur_node.left.right is None:
self._insert(cur_node.left, data)
elif cur_node.right.left is None or cur_node.right.right is None:
self._insert(cur_node.right, data)
else:
self._insert(cur_node.left, data)
return
從根節點開始往下搜尋,查看某個節點值是否存在。
如果有,返回該節點。
如果沒有,返回None。
時間複雜度: O(n) 線性時間
def search(self, data):
return self._search(self.root, data)
def _search(self, cur_node, data):
if not cur_node:
return None
if cur_node.data == data:
return cur_node
else:
return self._search(cur_node.left, data) or self._search(cur_node.right, data)
從根節點開始往下搜尋,查看某個節點值是否存在。
如果有,刪除該節點,填補空缺,返回被刪除的節點。
如果沒有,返回None。
(補充: Binary Tree並無硬性規定填補空缺的方式,讀者可自行定義)
時間複雜度: O(n) 線性時間
def delete(self, data):
return self._deleteNode(self.root, data)
def _deleteNode(self, cur_node, data):
if not cur_node:
return None
if cur_node.data != data:
return self._deleteNode( cur_node.left, data ) or self._deleteNode( cur_node.right, data )
else:
# 當下就是要被刪除的節點
if (not cur_node.left) or (not cur_node.right):
# 至少有一個子節點是空的
# 由存在的那個子節點 或者 None 填補刪除後的空缺
cur_node = cur_node.left if cur_node.left else cur_node.right
else:
# 兩個子節點都存在
# 由右子樹最左下角的節點 填補刪除後的空缺
replace = cur_node.right
while replace.left:
replace = replace.left
cur_node.data = replace.data
cur_node.right = self._deleteNode( cur_node.right, replace.data )
return Node(data)
從根節點開始往葉子節點拜訪,取得最長路徑上的節點數 = 樹的深度。
時間複雜度: O(log n) 對數時間
def depth(self):
return self._depth(self.root)
def _depth(self, cur_node):
if not cur_node:
return 0
return 1 + max( self._depth(cur_node.left), self._depth(cur_node.right) )
先拜訪左子樹、接著拜訪當下節點、最後拜訪右子樹。
依此定義,遞迴拜訪整顆樹。
時間複雜度: O(n) 線性時間
def inorder(self):
self._inorder(self.root)
return
def _inorder(self, cur_node):
if cur_node:
self._inorder(cur_node.left)
print(str(cur_node.data) + "->", end='')
self._inorder(cur_node.right)
return
從樹根開始,逐層由上往下拜訪每一層,每一層由左往右拜訪每個節點。
時間複雜度: O(n) 線性時間
def levelorder(self):
bfs_q = [self.root] if self.root else []
while bfs_q:
next_level = []
for cur_node in bfs_q:
print(str(cur_node.data) + "->", end='')
if cur_node.left:
next_level.append(cur_node.left)
if cur_node.right:
next_level.append(cur_node.right)
bfs_q = next_level
return
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self):
self.root = None
def insert(self, data):
if self.root == None:
self.root = Node(data)
else:
self._insert(self.root, data)
return
def _insert(self, cur_node, data):
if cur_node == None:
cur_node = Node(data)
elif cur_node.left == None:
cur_node.left = Node(data)
elif cur_node.right == None:
cur_node.right = Node(data)
elif cur_node.left.left is None or cur_node.left.right is None:
self._insert(cur_node.left, data)
elif cur_node.right.left is None or cur_node.right.right is None:
self._insert(cur_node.right, data)
else:
self._insert(cur_node.left, data)
return
def inorder(self):
self._inorder(self.root)
return
def _inorder(self, cur_node):
if cur_node:
self._inorder(cur_node.left)
print(str(cur_node.data) + "->", end='')
self._inorder(cur_node.right)
return
def levelorder(self):
bfs_q = [self.root] if self.root else []
while bfs_q:
next_level = []
for cur_node in bfs_q:
print(str(cur_node.data) + "->", end='')
if cur_node.left:
next_level.append(cur_node.left)
if cur_node.right:
next_level.append(cur_node.right)
bfs_q = next_level
return
def search(self, data):
return self._search(self.root, data)
def _search(self, cur_node, data):
if not cur_node:
return None
if cur_node.data == data:
return cur_node
else:
return self._search(cur_node.left, data) or self._search(cur_node.right, data)
def delete(self, data):
return self._deleteNode(self.root, data)
def _deleteNode(self, cur_node, data):
if not cur_node:
return None
if cur_node.data != data:
return self._deleteNode( cur_node.left, data ) or self._deleteNode( cur_node.right, data )
else:
# 當下就是要被刪除的節點
if (not cur_node.left) or (not cur_node.right):
# 至少有一個子節點是空的
# 由存在的那個子節點 或者 None 填補刪除後的空缺
cur_node = cur_node.left if cur_node.left else cur_node.right
else:
# 兩個子節點都存在
# 由右子樹最左下角的節點 填補刪除後的空缺
replace = cur_node.right
while replace.left:
replace = replace.left
cur_node.data = replace.data
cur_node.right = self._deleteNode( cur_node.right, replace.data )
return Node(data)
def depth(self):
return self._depth(self.root)
def _depth(self, cur_node):
if not cur_node:
return 0
return 1 + max( self._depth(cur_node.left), self._depth(cur_node.right) )
def test():
tree = BinaryTree()
for i in range(1, 11):
print(f"insert {i}")
tree.insert(i)
print("\nSearch 5:")
res = tree.search(5)
if res:
print(res.data)
else:
print("None")
print("\nInorder traversal:")
tree.inorder()
print()
print("\nLevel-order traversal:")
tree.levelorder()
print()
print("\nDelete 8:")
res = tree.delete(8)
if res:
print(res.data)
else:
print("None")
print("\nDelete 11:")
res = tree.delete(11)
if res:
print(res.data)
else:
print("None")
print(f"\nTree depth = {tree.depth()}" )
if __name__ == "__main__":
test()
insert 1
insert 2
insert 3
insert 4
insert 5
insert 6
insert 7
insert 8
insert 9
insert 10
Search 5:
5
Inorder traversal:
8->4->9->2->10->5->1->6->3->7->
Level-order traversal:
1->2->3->4->5->6->7->8->9->10->
Delete 8:
8
Delete 11:
None
Tree depth = 4
其實Tree就是Node的組合與推廣,每個節點最多可以有兩個子節點的樹狀資料結構。
讀者可以透過紙筆追蹤演算法和程式執行邏輯,測試幾個簡單的小範例,
會有更深刻的了解和體會!
一題多解 二元樹的右側視角Binary Tree Right Side View_Leetcode #199_精選75題
🎄圖論應用: 二元樹的後序拜訪 Binary Tree Postorder Traversal_LC #145
圖論應用題 驗證二元樹節點 Validate Binary Tree Nodes_Leetcode #1361
BFS 經典入門題 Binary Tree Level Order Traversal II Leetcode #107
BFS 經典入門題 Binary Tree Level Order Traversal_Leetcode #102
經典圖論面試題 Invert Binary Tree_Leetcode #226
使用DFS 模板 + 基礎圖論題目 Binary Tree Paths Leetcode #257