🏝用Python來實現 Binary Search Tree 二元搜尋樹

閱讀時間約 20 分鐘

定義


二元搜尋樹(Binary Search Tree,簡稱 BST)是一種特殊的二元樹結構
具有以下特性:

  1. 左子樹:左子樹上所有節點的值均小於該節點的值。
  2. 右子樹:右子樹上所有節點的值均大於該節點的值。
  3. 無重複值每個節點的值都是唯一的

這些特性使得二元搜尋樹在搜尋、插入和刪除操作上都具有較高的效率


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

raw-image

優點

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

2.整顆樹的中序拜訪具有排序性質(左子樹 < 目前節點 < 右子樹)。

3.數據均勻分布時,有較優越的搜尋、插入和刪除效能


缺點

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

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

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


節點Node的Python實作

class Node:

def __init__(self, data):

# 資料欄位
self.data = data

# 左子節點
self.left = None

# 右子節點
self.right = None

Binary Search Tree的class定義 與 建構子

class BinarySearchTree:
def __init__(self):

# 初始化時是一顆空樹
self.root = None

Binary Search Tree常見的操作


1.插入節點 insert(data)


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

如果data比當下節點還小,則前往左子樹尋找空位

如果data比當下節點還大,則前往右子樹尋找空位


時間複雜度: 平均O(log n) 對數時間,最差O(n)線性時間。


raw-image


  def insert(self, key):
if self.root is None:
self.root = Node(key)
else:
self._insert(self.root, key)
return

def _insert(self, root, key):
if key < root.data:
if root.left is None:
root.left = Node(key)
else:
self._insert(root.left, key)
else:
if root.right is None:
root.right = Node(key)
else:
self._insert(root.right, key)
return

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


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


如果當下節點值等於data,代表找到了。

如果data比當下節點還小,則前往左子樹尋找data

如果data比當下節點還大,則前往右子樹尋找data


如果找到了,返回該節點。

如果沒有,返回None。


時間複雜度: 平均O(log n) 對數時間,最差O(n)線性時間。


raw-image


  def search(self, key):
return self._search(self.root, key)

def _search(self, root, key):
if root is None or root.data == key:
return root

if key < root.data:
return self._search(root.left, key)

else:
return self._search(root.right, key)

3.刪除指定的節點 delete( data )


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


如果當下節點值等於data,代表找到了。

如果data比當下節點還小,則前往左子樹尋找data

如果data比當下節點還大,則前往右子樹尋找data


如果有找到,刪除該節點,以右子樹的最小值填補空缺,返回被刪除的節點

(補充: 有些課本會以左子樹的最大值填補空缺,也是可以的。)

如果沒有,返回None。


時間複雜度: 平均O(log n) 對數時間,最差O(n)線性時間。


raw-image


  def delete(self, key):
self.root = self._delete(self.root, key)

def _delete(self, root, key):

if root is None:
return root

if key < root.data:
root.left = self._delete(root.left, key)

elif key > root.data:
root.right = self._delete(root.right, key)

else:
if root.left is None:
return root.right
elif root.right is None:
return root.left
temp = self._minValueNode(root.right)
root.data = temp.data
root.right = self._delete(root.right, temp.data)

return Node(data)

def _minValueNode(self, node):
current = node
while current.left is not None:
current = current.left
return current

4.取得樹的深度 depth()


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


時間複雜度: O(log n) 對數時間

raw-image


  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) 線性時間


raw-image
  def inorder(self):
return self._inorder(self.root, [])

def _inorder(self, root, result):
if root:
self._inorder(root.left, result)
result.append(root.data)
self._inorder(root.right, result)
return result

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


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


時間複雜度: O(n) 線性時間


raw-image


  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 Search Tree實作和程式碼

class Node:
def __init__(self, key):
self.left = None
self.right = None
self.data = key

class BinarySearchTree:
def __init__(self):

# 初始化時是一顆空樹
self.root = None


def insert(self, key):
if self.root is None:
self.root = Node(key)
else:
self._insert(self.root, key)
return


