1. 程式人生 > >紅黑樹邊學邊做--python3實現與視覺化

紅黑樹邊學邊做--python3實現與視覺化

一.紅黑樹性質和應用

  1. 每個節點或是紅色的,或是黑色的

  2. 根節點是黑色的

  3. 每個葉節點(NIL)是黑色的

  4. 如果一個節點是紅色的,則它的兩個子節點都是黑色的

  5. 對於每個節點,從該節點到其所有後代葉節點的簡單路徑上,均包含相同數目的黑色節點。

這五條性質決定了紅黑樹最長的分支的深度最多是最短分支深度的2倍,因為最短的就是每個節點都是黑的,最長的最多就是紅黑相間嘛。這使得紅黑樹始終處於一個大致平衡的狀態,保證了效能。
總而言之,紅黑樹的最大特點就是即使在最壞情況下,插入,查詢,刪除等操作的時間複雜度仍然都是O(logn),並且有多少資料就佔用多少記憶體,相比hashmap更為節省記憶體。

二.紅黑樹python3實現與視覺化

1).準備工作

2).python實現與講解

首先直接貼程式碼

1.python實現

BLACK = 0
RED = 1
from graphviz import Digraph


class Rbtree:
    root = None
    name = None

    def __init__(self, name='rbtree'):
        self.name = name

    def insert(self, point):
        if self.root is None:
            point.color = BLACK
            self.root = point
            return
self.root.add_child(point) def find(self, key): if self.root is None: print('該樹為空樹!') return None return self.root.find(key) # def select(self, point): # def print(self): def view(self): graph = Digraph(self.name) if self.root is
not None: self.root.draw(graph) graph.view(cleanup=True) class RbPoint: parent = None left = None right = None color = -1 # 節點顏色 0 黑 1紅 key = None tree = None def __init__(self, key, tree): self.key = key def view(self): pg = Digraph('find') pg.node(str(self.key), style='filled', color=('red' if self.color == RED else 'BLACK'), fontcolor='white', shape='circle') pg.view() def draw(self, graph): if self.color == BLACK: s = 'black' else: s = 'red' # 畫出自己這個點,包括值和顏色 graph.node(str(self.key), style='filled', color=('red' if self.color == RED else 'BLACK'), fontcolor='white', shape='circle') if self.left is not None: graph.edge(str(self.key), str(self.left.key)) self.left.draw(graph) if self.right is not None: graph.edge(str(self.key), str(self.right.key)) self.right.draw(graph) def change_color(self): if self.color == BLACK: self.color = RED else: self.color = BLACK def rotate(self, child): # 和左孩子進行旋轉 if child == self.left: print('從左往右旋') if self.parent is not None: if self.parent.left == self: self.parent.left = child else: self.parent.right = child child.parent = self.parent self.parent = child self.left = child.right child.right = self if child.parent is None: # 該節點變為根節點 print('根節點變更') tree.root = child # 和右孩子進行旋轉 else: print('從右往左旋') if self.parent is not None: if self.parent.left == self: self.parent.left = child else: self.parent.right = child child.parent = self.parent self.right = child.left child.left = self self.parent = child if child.parent is None: # 該節點變為根節點 print('根節點變更') tree.root = child def find(self, key): print('當前節點值:', self.key, '查詢值:', key) if key == self.key: return self if key < self.key: if self.left is None: return None else: return self.left.find(key) else: if self.right is None: return None else: return self.right.find(key) def add_child(self, child): if child.key < self.key: if self.left is None: self.left = child child.parent = self print('鍵為', child.key, '的節點插入到鍵為', self.key, '的節點的左孩子處') self.adjust(child) else: self.left.add_child(child) return if child.key > self.key: if self.right is None: self.right = child child.parent = self print('鍵為', child.key, '的節點插入到鍵為', self.key, '的節點的右孩子處') self.adjust(child) else: self.right.add_child(child) def adjust(self, child): def handle1(g, p): g.rotate(p) g.change_color() p.change_color() print('狀況1調整完畢') def handle2(g, p, n): p.rotate(n) # 狀況2->狀況1 g.rotate(n) n.change_color() g.change_color() print('狀況2') def handle3(g, p, u): print('狀況3') p.change_color() u.change_color() g.change_color() if g.parent is not None: g.parent.adjust(g) else: # g為根節點 g.color = BLACK def handle4(g, p): p.change_color() g.change_color() if g.parent is not None: g.parent.adjust(g) else: # g為根節點 g.color = BLACK print('開始調整') # 子節點預設紅色 child.color = RED # 根據p節點(父節點)顏色判斷是否需要調整 if self.color == BLACK: # 黑色,不需要調整 return # 父節點也為紅色,必須調整 # 父節點為紅色,G節點(父節點的父節點)必存在且必為黑色 # 狀況1:U節點(叔叔節點)為黑色,n節點(新增加的節點)外側插入 # 狀況2:u為黑色,n內側插入 # 狀況3:u為紅色 g = self.parent if self == g.left: # 父節點為祖父節點的左孩子,叔叔節點為祖父節點的右孩子 u = g.right if u is None or u.color == BLACK: # u節點為黑色,狀況1或2 if child == self.left: # 狀況1 handle1(g, self) else: # 狀況2 handle2(g, self, child) else: # u節點為紅色,狀況3 handle3(g, self, u) # 孩子節點為父節點的左孩子,狀況1 else: # 父節點為祖父節點的右孩子 u = g.left if u is None or u.color == BLACK: if child == self.right: # 狀況1 handle1(g, self) else: # 狀況2 handle2(g, self, child) else: # 狀況3,u節點為紅色 handle3(g, self, u) if __name__ == '__main__': tree = Rbtree('t2') for i in range(20, -1, -1): p = RbPoint(i, tree) tree.insert(p) tree.view()

