Python實(shí)現(xiàn)二叉搜索樹(shù)增刪改查
引文
二叉搜索樹(shù)(Binary Search Tree,BST)是一種特殊的二叉樹(shù),它滿足以下性質(zhì):
- 每個(gè)節(jié)點(diǎn)的值都大于其左子樹(shù)中的任何值,小于其右子樹(shù)中的任何值。
- 每個(gè)子樹(shù)也是一個(gè)二叉搜索樹(shù)。
二叉搜索樹(shù)有很多優(yōu)點(diǎn),例如:
- 它可以快速地查找、插入和刪除元素。
- 它可以用來(lái)實(shí)現(xiàn)排序、范圍查詢(xún)、最大值和最小值等操作。
- 它可以用來(lái)構(gòu)建平衡樹(shù)、紅黑樹(shù)等高效的數(shù)據(jù)結(jié)構(gòu)。
在本文中,我將介紹如何用Python語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的二叉搜索樹(shù),并展示它的基本操作和應(yīng)用。
一、定義節(jié)點(diǎn)類(lèi)
要實(shí)現(xiàn)一個(gè)二叉搜索樹(shù),我們首先需要定義一個(gè)節(jié)點(diǎn)類(lèi),用來(lái)表示樹(shù)中的每個(gè)元素。一個(gè)節(jié)點(diǎn)類(lèi)包含以下屬性:
- value:節(jié)點(diǎn)的值,可以是任意類(lèi)型。
- left:節(jié)點(diǎn)的左子節(jié)點(diǎn),如果沒(méi)有則為None。
- right:節(jié)點(diǎn)的右子節(jié)點(diǎn),如果沒(méi)有則為None。
我們可以用以下代碼定義一個(gè)節(jié)點(diǎn)類(lèi):
class Node: def __init__(self, value): self.value = value self.left = None self.right = None
二、定義二叉搜索樹(shù)類(lèi)
接下來(lái),我們需要定義一個(gè)二叉搜索樹(shù)類(lèi),用來(lái)表示整個(gè)樹(shù)結(jié)構(gòu)。一個(gè)二叉搜索樹(shù)類(lèi)包含以下屬性:
- root:樹(shù)的根節(jié)點(diǎn),如果為空則為None。
我們可以用以下代碼定義一個(gè)二叉搜索樹(shù)類(lèi):
class BST: def __init__(self): self.root = None
三、實(shí)現(xiàn)插入操作
要向二叉搜索樹(shù)中插入一個(gè)元素,我們需要遵循以下步驟:
- 如果樹(shù)為空,則創(chuàng)建一個(gè)新節(jié)點(diǎn)作為根節(jié)點(diǎn),并返回。
- 如果樹(shù)不為空,則從根節(jié)點(diǎn)開(kāi)始,比較要插入的值和當(dāng)前節(jié)點(diǎn)的值。
- 如果要插入的值小于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的左子樹(shù)中插入。
- 如果要插入的值大于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的右子樹(shù)中插入。
- 如果要插入的值等于當(dāng)前節(jié)點(diǎn)的值,則不做任何操作,并返回。
我們可以用以下代碼實(shí)現(xiàn)插入操作:
def insert(self, value): # 創(chuàng)建一個(gè)新節(jié)點(diǎn) new_node = Node(value) # 如果樹(shù)為空,則將新節(jié)點(diǎn)設(shè)為根節(jié)點(diǎn) if self.root is None: self.root = new_node return # 否則從根節(jié)點(diǎn)開(kāi)始遍歷 current = self.root while True: # 如果要插入的值小于當(dāng)前節(jié)點(diǎn)的值,則向左走 if value < current.value: # 如果左子節(jié)點(diǎn)為空,則將新節(jié)點(diǎn)設(shè)為左子節(jié)點(diǎn) if current.left is None: current.left = new_node return # 否則繼續(xù)向左走 else: current = current.left # 如果要插入的值大于當(dāng)前節(jié)點(diǎn)的值,則向右走 elif value > current.value: # 如果右子節(jié)點(diǎn)為空,則將新節(jié)點(diǎn)設(shè)為右子節(jié)點(diǎn) if current.right is None: current.right = new_node return # 否則繼續(xù)向右走 else: current = current.right # 如果要插入的值等于當(dāng)前節(jié)點(diǎn)的值,則不做任何操作,并返回 else: return
四、實(shí)現(xiàn)查找操作
要在二叉搜索樹(shù)中查找一個(gè)元素,我們需要遵循以下步驟:
- 如果樹(shù)為空,則返回False。
- 如果樹(shù)不為空,則從根節(jié)點(diǎn)開(kāi)始,比較要查找的值和當(dāng)前節(jié)點(diǎn)的值。
- 如果要查找的值小于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的左子樹(shù)中查找。
- 如果要查找的值大于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的右子樹(shù)中查找。
- 如果要查找的值等于當(dāng)前節(jié)點(diǎn)的值,則返回True。
我們可以用以下代碼實(shí)現(xiàn)查找操作:
def search(self, value): # 如果樹(shù)為空,則返回False if self.root is None: return False # 否則從根節(jié)點(diǎn)開(kāi)始遍歷 current = self.root while current is not None: # 如果要查找的值小于當(dāng)前節(jié)點(diǎn)的值,則向左走 if value < current.value: current = current.left # 如果要查找的值大于當(dāng)前節(jié)點(diǎn)的值,則向右走 elif value > current.value: current = current.right # 如果要查找的值等于當(dāng)前節(jié)點(diǎn)的值,則返回True else: return True # 如果遍歷完畢還沒(méi)有找到,則返回False return False
五、實(shí)現(xiàn)刪除操作
要在二叉搜索樹(shù)中刪除一個(gè)元素,我們需要遵循以下步驟:
- 如果樹(shù)為空,則返回None。
- 如果樹(shù)不為空,則從根節(jié)點(diǎn)開(kāi)始,比較要?jiǎng)h除的值和當(dāng)前節(jié)點(diǎn)的值。
- 如果要?jiǎng)h除的值小于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的左子樹(shù)中刪除,并將刪除后的左子樹(shù)設(shè)為當(dāng)前節(jié)點(diǎn)的左子節(jié)點(diǎn)。
- 如果要?jiǎng)h除的值大于當(dāng)前節(jié)點(diǎn)的值,則遞歸地在當(dāng)前節(jié)點(diǎn)的右子樹(shù)中刪除,并將刪除后的右子樹(shù)設(shè)為當(dāng)前節(jié)點(diǎn)的右子節(jié)點(diǎn)。
- 如果要?jiǎng)h除的值等于當(dāng)前節(jié)點(diǎn)的值,則分以下三種情況處理:
- 如果當(dāng)前節(jié)點(diǎn)沒(méi)有子節(jié)點(diǎn),則直接刪除該節(jié)點(diǎn),并返回None。
- 如果當(dāng)前節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),則直接用該子節(jié)點(diǎn)替換該節(jié)點(diǎn),并返回該子節(jié)點(diǎn)。
- 如果當(dāng)前節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn),則先在其右子樹(shù)中找到最小值(或者在其左子樹(shù)中找到最大值),然后用該最?。ɑ蜃畲螅┲堤鎿Q該節(jié)點(diǎn),并遞歸地在其右(或左)子樹(shù)中刪除該最小(或最大)值,并將刪除后的右(或左)子樹(shù)設(shè)為該節(jié)點(diǎn)的右(或左)子節(jié)點(diǎn)。
我們可以用以下代碼實(shí)現(xiàn)刪除操作:
def delete(self, value): # 如果樹(shù)為空,則返回None if self.root is None: return None # 否則從根節(jié)點(diǎn)開(kāi)始遍歷 current = self.root parent = None while current is not None and current.value != value: # 記錄父節(jié)點(diǎn) parent = current # 如果要?jiǎng)h除的值小于當(dāng)前節(jié)點(diǎn)的值,則向左走 if value < current.value: current = current.left # 如果要?jiǎng)h除的值大于當(dāng)前節(jié)點(diǎn)的值,則向右走 else: current = current.right # 如果沒(méi)有找到要?jiǎng)h除的元素,則返回None if current is None: return None # 定義一個(gè)輔助函數(shù),用來(lái)找到一棵樹(shù)中最?。ɑ蜃畲螅┰厮诘慕Y(jié)點(diǎn)和其父結(jié)點(diǎn),參數(shù)是根結(jié)點(diǎn)和一個(gè)布爾變量,表示是否尋找最小元素,默認(rèn)為T(mén)rue,如果為False則尋找最大元素 def find_min_max(node, is_min=True): # 初始化最?。ɑ蜃畲螅┙Y(jié)點(diǎn)和其父結(jié)點(diǎn) min_max = node parent = None # 如果尋找最小元素,則一直向左走,否則一直向右走 if is_min: while min_max.left is not None: parent = min_max min_max = min_max.left else: while min_max.right is not None: parent = min_max min_max = min_max.right # 返回最小(或最大)結(jié)點(diǎn)和其父結(jié)點(diǎn) return min_max, parent # 如果當(dāng)前結(jié)點(diǎn)沒(méi)有子結(jié)點(diǎn),則直接刪除該結(jié)點(diǎn),并返回None if current.left is None and current.right is None: # 如果是根結(jié)點(diǎn),則將根結(jié)點(diǎn)設(shè)為None if parent is None: self.root = None # 否則判斷是父結(jié)點(diǎn)的左子結(jié)點(diǎn)還是右子結(jié)點(diǎn),并將其設(shè)為None else: if parent.left == current: parent.left = None else: parent.right = None return None # 如果當(dāng)前結(jié)點(diǎn)只有一個(gè)子結(jié)點(diǎn),則直接用該子結(jié)點(diǎn)替換該結(jié)點(diǎn),并返回該子結(jié)點(diǎn) if current.left is None or current.right is None: # 找到該子結(jié)點(diǎn),可以是左子結(jié)點(diǎn)也可以是右子結(jié)點(diǎn) child = current.left if current.left is not None else current.right # 如果是根結(jié)點(diǎn),則將根結(jié)點(diǎn)設(shè)為該子結(jié)點(diǎn) if parent is None: self.root = child # 否則判斷是父結(jié)點(diǎn)的左子結(jié)點(diǎn)還是右子結(jié)點(diǎn),并將其設(shè)為該子結(jié)點(diǎn) else: if parent.left == current: parent.left = child else: parent.right = child return child # 如果當(dāng)前結(jié)點(diǎn)有兩個(gè)子結(jié)點(diǎn),則先在其右子樹(shù)中找到最小值(或者在其左子樹(shù)中找到最大值),然后用該最小(或最大)值替換該結(jié)點(diǎn),并遞歸地在其右(或左)子樹(shù)中刪除該最?。ɑ蜃畲螅┲?,并將刪除后的右(或左)子樹(shù)設(shè)為該結(jié)點(diǎn)的右(或左)子節(jié)點(diǎn) # 這里我們選擇在右子樹(shù)中找到最小值,也可以在左子樹(shù)中找到最大值,只要保證替換后的結(jié)點(diǎn)滿足二叉搜索樹(shù)的性質(zhì)即可 min_node, min_parent = find_min_max(current.right, is_min=True) # 用最小值替換當(dāng)前結(jié)點(diǎn)的值 current.value = min_node.value # 遞歸地在右子樹(shù)中刪除最小值,并將刪除后的右子樹(shù)設(shè)為當(dāng)前結(jié)點(diǎn)的右子節(jié)點(diǎn) current.right = self.delete(min_node.value) return current
六、實(shí)現(xiàn)遍歷操作
遍歷是一種訪問(wèn)樹(shù)中所有元素的方法,有三種常見(jiàn)的遍歷方式:
- 前序遍歷(Pre-order Traversal):先訪問(wèn)根節(jié)點(diǎn),再訪問(wèn)左子樹(shù),最后訪問(wèn)右子樹(shù)。
- 中序遍歷(In-order Traversal):先訪問(wèn)左子樹(shù),再訪問(wèn)根節(jié)點(diǎn),最后訪問(wèn)右子樹(shù)。
- 后序遍歷(Post-order Traversal):先訪問(wèn)左子樹(shù),再訪問(wèn)右子樹(shù),最后訪問(wèn)根節(jié)點(diǎn)。
我們可以用以下代碼實(shí)現(xiàn)遍歷操作:
def pre_order(self, node): # 如果結(jié)點(diǎn)為空,則返回 if node is None: return # 先訪問(wèn)根節(jié)點(diǎn),打印其值 print(node.value, end=" ") # 再訪問(wèn)左子樹(shù),遞歸調(diào)用前序遍歷函數(shù) self.pre_order(node.left) # 最后訪問(wèn)右子樹(shù),遞歸調(diào)用前序遍歷函數(shù) self.pre_order(node.right) def in_order(self, node): # 如果結(jié)點(diǎn)為空,則返回 if node is None: return # 先訪問(wèn)左子樹(shù),遞歸調(diào)用中序遍歷函數(shù) self.in_order(node.left) # 再訪問(wèn)根節(jié)點(diǎn),打印其值 print(node.value, end=" ") # 最后訪問(wèn)右子樹(shù),遞歸調(diào)用中序遍歷函數(shù) self.in_order(node.right) def post_order(self, node): # 如果結(jié)點(diǎn)為空,則返回 if node is None: return # 先訪問(wèn)左子樹(shù),遞歸調(diào)用后序遍歷函數(shù) self.post_order(node.left) # 再訪問(wèn)右子樹(shù),遞歸調(diào)用后序遍歷函數(shù) self.post_order(node.right) # 最后訪問(wèn)根節(jié)點(diǎn),打印其值 print(node.value, end=" ")
七、實(shí)現(xiàn)其他操作
除了上述基本操作外,二叉搜索樹(shù)還可以實(shí)現(xiàn)一些其他有用的操作,例如:
- 求樹(shù)的高度(Height):從根節(jié)點(diǎn)到葉節(jié)點(diǎn)的最長(zhǎng)路徑上的節(jié)點(diǎn)數(shù)。
- 求樹(shù)的大?。⊿ize):樹(shù)中所有節(jié)點(diǎn)的個(gè)數(shù)。
- 求第k?。ɑ虻趉大)元素:按照中序遍歷的順序,找到第k個(gè)元素。
- 求兩個(gè)元素的最近公共祖先(Lowest Common Ancestor):兩個(gè)元素在樹(shù)中的最低層次的公共祖先節(jié)點(diǎn)。
我們可以用以下代碼實(shí)現(xiàn)這些操作:
def height(self, node): # 如果結(jié)點(diǎn)為空,則返回0 if node is None: return 0 # 否則返回左右子樹(shù)中較大的高度加1 return max(self.height(node.left), self.height(node.right)) + 1 def size(self, node): # 如果結(jié)點(diǎn)為空,則返回0 if node is None: return 0 # 否則返回左右子樹(shù)的大小之和加1 return self.size(node.left) + self.size(node.right) + 1 def kth_smallest(self, node, k): # 如果結(jié)點(diǎn)為空,則返回None if node is None: return None # 定義一個(gè)輔助函數(shù),用來(lái)計(jì)算一棵樹(shù)中有多少個(gè)小于等于給定值的元素,參數(shù)是根結(jié)點(diǎn)和給定值 def count_less_equal(node, value): # 如果結(jié)點(diǎn)為空,則返回0 if node is None: return 0 # 如果結(jié)點(diǎn)的值小于等于給定值,則返回左子樹(shù)的個(gè)數(shù)加右子樹(shù)中小于等于給定值的個(gè)數(shù)加1 if node.value <= value: return count_less_equal(node.left, value) + count_less_equal(node.right, value) + 1 # 如果結(jié)點(diǎn)的值大于給定值,則返回左子樹(shù)中小于等于給定值的個(gè)數(shù) else: return count_less_equal(node.left, value) # 計(jì)算當(dāng)前結(jié)點(diǎn)左子樹(shù)中有多少個(gè)元素 left_count = self.size(node.left) # 如果左子樹(shù)中有k-1個(gè)元素,則當(dāng)前結(jié)點(diǎn)就是第k小元素,返回其值 if left_count == k - 1: return node.value # 如果左子樹(shù)中有超過(guò)k-1個(gè)元素,則第k小元素在左子樹(shù)中,遞歸調(diào)用函數(shù)在左子樹(shù)中查找 elif left_count > k - 1: return self.kth_smallest(node.left, k) # 如果左子樹(shù)中有少于k-1個(gè)元素,則第k小元素在右子樹(shù)中,遞歸調(diào)用函數(shù)在右子樹(shù)中查找,注意要更新k的值為k-left_count-1,因?yàn)橐呀?jīng)排除了左子樹(shù)和當(dāng)前結(jié)點(diǎn) else: return self.kth_smallest(node.right, k - left_count - 1) def lowest_common_ancestor(self, node, p, q): # 如果結(jié)點(diǎn)為空,則返回None if node is None: return None # 如果結(jié)點(diǎn)的值等于p或q中的任意一個(gè),則返回該結(jié)點(diǎn),表示找到了其中一個(gè)元素 if node.value == p or node.value == q: return node # 否則在左右子樹(shù)中分別查找p和q,得到兩個(gè)結(jié)果 left_result = self.lowest_common_ancestor(node.left, p, q) right_result = self.lowest_common_ancestor(node.right, p, q) # 如果兩個(gè)結(jié)果都不為空,則表示p和q分別在當(dāng)前結(jié)點(diǎn)的兩側(cè),那么當(dāng)前結(jié)點(diǎn)就是最近公共祖先,返回該結(jié)點(diǎn) if left_result is not None and right_result is not None: return node # 如果兩個(gè)結(jié)果中有一個(gè)為空,則表示p和q都在另一個(gè)結(jié)果所在的子樹(shù)中,那么返回非空的結(jié)果作為最近公共祖先 if left_result is None: return right_result else: return left_result
到此這篇關(guān)于Python實(shí)現(xiàn)二叉搜索樹(shù)增刪改查的文章就介紹到這了,更多相關(guān)Python 二叉搜索樹(shù)增刪改查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
kaggle數(shù)據(jù)分析家庭電力消耗過(guò)程詳解
這篇文章主要為大家介紹了kaggle數(shù)據(jù)分析家庭電力消耗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Python自動(dòng)爬取圖片并保存實(shí)例代碼
大家好,本篇文章主要講的是Python自動(dòng)爬取圖片并保存實(shí)例代碼,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01Python List remove()實(shí)例用法詳解
在本篇內(nèi)容里小編給大家整理了一篇關(guān)于Python List remove()方法及實(shí)例,有需要的朋友們跟著學(xué)習(xí)下。2021-08-08總結(jié)分析python數(shù)據(jù)化運(yùn)營(yíng)關(guān)聯(lián)規(guī)則
本文內(nèi)容主要介紹了python數(shù)據(jù)化運(yùn)營(yíng)中關(guān)聯(lián)規(guī)則的一般應(yīng)用場(chǎng)景,以及關(guān)聯(lián)規(guī)則的實(shí)現(xiàn),并例舉了適應(yīng)的應(yīng)用示例,方便大家更直觀的理解應(yīng)用2021-08-08使用Python項(xiàng)目生成所有依賴(lài)包的清單方式
這篇文章主要介紹了使用Python項(xiàng)目生成所有依賴(lài)包的清單方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07Python+Selenium自動(dòng)化環(huán)境搭建與操作基礎(chǔ)詳解
Selenium是如今最常用的自動(dòng)化測(cè)試工具之一,支持快速開(kāi)發(fā)自動(dòng)化測(cè)試框架,且支持在多種瀏覽器上執(zhí)行測(cè)試。本文將介紹關(guān)于Selenium?Python自動(dòng)化腳本環(huán)境搭建的相關(guān)資料,需要的朋友可以參考下2022-03-03pandas or sql計(jì)算前后兩行數(shù)據(jù)間的增值方法
下面小編就為大家分享一篇pandas or sql計(jì)算前后兩行數(shù)據(jù)間的增值方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04