紅黑樹邊學邊做--python3實現與視覺化
一.紅黑樹性質和應用
每個節點或是紅色的,或是黑色的
根節點是黑色的
每個葉節點(NIL)是黑色的
如果一個節點是紅色的,則它的兩個子節點都是黑色的
對於每個節點,從該節點到其所有後代葉節點的簡單路徑上,均包含相同數目的黑色節點。
這五條性質決定了紅黑樹最長的分支的深度最多是最短分支深度的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方法
三.尾聲
這裡的紅黑樹實現只實現了插入,有興趣的話也可以嘗試進一步實現刪除等操作。