如果環境配置沒有問題的話應該可以直接執行,會生成如下圖片
這裡寫圖片描述
(當然實際上生成的是個pdf)
這就是我們構建的一個簡單紅黑樹了,具體構建過程如下:

if __name__ == '__main__':
    tree = Rbtree('t2')
    for i in range(20, -1, -1):
        p = RbPoint(i, tree)
        tree.insert(p)
    tree.view()

也就是把20到0的數字依次插進紅黑樹而已

2.思路講解

這裡比較關鍵的就是兩個類,Rbtree和Rbpoint,分別表示紅黑樹和紅黑樹的節點,通過紅黑樹完成插入,查詢等操作,紅黑樹始終持有根節點root,從而可以呼叫root的查詢,插入方法來完成具體操作。
其中Rbpoint的屬性left,right,partent分別表示左右孩子和父親,key表示標識這個節點的關鍵字,用來比較不同節點的大小,是排序的依據。之後Rbpoint實現了find,add_child和adjust這幾個比較關鍵的方法。find就是查詢某個節點的位置,並返回這個節點,add_child則是插入新節點到以當前節點為根的樹中,adjust就是插入新節點以後對紅黑樹進行調整。
重點說一下add_child的思路,先比較新節點和當前節點的大小關係,如果新節點小,看看當前節點左孩子是否為空,為空就插入,然後進行調整,如果不為空,就繼續呼叫左孩子的add_child方法插入這個新節點,不斷遞迴,新節點比當前節點大的情況同理。
然後是插入後進行調整。我們預設新插入的節點都是紅節點,插入後就是判斷不同情況,進行調整。
如果插入的位置是根節點,那麼直接把新節點塗成黑色即可。如果插入位置的父節點是黑色,那麼什麼也不用做。
此外就剩下三種情況了,我們只要先判斷出這三種情況,然後進行調整即可。
情況1:
U節點(叔叔節點)為黑色,n節點(新增加的節點)外側插入
這種情況下直接將爺爺節點和父節點進行右旋處理,然後變換原先爺爺節點和父節點顏色即可,同時注意可能造成的root節點變更,需要及時更新。
這裡寫圖片描述
情況2:u為黑色,n內側插入
這種情況首先將新節點和父節點左旋處理,這就變成了情況1,然後將爺爺節點和新節點左旋(因為新節點已經到了原來父節點的位置),再分別變換顏色即可
這裡寫圖片描述
狀況3:u為紅色
直接將父節點和叔叔節點都變為黑色,爺爺節點變為紅色,然後把爺爺節點視為新插入的節點,繼續調整即可。
這裡寫圖片描述
注意:沒有叔叔節點則按照叔叔節點為黑色處理,但是要注意空指標的問題。此外,在左旋和右旋操作時,root節點可能會變化,需要注意維護Rbtree中的root始終指向根節點。

3.旋轉操作

這裡寫圖片描述
具體實現見程式碼中的rotate方法

三.尾聲

這裡的紅黑樹實現只實現了插入,有興趣的話也可以嘗試進一步實現刪除等操作。