🏝用Python來實現 Binary Tree 二元樹

更新於 發佈於 閱讀時間約 1 分鐘

接著來進入圖論的重點之一,Tree與Binary Tree。

二元樹(Binary Tree)是一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點右子節點。這些子節點可以是實體節點 或 空節點。

二元樹是其他進階樹的基礎,可延伸推廣到Binary Search Tree二元搜索樹、Heap堆、Huffman Tree霍夫曼樹...等等。


定義


二元樹一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點右子節點。這些子節點可以是實體節點 或 空節點。


  1. 根節點(Root Node):樹的頂部節點
  2. 葉節點(Leaf Node)沒有子節點的節點。
  3. 內部節點(Internal Node)至少有一個子節點的節點。
  4. 深度(Depth):從根節點到葉節點的最長路徑上的節點數


一開始,初始化時是一顆空樹,隨著資料的增加而成為一顆擁有實體節點的二元樹。

raw-image


兩種特殊形式

  1. Complete Binary Tree

除了最後一層外,每一層的節點都是滿的,且最後一層的節點從左到右依次排列


  1. Full Binary Tree

所有內部節點都有兩個子節點,最後一層皆為葉子節點。


優點

1.可以根據需求動態延伸或縮短,有效利用記憶體空間

缺點

1.不支援random acess,不支援BinaryTree[ index ] 這種操作。

2.如果要搜尋或者訪問某個元素,一定要從根節點開始拜訪(或搜尋)整顆樹。

run-time成本相當昂貴。

3.只能單向訪問,相當於單行道,只能從上到下(從根節點到葉子節點)訪問。


節點Node的Python實作

class Node:

def __init__(self, data):

# 資料欄位
self.data = data

# 左子節點
self.left = None

# 右子節點
self.right = None

Binary Tree的class定義 與 建構子(初始化的函式)

class BinaryTree:

def __init__(self):
# 初始化時是一顆空樹
self.root = None

Binary Tree常見的操作

1.插入節點 insert(data)


如果當下節點為空,則放入當下的位置。

如果左子節點為空,則放入左子節點。

如果右子節點為空,則放入右子節點。

如果左子節點、右子節點都已經使用,則往下一層尋找空位


(補充: 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

2.搜尋某個節點值 search(data)


從根節點開始往下搜尋查看某個節點值是否存在

如果有,返回該節點。

如果沒有,返回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)

3.刪除指定的節點 delete( 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)

4.取得樹的深度 depth()


從根節點開始往葉子節點拜訪,取得最長路徑上的節點數 = 樹的深度。


時間複雜度: 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) )

5.樹的中序拜訪 inorder traversal


先拜訪左子樹、接著拜訪當下節點、最後拜訪右子樹。

依此定義,遞迴拜訪整顆樹。


時間複雜度: 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

6.樹的逐層拜訪 level-order traversal


從樹根開始,逐層由上往下拜訪每一層,每一層由左往右拜訪每個節點。


時間複雜度: 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

測試範例

raw-image


完整的Binary Tree實作和程式碼

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相關的演算法練習題與詳解


一題多解 二元樹的右側視角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


一題多解 二元樹的最大深度 Maximum Depth of Binary Tree_Leetcode 104精選75