def _insert(self, root, key):
if key < root.data:
if root.left is None:
root.left = Node(key)
else:
self._insert(root.left, key)
else:
if root.right is None:
root.right = Node(key)
else:
self._insert(root.right, key)
return


def search(self, key):
return self._search(self.root, key)


def _search(self, root, key):
if root is None or root.data == key:
return root

if key < root.data:
return self._search(root.left, key)

else:
return self._search(root.right, key)


def inorder(self):
return self._inorder(self.root, [])


def _inorder(self, root, result):
if root:
self._inorder(root.left, result)
result.append(root.data)
self._inorder(root.right, result)
return result


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 delete(self, key):
self.root = self._delete(self.root, key)


def _delete(self, root, key):
if root is None:
return root
if key < root.data:
root.left = self._delete(root.left, key)
elif key > root.data:
root.right = self._delete(root.right, key)
else:
if root.left is None:
return root.right
elif root.right is None:
return root.left
temp = self._minValueNode(root.right)
root.data = temp.data
root.right = self._delete(root.right, temp.data)
return root


def _minValueNode(self, node):
current = node
while current.left is not None:
current = current.left
return current


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():
# Demo:
bst = BinarySearchTree()
bst.insert(50)
bst.insert(30)
bst.insert(20)
bst.insert(40)
bst.insert(70)
bst.insert(60)
bst.insert(80)

print("\nLevel order traversal of the BST" )
bst.levelorder()
print()

print("\nInorder traversal of the BST:", bst.inorder())

node = bst.search(40)
if node:
print("\nNode found with data 40:", node.data)
else:
print("\nNode not found")

bst.delete(20)
print("\nInorder traversal after deleting 20:", bst.inorder())

print(f"\nTree depth = {bst.depth()}" )

if __name__ == "__main__":
test()

測試輸出


Level order traversal of the BST
50->30->70->20->40->60->80->

Inorder traversal of the BST: [20, 30, 40, 50, 60, 70, 80]

Node found with data 40: 40

Inorder traversal after deleting 20: [30, 40, 50, 60, 70, 80]

Tree depth = 3

結語


其實Tree就是Node的組合與推廣,每個節點最多可以有兩個子節點的樹狀資料結構。

而Binary Search Tree就是Binary Tree額外加上依據節點值大小產生方向性的規則


讀者可以透過紙筆追蹤演算法和程式執行邏輯,測試幾個簡單的小範例,

會有更深刻的了解和體會!


Binary Search Tree相關的演算法練習題與詳解


DFS經典應用題 BST最靠近的公共祖先節點 Leetcode #235


經典圖論題 二元搜索樹的區間和 Range Sum of BST_Leetcode #938


📆行程安排 我的行事曆I_My Calendar I_Leetcode #729


資料結構經典 在二元搜尋樹BST中搜索目標值_Leetcode #700_Leetcode精選75題


圖論進階題: 在二元搜索樹BST中刪除節點_Leetcode #450_Leetcode 精選75題


合縱連橫: 從定義出發,理解 二元搜尋樹BST 背後的本質

