一.红黑树性质和应用
每个节点或是红色的,或是黑色的
根节点是黑色的
每个叶节点(NIL)是黑色的
如果一个节点是红色的,则它的两个子节点都是黑色的
对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
这五条性质决定了红黑树最长的分支的深度最多是最短分支深度的2倍,因为最短的就是每个节点都是黑的,最长的最多就是红黑相间嘛。这使得红黑树始终处于一个大致平衡的状态,保证了性能。
总而言之,红黑树的最大特点就是即使在最坏情况下,插入,查找,删除等操作的时间复杂度仍然都是O(logn),并且有多少数据就占用多少内存,相比hashmap更为节省内存。
二.红黑树python3实现与可视化
1).准备工作
首先需要安装python3,我这里的环境是windows7,python3.6。之后需要安装一个可视化模块graphviz,详情参照上一篇博客
https://blog.csdn.net/qq_35603331/article/details/81591949
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方法
三.尾声
这里的红黑树实现只实现了插入,有兴趣的话也可以尝试进一步实现删除等操作。