红黑树的删除_Python实现红黑树的删除操作
一、代碼準備
在進行紅黑樹的刪除操作前,要先有一棵紅黑樹,所以這里使用上一篇文章中的代碼(文末附了完整代碼),先在紅黑樹中插入數據。1. 定義了一個節點類 RBNode ,用于創建新節點。2. 定義了紅黑樹類 RBBinaryTree ,類中實現了按樹形結構打印紅黑樹的方法 show_tree(),并且根據紅黑樹的節點顏色,打印值時打印對應的顏色。還有二叉搜索樹的插入方法 insert(root, value) 和搜索方法 search(root, data) 。同時還有紅黑樹的插入方法 rb_insert(value),獲取后繼節點的get_min(root)方法,以及對紅黑樹進行調整的左旋方法left_rotate(node),右旋方法right_rotate(node)和變色方法change_color(node)。
二、實現紅黑樹的刪除方法
紅黑樹的刪除方法可以分兩個步驟實現,第一步是按照二叉搜索樹的方法將節點刪除,第二步是對刪除節點后的紅黑樹進行調整,使紅黑樹重新滿足5條特性。不過,在實際的代碼中,要將這兩個步驟倒過來,先對紅黑樹進行調整,然后刪除節點,因為刪除節點后,節點間的關系就“斷開”了,不方便進行調整。二叉搜索樹刪除節點,分為三種情況:1. 被刪除節點為葉節點,即被刪除節點沒有左子樹和右子樹。這種情況直接找到節點將其刪除。2. 被刪除節點有一棵子樹,這棵子樹可以是左子樹或右子樹。這種情況將節點刪除后,用節點的子樹替換被刪除節點的位置,進行“補位”。3. 被刪除節點有兩棵子樹,即被刪除節點同時有左子樹和右子樹。這種情況先找到被刪除節點的后繼節點,將后繼節點的值“轉移”到被刪除節點中,然后刪除后繼節點。而后繼節點一定屬于前兩種情況的其中之一。因此,對于紅黑樹的刪除,也是按這個思路。為了方便理解,先申明幾個需要使用到的術語:待刪除節點:需要被刪除的節點,因為調整放在刪除的前面,所以在進行調整時待刪除節點還在紅黑樹中,用D(delete_node)表示。因素節點:因為這個節點的變化,使紅黑樹的結構發生了變化,用F(factor_node)表示。在第一次進行調整時,因素節點就是待刪除節點,如果一次調整后進行遞歸調整,遞歸時的因素節點可能會發生改變。父節點:因素節點的父節點,用P(parent_node)表示。兄弟節點:因素節點的父節點的另一個子節點,用B(brother_node)表示。兄弟節點的左子節點:因素節點的兄弟節點的左子節點,用BL(brother_node.left_child)表示。兄弟節點的右子節點:因素節點的兄弟節點的右子節點,用BR(brother_node.right_child)表示。本文的所有結構圖中,藍色節點表示該節點可以是紅節點或黑節點,不影響調整步驟和平衡結果。按照被刪除節點是否有子節點,分三種情況:1. 被刪除節點是葉節點(這里的葉節點不是指葉子節點NIL),葉節點沒有非空子節點。被刪除節點是葉節點時,根據被刪除節點的顏色,可以分為兩種情況。1.1 被刪除節點是紅節點,直接將節點刪除,不會破壞紅黑樹的5條特性,不需要進行調整。1.2 被刪除節點是黑節點。這是紅黑樹刪除中最復雜的部分,具體有如下三種情況。1.2.1 被刪除節點是根節點。直接將節點刪除,紅黑樹變成一棵空樹。1.2.2 被刪除節點是左子節點。根據兄弟節點的顏色,又可以分如下四種情況。1.2.2.1 兄弟節點是黑節點,并且兄弟節點的右子節點是紅節點。第一步將兄弟節點的顏色變成父節點的顏色,將父節點和兄弟節點的右子節點的顏色都設置成黑色。第二步以父節點作為旋轉節點進行左旋。調整完成,此時將待刪除節點從紅黑樹中刪除即可。刪除節點后,黑節點減少(破壞了特性5),所以要找到一個紅節點變成黑節點,補充減少的黑色。這里相當于是“借用”了BR節點的紅色,變成黑色來補充損失的黑色。這種情況只要BR節點是紅色即可,不管BL節點是否存在和BL是什么顏色。如果BL節點為空,則調整過程與上圖一樣,沒有變化。如果BL節點是一個紅節點,即BL和BR都是紅節點,BL的加入不會對紅黑樹的特性產生影響,調整的過程如下圖所示。如果BL節點是黑節點,調整方式也不變。因素節點是葉節點時,BL節點不可能是非空黑節點,因為在刪除節點前,紅黑樹是滿足5條特性的,因素節點和兄弟節點都是黑色,如果兄弟節點有黑色子節點,則因素節點也一定有黑色子節點,這與因素節點是葉節點矛盾。如果第一次調整后沒有達到平衡,將因素節點變成待刪除節點的父節點進行遞歸,因素節點不再是葉節點,這時可能會遇到BL節點是黑節點的情況,如后面的1.2.2.3調整后就可能變成這種情況。調整方式不變,并且經過此次調整后即可結束調整,調整過程如下圖(D的兄弟節點一開始是黑節點,上一次調整中變成了紅色)。
1.2.2.2 兄弟節點是黑節點,并且兄弟節點的左子節點是紅節點。第一步將兄弟節點變成紅色,將兄弟節點的左子節點變成黑色。第二步以兄弟節點作為旋轉節點進行右旋。這時二叉樹的結構變成了1.2.2.1的情況,第一次調整結束,因素節點不變,遞歸進行下一次調整。刪除節點后,黑節點減少(破壞了特性5),這里相當于是“借用”了BL節點的紅色,變成黑色來補充損失的黑色。這種情況只要BL節點是紅色即可,不管BR節點是否存在和BR是什么顏色。如果BR節點為空,則調整過程與上圖一樣,沒有變化。如果BR節點是紅色,即BL和BR都是紅節點,則會直接按1.2.2.1進行處理。如果BR是黑節點,調整方式也不變。與上面的1.2.2.1一樣,因素節點是葉節點時,BR節點不可能是黑節點。在后面的1.2.2.3中,如果第一次調整后沒有達到平衡,將因素節點變成待刪除節點的父節點進行遞歸,因素節點不再是葉節點,這時可能會遇到BR節點是黑節點的情況,如后面的1.2.2.3調整后就可能變成這種情況。調整方式不變,調整過程如下圖(D的兄弟節點一開始是黑節點,上一次調整中變成了紅色)。1.2.2.3 兄弟節點是黑節點,并且兄弟節點沒有子節點。第一步將兄弟節點變成紅色。第二步判斷父節點是不是紅節點。如果父節點是紅節點,直接將父節點變成黑節點,調整結束。如果父節點是黑節點,則將因素節點變為父節點,進行下一次遞歸調整,直到父節點是根節點。刪除節點后,黑節點減少(破壞了特性5),這里相當于是“借用”了父節點的紅色,變成黑色來補充損失的黑色,如果父節點不是紅色,則由父節點去(父節點的BL、BR或P)“借”,如果一直遞歸到父節點是根節點,都沒地方可以“借”,則所有葉節點到根節點的路徑上黑節點都減一。在遞歸調整的過程中,可能會遇到1.2.2.1和1.2.2.2兩種情況以及后面的1.2.3.1和1.2.3.2兩種情況,也可能遇到BL和BR都是黑節點的情況。如果BL和BR都是黑節點,則繼續將兄弟節點變成紅色,將因素節點變為父節點,繼續遞歸下去,調整過程如下圖(D的兄弟節點一開始是黑節點,上一次調整中變成了紅色,第二次調整F變成了D的父節點,此時BL和BR都為黑色,則繼續將B變紅,遞歸處理)。1.2.2.4 兄弟節點是紅節點。此時BL、BR和P一定都是黑節點,否則不滿特性4。第一步將兄弟節點變成黑色,將父節點變成紅色。第二步以父節點作為旋轉節點進行左旋。這時二叉樹的結構變成了1.2.2.3的情況,第一次調整結束,因素節點不變,遞歸進行下一次調整。并且下一次調整中因素節點的父節點是紅節點,一定可以調整完成。
刪除節點后,黑節點減少(破壞了特性5),這里相當于是“借用”了兄弟節點的紅色,變成黑色來補充損失的黑色。1.2.3 被刪除節點是右子節點。這與被刪除節點是左節點的情況互為鏡像,原理也一樣,根據兄弟節點的顏色,分如下四種情況。1.2.3.1 兄弟節點是黑節點,并且兄弟節點的左子節點是紅節點。第一步將兄弟節點的顏色變成父節點的顏色,將父節點和兄弟節點的左子節點的顏色都設置成黑色。第二步以父節點作為旋轉節點進行右旋。調整完成,此時將待刪除節點從紅黑樹中刪除即可。與被刪除節點是左子節點的原理一樣,只要BL是紅節點即可,不用關注是否有BR節點和BR是什么顏色,處理方式都一樣,這里就不展開了。1.2.3.2 兄弟節點是黑節點,并且兄弟節點的右子節點是紅節點。第一步將兄弟節點變成紅色,將兄弟節點的右子節點變成黑色。第二步以兄弟節點作為旋轉節點進行左旋。這時二叉樹的結構變成了1.2.3.1的情況,第一次調整結束,因素節點不變,遞歸進行下一次調整。與被刪除節點是左子節點一樣,只要BR是紅節點即可,不用關注是否有BL節點和BL是什么顏色,處理方式都一樣。1.2.3.3 兄弟節點是黑節點,并且兄弟節點沒有子節點。第一步將兄弟節點變成紅色。第二步判斷父節點是不是紅節點。如果父節點是紅節點,直接將父節點變成黑節點,調整結束。如果父節點是黑節點,則將因素節點變為父節點,進行下一次遞歸調整,直到父節點是根節點。在遞歸調整的過程中,可能會遇到1.2.3.1和1.2.3.2兩種情況以及前面的1.2.2.1和1.2.2.2兩種情況,也可能遇到BL和BR都是黑節點的情況。如果BL和BR都是黑節點,則繼續將兄弟節點變成紅色,將因素節點變為父節點,繼續遞歸下去。1.2.3.4 兄弟節點是紅節點。此時BL、BR和P一定都是黑節點,否則不滿特性4。第一步將兄弟節點變成黑色,將父節點變成紅色。第二步以父節點作為旋轉節點進行右旋。這時二叉樹的結構變成了1.2.3.3的情況,第一次調整結束,因素節點不變,遞歸進行下一次調整。并且下一次調整中因素節點的父節點是紅節點,一定可以調整完成。
到這里,待刪除節點是葉節點的所有情況都分析完成了,代碼實現如下。 def _rb_delete_no_child(self, node): """紅黑樹刪除兩個子節點都為空的節點""" if node == self.root: self.root = None self.root.color = 'black' return factor_node = node # 如果待刪除節點為黑節點則需要進行調整 if factor_node.color is 'black': while True: parent_node = factor_node.parent brother_node = parent_node.right_child if factor_node is parent_node.left_child else parent_node.left_child # 待刪除節點是左子節點 if factor_node is parent_node.left_child: # 如果兄弟節點是黑節點 if brother_node.color is 'black': # 如果兄弟節點沒有子節點(遞歸處理時如果兄弟節點的兩個子節點都是黑節點) if brother_node.left_child is None and brother_node.right_child is None or \ ((brother_node.left_child and brother_node.left_child.color is 'black') and (brother_node.right_child and brother_node.right_child.color is 'black')): self.change_color(brother_node) if parent_node.color is 'red': self.change_color(parent_node) break else: if parent_node == self.root: break factor_node = parent_node # 如果兄弟節點有右子節點,此右節點一定是紅節點(遞歸處理時,如果兄弟節點的右子節點為紅節點) elif brother_node.right_child is not None and brother_node.right_child.color is 'red': brother_node.color = parent_node.color parent_node.color, brother_node.right_child.color = 'black', 'black' self.left_rotate(parent_node) break # 如果兄弟節點有左節點,此左節點一定是紅節點(遞歸處理時,如果兄弟節點的左子節點為紅節點) else: brother_node.color, brother_node.left_child.color = 'red', 'black' self.right_rotate(brother_node) # 如果兄弟節點是紅節點 elif brother_node.color is 'red': self.change_color(parent_node) self.change_color(brother_node) self.left_rotate(parent_node) # 待刪除節點是右子節點 if factor_node is parent_node.right_child: if brother_node.color is 'black': # 如果兄弟節點沒有子節點(遞歸處理時如果兄弟節點的兩個子節點都是黑節點) if brother_node.left_child is None and brother_node.right_child is None or \ ((brother_node.left_child and brother_node.left_child.color is 'black') and (brother_node.right_child and brother_node.right_child.color is 'black')): self.change_color(brother_node) if parent_node.color is 'red': self.change_color(parent_node) break else: if parent_node == self.root: break factor_node = parent_node # 如果兄弟節點有左節點,此左節點一定是紅節點(遞歸處理時,如果兄弟節點的左子節點為紅節點) elif brother_node.left_child is not None and brother_node.left_child.color is 'red': brother_node.color = parent_node.color parent_node.color, brother_node.left_child.color = 'black', 'black' self.right_rotate(parent_node) break # 如果兄弟節點有右節點,此右節點一定是紅節點(遞歸處理時,如果兄弟節點的右子節點為紅節點) else: brother_node.color, brother_node.right_child.color = 'red', 'black' self.left_rotate(brother_node) # 如果兄弟節點是紅節點 elif brother_node.color is 'red': self.change_color(parent_node) self.change_color(brother_node) self.right_rotate(parent_node) if node is node.parent.left_child: node.parent.left_child = None else: node.parent.right_child = None node.parent = None_rb_delete_no_child(node): 紅黑樹刪除葉節點的方法。刪除葉節點后的調整是三種情況中最復雜的一種,要先理解每一種情況的調整方法,以及遞歸處理時可能會變成哪些情況。代碼是按照上面的分析過程實現的,需要仔細分析并理解。2. 被刪除節點有一個子節點。被刪除節點要么有左子節點沒有右子節點,要么沒有左子節點有右子節點。這種情況的處理比較簡單,因為在刪除前,紅黑樹滿足5條特性,所以不管子節點是左子節點還是右子節點,這個子節點一定是紅節點,否則不滿足特性5,進一步可以得出被刪除節點一定是黑節點,否則不滿足特性4。刪除比較簡單,第一步將待刪除節點的子節點變成黑色,第二步按照二叉搜索樹的方法將待刪除節點刪除,即用子節點補位到待刪除節點的位置。在這個過程中,不用區分待刪除節點的子節點是左子節點還是右子節點,也不用區分待刪除節點是其父節點的左子節點還是右子節點,或者待刪除節點是不是根節點。處理方式都一樣,將其子節點變黑補位即可,代碼如下。 def _rb_delete_one_child(self, node): """紅黑樹刪除有一個子節點的節點""" if node.left_child: self.change_color(node.left_child) if node.parent and node.parent.left_child == node: node.left_child.parent, node.parent.left_child = node.parent, node.left_child elif node.parent and node.parent.right_child == node: node.left_child.parent, node.parent.right_child = node.parent, node.left_child else: self.root = node.left_child node.left_child.parent, node.left_child = None, None elif node.right_child: self.change_color(node.right_child) if node.parent and node.parent.left_child == node: node.right_child.parent, node.parent.left_child = node.parent, node.right_child elif node.parent and node.parent.right_child == node: node.right_child.parent, node.parent.right_child = node.parent, node.right_child else: self.root = node.right_child node.right_child.parent, node.right_child = None, None node.parent = None_rb_delete_one_child(self, node): 紅黑樹刪除只有一個子節點的節點。這種情況的處理很簡單,代碼也比較簡單。3. 被刪除節點有兩個子節點,即被刪除節點同時有左子節點和右子節點。根據二叉搜索樹的刪除方法,這種情況需要找到被刪除節點的前繼節點或后繼節點,本文中使用后繼節點,用后繼節點的值替換被刪除節點的值,避免樹的“斷裂”,然后將后繼節點刪除。后繼節點是不可能有左子節點的,因此后繼節點要么沒有子節點,要么有一個右子節點,處理方法可以分為兩種。3.1 后繼節點沒有子節點。這時調用上面的情況1,將后繼節點刪除。3.2 后繼節點有一個右子節點。后繼節點一定是黑節點,后繼節點的右子節點一定是紅節點,調用上面的情況2,將后繼節點刪除。代碼實現如下。 def _rb_delete(self, node): """刪除節點的三種情況""" if node.left_child is None and node.right_child is None: self._rb_delete_no_child(node) elif node.left_child and not node.right_child or not node.left_child and node.right_child: self._rb_delete_one_child(node) else: rear_node = self.get_min(node.right_child) node.data = rear_node.data self._rb_delete(rear_node)刪除節點,首先這個節點要在紅黑樹中,因此不能創建一個節點然后刪除,而是根據節點的值,先到紅黑樹中進行搜索,如果這個值存在紅黑樹中,則將其刪除。(本文中的紅黑樹不會添加重復的值,這個可以按需進行修改)最后,紅黑樹的刪除方法如下。
def rb_delete(self, value): """紅黑樹刪除""" if not self.is_empty(): node_delete = self.search(self.root, value) if node_delete: self._rb_delete(node_delete) else: raise KeyError("Error, {value} not in this tree".format(value=value)) else: raise KeyError("Error, tree is empty")rb_delete(value): 紅黑樹的刪除方法。總結,紅黑樹的刪除中,被刪除節點是葉節點時最復雜,需要仔細分析每一種情況。當被刪除節點是黑節點時,會破壞特性5,經過被刪除節點的路徑上黑節點少了一個,所以要找一個紅節點變成黑色來補充,每一種情況分別是去BL、BR或P“借”,如果沒有紅節點可以“借”,則讓父節點去“借”,一直到父節點變成根節點都沒有紅節點可以“借”,則所有路徑上的黑節點都減一,從而保持平衡。被刪除節點有一個子節點時比較簡單,將子節點變黑補位即可。被刪除節點有兩個子節點時,可以將被刪除節點轉換成后繼節點,從而變成前兩種情況進行處理。三、紅黑樹刪除方法的驗證
1. 先插入數據到紅黑樹中,如還是用之前的數據[50, 77, 55, 29, 10, 30, 66, 18, 80, 51, 90]。if __name__ == '__main__': tree = RBBinaryTree() data = [50, 77, 55, 29, 10, 30, 66, 18, 80, 51, 90] for i in data: tree.rb_insert(i) tree.show_tree()運行結果:插入數據后紅黑樹的結構如下圖。現在刪除其中的一個節點,如刪除節點66。 tree.rb_delete(66) tree.show_tree()運行結果:刪除節點66后紅黑樹的結構如下圖。
可以看到,紅黑樹的刪除功能已經實現了。不過,由于數據比較少,沒有包含刪除的每一種情況,本文中也不可能每種情況都進行驗證,并且數據量大時,也不方便畫出紅黑樹的結構圖。所以,有必要實現一個方法來對紅黑樹進行自查,判斷當前紅黑樹是否滿足5條特性。 def rb_check(self): """檢查紅黑樹是否滿足5條特性""" if self.is_empty(): print("空樹") return queue = list() queue.insert(0, self.root) height = 0 while len(queue): node = queue.pop() if node.color is not "red" and node.color is not "black": raise Exception("節點{}不滿足特性1".format(node.data)) if node is self.root and node.color is not "black": raise Exception("節點{}不滿足特性2".format(node.data)) if node.color is "red" and (node.left_child and node.left_child.color is "red" or node.right_child and node.right_child.color is "red"): raise Exception("節點{}不滿足特性4".format(node.data)) if node.left_child is None and node.right_child is None: path = 0 cur = node while cur: if cur.color is "black": path += 1 cur = cur.parent if height and path != height: raise Exception("節點{}不滿足特性5".format(node.data)) else: height = path if node.left_child is not None: queue.insert(0, node.left_child) if node.right_child is not None: queue.insert(0, node.right_child) print("滿足紅黑樹的5條特性,葉子節點到根節點的非空黑節點個數為{}".format(height))rb_check(): 對當前紅黑樹進行判斷,如果不滿足5條特性中的一條,則拋出異常。此方法對紅黑樹的所有節點進行層序遍歷,依次對每一個節點判斷是否滿足紅黑樹的特性。下面添加一棵有1000個節點的紅黑樹,進行驗證。 data = range(1000) for i in data: tree.rb_insert(i) tree.rb_check() tree.rb_delete(66) print("--- after delete ---") tree.rb_check()運行結果:滿足紅黑樹的5條特性,葉子節點到根節點的非空黑節點個數為9--- after delete ---滿足紅黑樹的5條特性,葉子節點到根節點的非空黑節點個數為9代碼附在后面,更多情況請自行取用驗證。(部分代碼是可以精簡的,不過為了保證可讀性和方便與分析過程對照,這樣更好一點)四、完整代碼# coding=utf-8class RBNode(object): """節點類""" def __init__(self, data, left_child=None, right_child=None, color='red'): self.data = data self.parent = None self.left_child = left_child self.right_child = right_child self.color = colorclass RBBinaryTree(object): """紅黑樹類""" def __init__(self): self.__root = None self.prefix_branch = '├' self.prefix_trunk = '|' self.prefix_leaf = '└' self.prefix_empty = '' self.prefix_left = '─L─' self.prefix_right = '─R─' def is_empty(self): return not self.__root @property def root(self): return self.__root @root.setter def root(self, value): self.__root = value if isinstance(value, RBNode) else RBNode(value) def left_rotate(self, node): """紅黑樹左旋""" parent_node, right_node = node.parent, node.right_child if not right_node: return # 1.node是旋轉節點,將旋轉節點的右子節點的左子節點變為旋轉節點的右子節點 node.right_child = right_node.left_child if node.right_child: node.right_child.parent = node # 2.將旋轉節點修改為右子節點的左子節點 right_node.left_child, node.parent = node, right_node # 3.將右子節點替換旋轉節點的位置,作為旋轉節點父節點的子節點 if not parent_node: self.root = right_node else: if parent_node.left_child == node: parent_node.left_child = right_node else: parent_node.right_child = right_node right_node.parent = parent_node def right_rotate(self, node): """紅黑樹右旋""" parent_node, left_node = node.parent, node.left_child if not left_node: return # 1.node是旋轉節點,將旋轉節點的左子節點的右子節點變為旋轉節點的左子節點 node.left_child = left_node.right_child if node.left_child: node.left_child.parent = node # 2.將旋轉節點修改為左子節點的右子節點 left_node.right_child, node.parent = node, left_node # 3.將左子節點替換旋轉節點的位置,作為旋轉節點父節點的子節點 if not parent_node: self.root = left_node else: if parent_node.left_child == node: parent_node.left_child = left_node else: parent_node.right_child = left_node left_node.parent = parent_node def change_color(self, node): """紅黑樹變色""" if node.color is 'red': node.color = 'black' elif node.color is 'black': node.color = 'red' def rb_insert(self, value): """紅黑樹插入""" node = value if isinstance(value, RBNode) else RBNode(value) if self.search(self.root, node.data): return if self.is_empty(): node.color = 'black' self.root = node return self.insert(self.root, node) factor_node = node while True: parent_node = factor_node.parent if parent_node.color is 'red': grandparent_node = parent_node.parent if parent_node is grandparent_node.left_child: uncle_node = grandparent_node.right_child else: uncle_node = grandparent_node.left_child # 如果父節點為紅節點且叔節點為黑節點 if uncle_node is None or uncle_node and uncle_node.color is 'black': if parent_node == grandparent_node.left_child: # 先左旋為左左結果,然后父節點和祖父節點變色,再右旋 if factor_node == parent_node.right_child: self.left_rotate(parent_node) self.change_color(factor_node) else: self.change_color(parent_node) self.change_color(grandparent_node) self.right_rotate(grandparent_node) elif parent_node == grandparent_node.right_child: # 先右旋為右右結構,然后父節點和祖父節點變色,再左旋 if factor_node == parent_node.left_child: self.right_rotate(parent_node) self.change_color(factor_node) else: self.change_color(parent_node) self.change_color(grandparent_node) self.left_rotate(grandparent_node) break # 如果父節點為紅節點且叔節點也為紅節點 elif uncle_node and uncle_node.color is 'red': # 父節點和叔節點變色,祖父節點變色(祖父節點是根節點除外) self.change_color(parent_node) self.change_color(uncle_node) if grandparent_node != self.root: self.change_color(grandparent_node) # 祖父節點變成紅節點后,將祖父節點作為新的因素節點,判斷其父節點,避免不滿足特性4 factor_node = grandparent_node else: break def insert(self, root, value): """二叉搜索樹插入節點-遞歸""" node = value if isinstance(value, RBNode) else RBNode(value) if self.is_empty(): self.root = node return if root is None: root = node elif node.data < root.data: root.left_child = self.insert(root.left_child, value) root.left_child.parent = root elif node.data > root.data: root.right_child = self.insert(root.right_child, value) root.right_child.parent = root return root def rb_delete(self, value): """紅黑樹刪除""" if not self.is_empty(): node_delete = self.search(self.root, value) if node_delete: self._rb_delete(node_delete) else: raise KeyError("Error, {value} not in this tree".format(value=value)) else: raise KeyError("Error, tree is empty") def _rb_delete(self, node): """刪除節點的三種情況""" if node.left_child is None and node.right_child is None: self._rb_delete_no_child(node) elif node.left_child and not node.right_child or not node.left_child and node.right_child: self._rb_delete_one_child(node) else: rear_node = self.get_min(node.right_child) node.data = rear_node.data self._rb_delete(rear_node) def _rb_delete_no_child(self, node): """紅黑樹刪除兩個子節點都為空的節點""" if node == self.root: self.root = None self.root.color = 'black' return factor_node = node # 如果待刪除節點為黑節點則需要進行調整 if factor_node.color is 'black': while True: parent_node = factor_node.parent brother_node = parent_node.right_child if factor_node is parent_node.left_child else parent_node.left_child # 待刪除節點是左子節點 if factor_node is parent_node.left_child: # 如果兄弟節點是黑節點 if brother_node.color is 'black': # 如果兄弟節點沒有子節點(遞歸處理時如果兄弟節點的兩個子節點都是黑節點) if brother_node.left_child is None and brother_node.right_child is None or \ ((brother_node.left_child and brother_node.left_child.color is 'black') and (brother_node.right_child and brother_node.right_child.color is 'black')): self.change_color(brother_node) if parent_node.color is 'red': self.change_color(parent_node) break else: if parent_node == self.root: break factor_node = parent_node # 如果兄弟節點有右子節點,此右節點一定是紅節點(遞歸處理時,如果兄弟節點的右子節點為紅節點) elif brother_node.right_child is not None and brother_node.right_child.color is 'red': brother_node.color = parent_node.color parent_node.color, brother_node.right_child.color = 'black', 'black' self.left_rotate(parent_node) break # 如果兄弟節點有左節點,此左節點一定是紅節點(遞歸處理時,如果兄弟節點的左子節點為紅節點) else: brother_node.color, brother_node.left_child.color = 'red', 'black' self.right_rotate(brother_node) # 如果兄弟節點是紅節點 elif brother_node.color is 'red': self.change_color(parent_node) self.change_color(brother_node) self.left_rotate(parent_node) # 待刪除節點是右子節點 if factor_node is parent_node.right_child: if brother_node.color is 'black': # 如果兄弟節點沒有子節點(遞歸處理時如果兄弟節點的兩個子節點都是黑節點) if brother_node.left_child is None and brother_node.right_child is None or \ ((brother_node.left_child and brother_node.left_child.color is 'black') and (brother_node.right_child and brother_node.right_child.color is 'black')): self.change_color(brother_node) if parent_node.color is 'red': self.change_color(parent_node) break else: if parent_node == self.root: break factor_node = parent_node # 如果兄弟節點有左節點,此左節點一定是紅節點(遞歸處理時,如果兄弟節點的左子節點為紅節點) elif brother_node.left_child is not None and brother_node.left_child.color is 'red': brother_node.color = parent_node.color parent_node.color, brother_node.left_child.color = 'black', 'black' self.right_rotate(parent_node) break # 如果兄弟節點有右節點,此右節點一定是紅節點(遞歸處理時,如果兄弟節點的右子節點為紅節點) else: brother_node.color, brother_node.right_child.color = 'red', 'black' self.left_rotate(brother_node) # 如果兄弟節點是紅節點 elif brother_node.color is 'red': self.change_color(parent_node) self.change_color(brother_node) self.right_rotate(parent_node) if node is node.parent.left_child: node.parent.left_child = None else: node.parent.right_child = None node.parent = None def _rb_delete_one_child(self, node): """紅黑樹刪除有一個子節點的節點""" if node.left_child: self.change_color(node.left_child) if node.parent and node.parent.left_child == node: node.left_child.parent, node.parent.left_child = node.parent, node.left_child elif node.parent and node.parent.right_child == node: node.left_child.parent, node.parent.right_child = node.parent, node.left_child else: self.root = node.left_child node.left_child.parent, node.left_child = None, None elif node.right_child: self.change_color(node.right_child) if node.parent and node.parent.left_child == node: node.right_child.parent, node.parent.left_child = node.parent, node.right_child elif node.parent and node.parent.right_child == node: node.right_child.parent, node.parent.right_child = node.parent, node.right_child else: self.root = node.right_child node.right_child.parent, node.right_child = None, None node.parent = None def rb_check(self): """檢查紅黑樹是否滿足5條特性""" if self.is_empty(): print("空樹") return queue = list() queue.insert(0, self.root) height = 0 while len(queue): node = queue.pop() if node.color is not "red" and node.color is not "black": raise Exception("節點{}不滿足特性1".format(node.data)) if node is self.root and node.color is not "black": raise Exception("節點{}不滿足特性2".format(node.data)) if node.color is "red" and (node.left_child and node.left_child.color is "red" or node.right_child and node.right_child.color is "red"): raise Exception("節點{}不滿足特性4".format(node.data)) if node.left_child is None and node.right_child is None: path = 0 cur = node while cur: if cur.color is "black": path += 1 cur = cur.parent if height and path != height: raise Exception("節點{}不滿足特性5".format(node.data)) else: height = path if node.left_child is not None: queue.insert(0, node.left_child) if node.right_child is not None: queue.insert(0, node.right_child) print("滿足紅黑樹的5條特性,葉子節點到根節點的非空黑節點個數為{}".format(height)) def search(self, root, data): """二叉搜索樹的查詢操作""" if root is None: return if root.data == data: return root elif data < root.data: return self.search(root.left_child, data) elif data > root.data: return self.search(root.right_child, data) def get_min(self, root): """查找二叉搜索樹值最小的節點""" if self.is_empty(): return return self.get_min(root.left_child) if root.left_child else root def show_tree(self): if self.is_empty(): print('空二叉樹') return print('-' * 20) print("\033[31m{}\033[0m".format(str(self.root.data))) if self.root.color is 'red' else print(str(self.root.data)) self.__print_tree(self.__root) print('-' * 20) def __print_tree(self, node, prefix=None): if prefix is None: prefix, prefix_left_child = '', '' else: prefix = prefix.replace(self.prefix_branch, self.prefix_trunk).replace(self.prefix_leaf, self.prefix_empty) prefix_left_child = prefix.replace(self.prefix_leaf, self.prefix_empty) if self.has_child(node): if node.right_child is not None: if node.right_child.color is 'red': print(prefix + self.prefix_branch + self.prefix_right + "\033[31m{}\033[0m".format(str(node.right_child.data))) else: print(prefix + self.prefix_branch + self.prefix_right + str(node.right_child.data)) if self.has_child(node.right_child): self.__print_tree(node.right_child, prefix + self.prefix_branch + ' ') else: print(prefix + self.prefix_branch + self.prefix_right) if node.left_child is not None: if node.left_child.color is 'red': print(prefix + self.prefix_leaf + self.prefix_left + "\033[31m{}\033[0m".format(str(node.left_child.data))) else: print(prefix + self.prefix_leaf + self.prefix_left + str(node.left_child.data)) if self.has_child(node.left_child): prefix_left_child += ' ' self.__print_tree(node.left_child, self.prefix_leaf + prefix_left_child) else: print(prefix + self.prefix_leaf + self.prefix_left) def has_child(self, node): return node.left_child is not None or node.right_child is not Noneif __name__ == '__main__': tree = RBBinaryTree() # data = [50, 77, 55, 29, 10, 30, 66, 18, 80, 51, 90] # for i in data: # tree.rb_insert(i) # # tree.show_tree() # # tree.rb_delete(66) # tree.show_tree() data = range(1000) for i in data: tree.rb_insert(i) tree.rb_check() tree.rb_delete(66) print("--- after delete ---") tree.rb_check()總結
以上是生活随笔為你收集整理的红黑树的删除_Python实现红黑树的删除操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python def return 文件
- 下一篇: tomcat启动成功 未加载项目_欣冠精