84會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
發表第一個留言支持創作者!
接著來進入圖論的重點之一,Tree與Binary Tree。 二元樹(Binary Tree)是一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點和右子節點。這些子節點可以是其他節點或空節點(即無子節點)。 二元樹是其他進階樹的基礎,可延伸推廣到Binary Search Tree
今天,我們將用Python list來實現Disjoint Set (併查集,另外也有人稱之為Union-Find)。 Disjoint Set適合用於處理一些子集合的合併和根節點的查找操作。 這種資料結構在圖論中非常有用,特別是在解決連通性相關問題的應用。
在之前的教學中,已經學會了用雙向鏈結串列來實作Stack 堆疊。 今天,要用另一種底層資列結構,python list,來實作Stack 堆疊。 讀者可以從中發現,因為python list的功能和function實作已經很豐富, 所以使用起來,相當直覺,也簡單許多。
在這次的BMI(身體質量指標)計算的續集裡,將學會funciton的基本觀念與實作, 把常用的功能包裝成可重複利用的元件: function。
在之前的教學中,已經學會了Node和Linked List的實作, 用Python實現了單向鏈結串列Singly linked list、雙向鏈結串列Doubly linked list。 今天要承接之前打下的基礎,用雙向鏈結串列來實作Queue(佇列 或稱 隊列)。
在之前的教學中,已經學會了Node和Linked List的實作, 用Python實現了單向鏈結串列Singly linked list、雙向鏈結串列Doubly linked list。 今天要承接之前打下的基礎,用雙向鏈結串列來實作Stack 堆疊。
接著來進入圖論的重點之一,Tree與Binary Tree。 二元樹(Binary Tree)是一種樹狀數據結構,其中每個節點最多有兩個子節點,通常稱為左子節點和右子節點。這些子節點可以是其他節點或空節點(即無子節點)。 二元樹是其他進階樹的基礎,可延伸推廣到Binary Search Tree
今天,我們將用Python list來實現Disjoint Set (併查集,另外也有人稱之為Union-Find)。 Disjoint Set適合用於處理一些子集合的合併和根節點的查找操作。 這種資料結構在圖論中非常有用,特別是在解決連通性相關問題的應用。
在之前的教學中,已經學會了用雙向鏈結串列來實作Stack 堆疊。 今天,要用另一種底層資列結構,python list,來實作Stack 堆疊。 讀者可以從中發現,因為python list的功能和function實作已經很豐富, 所以使用起來,相當直覺,也簡單許多。
在這次的BMI(身體質量指標)計算的續集裡,將學會funciton的基本觀念與實作, 把常用的功能包裝成可重複利用的元件: function。
在之前的教學中,已經學會了Node和Linked List的實作, 用Python實現了單向鏈結串列Singly linked list、雙向鏈結串列Doubly linked list。 今天要承接之前打下的基礎,用雙向鏈結串列來實作Queue(佇列 或稱 隊列)。
在之前的教學中,已經學會了Node和Linked List的實作, 用Python實現了單向鏈結串列Singly linked list、雙向鏈結串列Doubly linked list。 今天要承接之前打下的基礎,用雙向鏈結串列來實作Stack 堆疊。
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
說來慚愧,我一直到疫情期間不能出國的時候,人生才第一次去澎湖。選了暑假去,剛好碰上延期的花火節,碰到馬公機場史上人數最多的一天!關於澎湖景點、美食推薦,網路上的資訊很多,我自己在做功課的時候覺得資訊滿混亂的,本篇就根據我們此行的經驗,為大家解答規劃時的疑惑💡
Thumbnail
距離香港不遠的西貢,藏有奇形怪石的美麗景色。逃離城市的喧囂,置身於大自然間,感受到生活的美好。沿途遊覽不同小島,參觀特色景點,欣賞石頭之美,是一次難忘的環島之旅。
Thumbnail
賣屋買屋我知道,讓你成交沒煩惱! 本週和大家分享我們最常最常碰到的客戶提問之一:「為什麼資料得公開?」 筆者可以細數一些顯而易見的好處,不過有一件事,是無論我們接下來如何改版、如何優化功能都無法再次為客戶提供的頭銜,即是第一批掛保證敢公開的時間戳記。 前陣子一位新北建案的經理和筆者分享,他很擔心 1
Thumbnail
這之後在荒島的每個晚上,每當我想要在腦中探尋、找回更多過往情境的記憶時,就會划起木筏來到這個被卡在海蝕洞的貨櫃屋裡。今晚,我想來點......
Thumbnail
屋內那個未曾消散、令人不適的視線,只是研究單位監控我的方式嗎?我感覺,那視線好像來自更遙遠的地方,一個不同的維度。此時此刻身處於荒島的我,會不會只是個虛構的概念?利用某些影像、聲音、甚至文字捏成人形,被人們透過面前發著幽光的螢幕,不發一語地盯著看呢?
Thumbnail
《Desert Island Discs》是英國BBC Radio 4 的一個廣播節目,自1942年起首播,至今已超過70年。這個節目情境是訪問節目來賓,如果他即將要長居荒島,與世隔絕,請選擇8張黑膠唱片、1本書、與1項奢侈品同行......
Thumbnail
網路謠言滿天飛,用更客觀的科學知識強身健體吧! 方格子健康月,匯集各大領域的健康保健專題,日常防護、皮膚保養、身體排毒、心理療癒——即刻啟動閱讀,讓你從裡到外補充滿滿能量,照顧好自己的身心靈!4/30前訂閱或購買健康月精選專題,輸入折扣碼health2020,精選專題全面8折起。
Thumbnail
方格子無廣告簡潔的介面非常適合用關燈模式進行閱讀或創作 身為一個宅宅工程師,螢幕打開應該就是要黑黑 der 才算是稱職(?) 方格子的介面因為沒有廣告也夠簡單,因此特別適合用關燈模式進行創作或是閱讀,在 Chrome 上面有許多外掛可以使用,新版的 Chrome 也可以選擇黑暗模式
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
說來慚愧,我一直到疫情期間不能出國的時候,人生才第一次去澎湖。選了暑假去,剛好碰上延期的花火節,碰到馬公機場史上人數最多的一天!關於澎湖景點、美食推薦,網路上的資訊很多,我自己在做功課的時候覺得資訊滿混亂的,本篇就根據我們此行的經驗,為大家解答規劃時的疑惑💡
Thumbnail
距離香港不遠的西貢,藏有奇形怪石的美麗景色。逃離城市的喧囂,置身於大自然間,感受到生活的美好。沿途遊覽不同小島,參觀特色景點,欣賞石頭之美,是一次難忘的環島之旅。
Thumbnail
賣屋買屋我知道,讓你成交沒煩惱! 本週和大家分享我們最常最常碰到的客戶提問之一:「為什麼資料得公開?」 筆者可以細數一些顯而易見的好處,不過有一件事,是無論我們接下來如何改版、如何優化功能都無法再次為客戶提供的頭銜,即是第一批掛保證敢公開的時間戳記。 前陣子一位新北建案的經理和筆者分享,他很擔心 1
Thumbnail
這之後在荒島的每個晚上,每當我想要在腦中探尋、找回更多過往情境的記憶時,就會划起木筏來到這個被卡在海蝕洞的貨櫃屋裡。今晚,我想來點......
Thumbnail
屋內那個未曾消散、令人不適的視線,只是研究單位監控我的方式嗎?我感覺,那視線好像來自更遙遠的地方,一個不同的維度。此時此刻身處於荒島的我,會不會只是個虛構的概念?利用某些影像、聲音、甚至文字捏成人形,被人們透過面前發著幽光的螢幕,不發一語地盯著看呢?
Thumbnail
《Desert Island Discs》是英國BBC Radio 4 的一個廣播節目,自1942年起首播,至今已超過70年。這個節目情境是訪問節目來賓,如果他即將要長居荒島,與世隔絕,請選擇8張黑膠唱片、1本書、與1項奢侈品同行......
Thumbnail
網路謠言滿天飛,用更客觀的科學知識強身健體吧! 方格子健康月,匯集各大領域的健康保健專題,日常防護、皮膚保養、身體排毒、心理療癒——即刻啟動閱讀,讓你從裡到外補充滿滿能量,照顧好自己的身心靈!4/30前訂閱或購買健康月精選專題,輸入折扣碼health2020,精選專題全面8折起。
Thumbnail
方格子無廣告簡潔的介面非常適合用關燈模式進行閱讀或創作 身為一個宅宅工程師,螢幕打開應該就是要黑黑 der 才算是稱職(?) 方格子的介面因為沒有廣告也夠簡單,因此特別適合用關燈模式進行創作或是閱讀,在 Chrome 上面有許多外掛可以使用,新版的 Chrome 也可以選擇黑暗模式