# 二元搜尋樹概念 [toc] > 二元搜尋樹會有規則,所以在做搜尋上會較簡單, 但是二元樹,很雜亂,所以只能透過地回和一一比對去做 # 二元樹和二元搜尋樹概念 - Not every level must be completely filled 就是不需要必定為completed tree - (最主要跟一般二元樹的差別)**任一個節點的左子樹都比父節點小,右子樹都比父節點大,且每一個節點的值都不重複**。 - 所以當我們要查找資料的時候,就可以從根節點開始,比根節點小的就從左子樹開始找,比較大的就從右子樹開始找。相對於其他資料結構而言,尋找、插入的時間複雜度較低,為O(logN)。 - 所以整個Tree最小的那個node 因該會在最左最下的那個node ,最大的則是最右最下的那個node - In a Binary Search Tree (BST), all keys in left subtree of a key must be smaller and all keys in right subtree must be greater. So a Binary Search Tree by definition has distinct keys and duplicates in binary search tree are not allowed. 記得所有的value不可重複**** # 二元搜尋樹 big o ![](https://i.imgur.com/ejfApqd.png) 搜尋 : 平均時間複雜度 log(n),因為只要往其中一邊走。 新增 : 平均時間複雜度 log(n),因為它要找到適當的插入位置 刪除 : 平均時間複雜度 log(n),因為它要找到適當的移除位置 ## 平衡二元搜尋樹 ( Balanced BST ) BST 平均來看基本操作都有指數級的時間複雜度,但為什麼還是沒選他呢 ? 主要的原因在於,它最壞變成 O(n) 的時間複雜度操作,如下圖 5 所示,它的確也是一個 BST,這樣如果我們要搜尋某個值,仍然是一個一個慢慢找。 ![](https://i.imgur.com/ESiTdE7.png) 圖 5 : 有問題的 BST 因為後來就誕生一種『 平衡二元搜尋樹 』,如下圖 6 所示,事實和上面二元樹相同,只是差在每當插入節點時,會進行平衡。 它的特點如下 : 葉節點的高度差的絕對值不能超過 1。 左右子樹都為平衡二元搜尋樹。 它的最好、最壞、平均的新增、刪除、搜尋的時間複雜度都是 O (logn)。 AVL 樹、紅黑樹都是屬於平衡二元搜尋樹,它們都會自動的進行平衡操作。 ![](https://i.imgur.com/xlQJovV.png) 圖 6 : 平衡二元樹 平衡二元搜尋樹,正常來說,已經是在搜尋中最有效率的資料結構了,但是為什麼它是沒選它呢 ? 因為它是以『 記憶體 』來說,操作最有效率,但是以『 硬碟 』來說則否。 在資料庫的世界,通常資料量都有點兒大,不太可能全部都塞在記憶體中操作,通常會放在硬碟中。 那為什麼平衡二元搜尋樹在硬碟中操作沒有什麼效率呢 ? 因為 I/O 的問題 然後你想想如果一個很深的平衡二元搜尋樹,那如果要找的值是在最小面,以下圖 7 為例,那它就至少要進行 100 次 i/o 操作。 ![](https://i.imgur.com/3kV64gl.png) 圖 7 : 平衡二元樹在節點多時的問題 B 樹 根據上述 i/o 的原因,因此又誕生出叫『 B 樹 』的東東,它想解決的東西為 :+1 :+1: :8ball: 當樹太大就要多次io處理: 是的,當樹的大小超過了系統的內存容量時,可能需要進行多次IO操作才能處理整個樹。這是因為內存容量有限,無法一次性將整個樹加載到內存中進行操作。 外部存儲器算法需要考慮到IO操作的效率和數據讀取的順序,以最大限度地減少IO次數和數據讀取的開銷。常見的技術包括使用緩存(cache)來減少IO操作、按照順序讀取數據以提高讀取效率、以及使用索引或搜索樹結構來加速節點查找等。 -[b tree](/g9s0omRrSmmRQbcRKJQDVw) # 二元搜尋樹搜尋、新增、改值、刪除四種操作 ## **新增節點** 假設我們今天要把值為 15 和 40 的節點新增進去 先做 15 的節點: 1. 與 root 節點 ( 20 ) 比較,15 < 20,左子樹不為空,往左子樹走 2. 與節點 ( 10 ) 比較,10 < 15,右子樹為空,就把 15 填進去 ![](https://i.imgur.com/mzawVH0.png) 相信聰明的你已經知道怎麼新增節點了,試試看把 40 新增進去吧。 沒錯就是這樣,按照上面的邏輯 40 就應該放在 30 的右邊(右子樹)。 ![](https://i.imgur.com/UNsWByK.png) ## **搜尋結點** 假設我們要搜尋 15 這個節點,**其實跟新增的步驟差不多**,先逐一比大小,差在要確認經過的節點的值是不是 15 即可。 1. 與 root 節點 ( 20 ) 比較,15 < 20,往左子樹走 2. 與節點 ( 10 ) 比較,10 < 15,往右子樹走 3. 與節點 ( 15 ) 比較,15 == 15,找到了! ## **修改節點的值** 我上網查發現沒有什麼人講到如果要改值的話怎麼操作最有效率,但最簡單的方法應該是就把該舊值的節點刪除,然後插入更新值的節點。 ## 刪除節點的值(有幾種情況) 1. 第一種情況:在最leaf 的node 直接delete 就好,因為你不需要重新排序他。 ![](https://i.imgur.com/fo0qNpr.jpg) 2. 第二種情況:如果是在父節點上,然後這個父節點只有一個子樹(左子樹or 右子樹)就把樹刪掉,然後讓子樹串上去,指標指上去就好 ![](https://i.imgur.com/lvYyBKL.jpg) 刪掉父節點,補上子節點。 ![](https://i.imgur.com/v38J9tv.jpg) 3. 第三種情況: 如果你要刪除的節點有很多子樹 但如果你是在父節點上,且你有兩層以上的子樹,那你要找的接替者succesor 如下圖,我們要刪除70, 並且補上70的位置,然後再讓85補上 會找到80的位置,因為比你要保持binary search tree的規律,所以要剛好藉在50<x<90之間 如果我們找90的右子樹,那麼他必定比90大,就不合理,那如果我們找90的左子樹,可以知道這個數字必定比90小,但右一定比50大(所以才會第一關跑來90這個子樹,而不是跑去50 ) ![](https://i.imgur.com/FuaqLOH.jpg) 至於要怎麼找出80這個位置: 如下圖,總而言之就是,我們要找到目前要刪除的點的子數中最大的那個樹(整個binary search tree 依照priority 排出順序後,你要刪除的node的前一個順位的那一個node就是你的補貼者)因為比你這個要刪出node更大的,可能會到刪除node的父子樹的右子樹去, 如果你要刪除的是左子樹→找最大的 如果你要刪除的是右子樹→找最小的 ![](https://i.imgur.com/FvG5dAA.jpg) - [BTS_CRUD完整程式碼](/stXJ17WyTX2kKszE7mHDag) # 二元搜尋樹插入概念(補充) ## 插入概念 和搜尋很像,從根節點開始比大小,比較小的往左子樹走,比較大的往右子樹走,一樣的則返回,直到插入為止(要邊排邊插入。 如果單純只是二元樹的插入話,就是插入在complete tree 上最後一個leaf 而已)。因為二元樹的建立沒有大小之分。 ![](https://i.imgur.com/9mkyRqX.jpg) # 二元搜尋樹部分code(插入新的node ) 思路: - 把每個子樹當作一個part ,每個子樹有父子樹以及左右子樹。每次地迴,就是再去下一個子樹part 去看。 - 因為BST的規則,父子樹要大於左子樹,所以我們必須要傳入兩個參數,要加入的點,以及父子樹,因為要做比較 - 然後比較父子樹和要加入的點的大小,如果小就往左邊地回,然後把左子樹看成新的子樹part 如第一步驟,反之如果比較大就往右邊走,然後一樣。 ## 二元搜尋樹部分code(插入新的node )程式碼 ```python= ```python import QueueLinkedList as queue class BSTNode: def __init__(self, data): self.data = data self.leftChild = None self.rightChild = None def insertNode(rootNode, nodeValue): # 必須傳入父節點,和子節點 #---------------先判斷有沒有樹 if rootNode.data == None: # 判斷是否有值 rootNode.data = nodeValue #----------------- elif nodeValue < rootNode.data: # 比對目前的這個節點是否小於你要插入的節點(放到左子樹) if rootNode.leftChild is None: rootNode.leftChild = BSTNode(nodeValue) # 如果左子樹為空,就建立左子樹 else: insertNode(rootNode.leftChild, nodeValue) #遞迴:如果左子樹不為空,就繼續尋找下去 elif nodeValue >rootNode.data: if rootNode.rightChild is None: # 同上的原理 只是變成右邊 rootNode.rightChild = BSTNode(nodeValue) else: insertNode(rootNode.rightChild, nodeValue) else: return "can not allowed duplicated value " return "The node has been successfully inserted ``` ## BST 全部程式碼 ```python= # Created by Elshad Karimov # Copyright © 2020 AppMillers. All rights reserved. import QueueLinkedList as queue class BSTNode: def __init__(self, data): self.data = data self.leftChild = None self.rightChild = None def insertNode(rootNode, nodeValue): # 必須傳入父節點,和子節點 #---------------先判斷有沒有樹 if rootNode.data == None: # 判斷是否有值 rootNode.data = nodeValue #----------------- elif nodeValue <= rootNode.data: # 比對目前的這個節點是否小於你要插入的節點(放到左子樹) if rootNode.leftChild is None: rootNode.leftChild = BSTNode(nodeValue) # 如果左子樹為空,就建立左子樹 else: insertNode(rootNode.leftChild, nodeValue) #遞迴:如果左子樹不為空,就繼續尋找下去 else: if rootNode.rightChild is None: # 同上的原理 只是變成右邊 rootNode.rightChild = BSTNode(nodeValue) else: insertNode(rootNode.rightChild, nodeValue) return "The node has been successfully inserted" def preOrderTraversal(rootNode): # travel 方法,一樣和之前的一般binary tree 一樣 if not rootNode: return print(rootNode.data) preOrderTraversal(rootNode.leftChild) preOrderTraversal(rootNode.rightChild) def inOrderTraversal(rootNode): if not rootNode: return inOrderTraversal(rootNode.leftChild) print(rootNode.data) inOrderTraversal(rootNode.rightChild) def postOrderTraversal(rootNode): if not rootNode: return postOrderTraversal(rootNode.leftChild) postOrderTraversal(rootNode.rightChild) print(rootNode.data) ## level order 的方法 跟binary tree 一模一樣 def levelOrderTraversal(rootNode): if not rootNode: return else: customQueue = queue.Queue() customQueue.enqueue(rootNode) while not(customQueue.isEmpty()): root = customQueue.dequeue() print(root.value.data) if root.value.leftChild is not None: customQueue.enqueue(root.value.leftChild) if root.value.rightChild is not None: customQueue.enqueue(root.value.rightChild) def searchNode(rootNode, nodeValue): if rootNode.data == nodeValue: print("The value is found") elif nodeValue < rootNode.data: # 跟上面的插入很像, 因為你在插入的動作,就需要去做排序比較 ,這個binary search tree的原則 if rootNode.leftChild.data == nodeValue: print("The value is found") else: searchNode(rootNode.leftChild, nodeValue) # 做地迴 else: if rootNode.rightChild.data == nodeValue: print("The value is found") else: searchNode(rootNode.rightChild, nodeValue) def minValueNode(bstNode): # binary search tree 最小值會發生在最左邊且最下面的那個leaf current = bstNode while (current.leftChild is not None): current = current.leftChild return current def deleteNode(rootNode, nodeValue): if rootNode is None: return rootNode if nodeValue < rootNode.data: rootNode.leftChild = deleteNode(rootNode.leftChild, nodeValue) elif nodeValue > rootNode.data: rootNode.rightChild = deleteNode(rootNode.rightChild, nodeValue) else: if rootNode.leftChild is None: temp = rootNode.rightChild rootNode = None return temp if rootNode.rightChild is None: temp = rootNode.leftChild rootNode = None return temp temp = minValueNode(rootNode.rightChild) # 從右子樹去找最小的,固定的,假設我們把我要刪除的那個點,當作根節點來看的話 rootNode.data = temp.data #這個最小的直是我們要替換的對象,所以我們會return 他 rootNode.rightChild = deleteNode(rootNode.rightChild, temp.data)# 這個code是要刪掉我們找到的那個最小的直,所以再一次地迴 return rootNode def deleteBST(rootNode): rootNode.data = None rootNode.leftChild = None rootNode.rightChild = None return "The BST has been successfully deleted" newBST = BSTNode(None) insertNode(newBST, 70) insertNode(newBST,50) insertNode(newBST,90) insertNode(newBST, 30) insertNode(newBST,60) insertNode(newBST,80) insertNode(newBST,100) insertNode(newBST,20) insertNode(newBST,40) print(deleteBST(newBST)) levelOrderTraversal(newBST) ```
{"metaMigratedAt":"2023-06-17T12:00:41.399Z","metaMigratedFrom":"Content","title":"二元搜尋樹概念","breaks":true,"contributors":"[{\"id\":\"2a2f05dd-e6c8-4093-a2ce-98223c62a6b9\",\"add\":8688,\"del\":4}]"}
Expand menu