留言
avatar-img
留言分享你的想法!
avatar-img
小松鼠的演算法樂園
96會員
427內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
2024/10/10
從Python 內建deque資料結構的角度切入, 同時了解deque 與 FIFO Queue相關的function用法。 collections.deque是一種兩端點皆可進出的雙端佇列 在兩端點高效地在O(1)常數時間內添加和刪除元素。 這使得deque非常適合實現FIFO Queue
Thumbnail
2024/10/10
從Python 內建deque資料結構的角度切入, 同時了解deque 與 FIFO Queue相關的function用法。 collections.deque是一種兩端點皆可進出的雙端佇列 在兩端點高效地在O(1)常數時間內添加和刪除元素。 這使得deque非常適合實現FIFO Queue
Thumbnail
2024/09/27
井字遊戲(OOXX)的遊戲描述 Tic Tac Toe(井字遊戲)是經典的雙人棋盤遊戲,在一個3x3的方格中進行。 每回合兩個玩家輪流選一個位置,先讓自己的符號(是 X 或 O)在 水平線、垂直線或對角線上連成一線的玩家宣告獲勝。
Thumbnail
2024/09/27
井字遊戲(OOXX)的遊戲描述 Tic Tac Toe(井字遊戲)是經典的雙人棋盤遊戲,在一個3x3的方格中進行。 每回合兩個玩家輪流選一個位置,先讓自己的符號(是 X 或 O)在 水平線、垂直線或對角線上連成一線的玩家宣告獲勝。
Thumbnail
2024/09/23
深入探討圖(Graph)的基本概念 及 最短路徑Shortest Path的尋找。 我們專注於廣度優先搜尋(BFS)演算法,以等權圖的最短路徑為例, 詳細說明如何利用BFS從起點擴散到終點,並且提供詳細的程式碼範例。 透過實作,讀者能夠更清楚理解圖論及BFS的應用,並體會水波紋擴散模型的重要性。
Thumbnail
2024/09/23
深入探討圖(Graph)的基本概念 及 最短路徑Shortest Path的尋找。 我們專注於廣度優先搜尋(BFS)演算法,以等權圖的最短路徑為例, 詳細說明如何利用BFS從起點擴散到終點,並且提供詳細的程式碼範例。 透過實作,讀者能夠更清楚理解圖論及BFS的應用,並體會水波紋擴散模型的重要性。
Thumbnail
看更多
你可能也想看
Thumbnail
這篇文章,會帶著大家複習以前學過的二元搜尋樹(Binary Search Tree)框架, 並且以二分搜尋樹的概念與定義為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 二元搜尋樹(Binary Search Tree)的定義
Thumbnail
這篇文章,會帶著大家複習以前學過的二元搜尋樹(Binary Search Tree)框架, 並且以二分搜尋樹的概念與定義為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 二元搜尋樹(Binary Search Tree)的定義
Thumbnail
題目敘述 題目會給我們一棵BST二元搜索樹的根結點root,還有一個指定的目標值key。 要求我們在樹中刪除帶有這個key值的節點,並且返回更新過後二元搜索樹的樹根root。 題目的原文敘述 測試範例 Example 1: Input: root = [5,3,6,2,4,null,
Thumbnail
題目敘述 題目會給我們一棵BST二元搜索樹的根結點root,還有一個指定的目標值key。 要求我們在樹中刪除帶有這個key值的節點,並且返回更新過後二元搜索樹的樹根root。 題目的原文敘述 測試範例 Example 1: Input: root = [5,3,6,2,4,null,
Thumbnail
題目敘述 題目會給定一顆二元樹的根結點Root node,和這棵樹中的任意兩個節點p, q。 請找出p, q 在這棵二元數裡面的最接近公共祖先節點是誰? 最接近的公共節點: 就是p, q兩個節點從下往上走,第一個交會的節點。 題目的原文敘述 測試範例 Example 1: Inpu
Thumbnail
題目敘述 題目會給定一顆二元樹的根結點Root node,和這棵樹中的任意兩個節點p, q。 請找出p, q 在這棵二元數裡面的最接近公共祖先節點是誰? 最接近的公共節點: 就是p, q兩個節點從下往上走,第一個交會的節點。 題目的原文敘述 測試範例 Example 1: Inpu
Thumbnail
題目敘述 題目會給定一個二元樹的樹根結點Root node,要求我們計算這顆二元樹的最大深度是多少? 二元樹的深度的定義: 從根結點到葉子結點的最大路徑長度。 題目的原文敘述 約束條件 Constraints: The number of nodes in the tree is
Thumbnail
題目敘述 題目會給定一個二元樹的樹根結點Root node,要求我們計算這顆二元樹的最大深度是多少? 二元樹的深度的定義: 從根結點到葉子結點的最大路徑長度。 題目的原文敘述 約束條件 Constraints: The number of nodes in the tree is
Thumbnail
題目敘述 題目會給我們一顆二元樹的根結點,請我們列出每一層最右邊的節點值,以陣列的形式返回答案。 題目的原文敘述 測試範例 Example 1: Input: root = [1,2,3,null,5,null,4] Output: [1,3,4] 每一層最右邊的節點值分別是1, 3,
Thumbnail
題目敘述 題目會給我們一顆二元樹的根結點,請我們列出每一層最右邊的節點值,以陣列的形式返回答案。 題目的原文敘述 測試範例 Example 1: Input: root = [1,2,3,null,5,null,4] Output: [1,3,4] 每一層最右邊的節點值分別是1, 3,
Thumbnail
題目會給我們節點總數目n、左子樹的關係陣列、右子樹的關係陣列,要求我們驗證在給定的條件下,能不能構成一顆合法的二元樹。
Thumbnail
題目會給我們節點總數目n、左子樹的關係陣列、右子樹的關係陣列,要求我們驗證在給定的條件下,能不能構成一顆合法的二元樹。
Thumbnail
題目會給定給我們一顆二元樹的根結點, 要求我們輸出上下顛倒的Level-order traversal的拜訪結果。
Thumbnail
題目會給定給我們一顆二元樹的根結點, 要求我們輸出上下顛倒的Level-order traversal的拜訪結果。
Thumbnail
題目會給我們一顆二元樹的根節點, 要求我們對齊根節點正中央的虛擬分割線,反轉整顆二元樹。
Thumbnail
題目會給我們一顆二元樹的根節點, 要求我們對齊根節點正中央的虛擬分割線,反轉整顆二元樹。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News