什么是红黑树?
历史由来?
红黑树是1970年代被发明出来的。红黑树的基础是搜索二叉树,搜索二叉树的查询可以做到时间复杂度可以做到O(logn),但是在有序插入数据时,搜索二叉树会退化为链表,时间复杂度为O(n),为了解决这个问题,红黑树被发明出来,主要是红黑树自平衡机制,能够保证O(logn)的时间复杂度。
红黑树自平衡机制是如何实现的
红黑树的性质
- 所有节点只有两种颜色,红色,黑色
- 根节点是黑色
- 所有叶子节点为NIL节点(黑色的空节点)
- 对于任意节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点数相同
- 如果一个节点是红色的,则子节点一定是黑色的 从性质5可以推导出,红色节点不能连续出现在一条路径上
自平衡的机制
左旋/右旋
左旋:以某个节点为旋转点,其右节点提升为旋转节点的父节点,同时右节点的左节点变更为旋转节点的右节点
右旋:以某个节点为旋转点,其左节点提升为旋转节点的父节点,左节点的右节点变更为旋转点的左节点
旋转的特点:只是变更了三个节点位置,同时有效的改变了树两侧的节点数,从而起到平衡的作用 旋转的特点只跟相邻的三个点两条线相关,只需要关注变更节点上三个属性变更即可。父节点,左右节点
颜色转换
红黑颜色转换是维持平衡的非常巧妙的方法。颜色转换主要是两种类型:
这两种类型父节点都是红色,因为如果是黑色,则不需要平衡。
情况1:叔叔节点为红色,则更新父节点和叔叔节点颜色为黑色,同时更新插入点指针为祖父节点,更新祖父节点为红色。
情况2:叔叔节点为黑色,以祖父节点为旋转点,旋转后,祖父节点更新为红色,父节点更新为黑色,这里只是举例右旋情况。
需要注意的是,根节点每次修复插入后,需要再次更新为黑色。这不会影响平衡。
通过变色和旋转规则,进行插入,保证了树的平衡。
如何理解红黑树插入平衡?
- 所谓红黑树的平衡是要保证红黑树的性质不变
- 插入的点为红色的点 根据这两个前提条件,就可以理解红黑树的插入平衡。通过分析可以知道,分为几种情况:
- 父节点为黑色,则不影响原有的平衡,无需变动
- 父节点为红色,叔叔节点为哨兵节点,则最终变化为黑色父节点,两个红色子节点,(同时利用左右旋转,转变祖父节点+左节点+左节点,或者祖父节点+右节点+右节点,左右旋转不改变颜色)
- 父节点为红色,叔叔节点为红色,则只需要改变父节点和叔叔节点为黑色,祖父节点为红色,因为这时改变了祖父节点的颜色,所以需要将检查点指向祖父节点
python代码实现:
class Node:
def __init__(self, key, color="red", parent=None, left=None, right=None):
self.key = key
self.color = color
self.parent = parent
self.left = left
self.right = right
class RedBlackTree:
def __init__(self):
self.NIL = Node(None, "black")
self.root = self.NIL
def left_rotate(self, x):
y = x.right
x.right = y.left
if y.left != self.NIL:
y.left.parent = x
y.parent = x.parent
if x.parent == None:
self.root = y
elif x == x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y
def right_rotate(self, x):
y = x.left
x.left = y.right
# 哨兵节点无需维护父节点指针
if y.right != self.NIL:
y.right.parent = x
y.parent = x.parent
if x.parent == None:
self.root = y
elif x == x.parent.right:
x.parent.right = y
else:
x.parent.left = y
y.right = x
x.parent = y
def insert(self, key):
z = Node(key)
z.left = self.NIL
z.right = self.NIL
y = None
x = self.root
while x != self.NIL:
y = x
if z.key < x.key:
x = x.left
else:
x = x.right
z.parent = y
if y == None:
self.root = z
z.color = "black"
return
elif z.key < y.key:
y.left = z
else:
y.right = z
z.color = "red"
self.insert_fixup(z)
def insert_fixup(self, z):
# 如果父节点为红色,那么一定不是根节,所以会有祖父节点
while z.parent.color == "red":
if z.parent == z.parent.parent.left:
y = z.parent.parent.right
if y.color == "red":
z.parent.color = "black"
y.color = "black"
z.parent.parent.color = "red"
z = z.parent.parent
else:
if z == z.parent.right:
z = z.parent
self.left_rotate(z)
z.parent.color = "black"
z.parent.parent.color = "red"
self.right_rotate(z.parent.parent)
else:
y = z.parent.parent.left
if y.color == "red":
z.parent.color = "black"
y.color = "black"
z.parent.parent.color = "red"
z = z.parent.parent
else:
if z == z.parent.left:
z = z.parent
self.right_rotate(z)
z.parent.color = "black"
z.parent.parent.color = "red"
self.left_rotate(z.parent.parent)
self.root.color = "black"
def inorder_traversal(self, node):
if node != self.NIL:
self.inorder_traversal(node.left)
print(f"Key: {node.key}, Color: {node.color}")
self.inorder_traversal(node.right)
# 使用示例
if __name__ == "__main__":
rbt = RedBlackTree()
rbt.insert(10)
rbt.insert(20)
rbt.insert(30)
rbt.insert(5)
rbt.insert(15)
rbt.inorder_traversal(rbt.root)
红黑树的删除平衡?
红黑书的删除平衡与插入平衡类似,需要分析删除节点后,是否满足红黑树的平衡性质。先从结构上分析,有四种情况
- 叶子节点
- 只有左节点
- 只有右节点
- 左右节点都存在 这里有个规律,就是对于红黑树来说,所有系节点的水平投影组成的值序列是有序的。那么删除前后,都会满足这个性质。所以第四中情况,删除的节点可以用右子树中的最小值代替,这个称为后继节点,从而情况4转变为前面三种情况中的一种。
以下分析加上颜色
叶子节点
叶子节点为红色,则删除后不会影响平衡。 如果叶子节点为黑色,那么叔叔节点为根节点所在的分支一定是有且只有一个黑色节点,那么就有以下几种情况
第一中情况中,p如果是根节点则为黑色,如果是非根节点一定是红色
第二三种情况有左孩子或者右孩子,只需要替换即可,同时将颜色置为黑色
第四种情况,找到后继节点,转换为前面三种情况即可,