红黑树

102 阅读5分钟

什么是红黑树?

历史由来?

红黑树是1970年代被发明出来的。红黑树的基础是搜索二叉树,搜索二叉树的查询可以做到时间复杂度可以做到O(logn),但是在有序插入数据时,搜索二叉树会退化为链表,时间复杂度为O(n),为了解决这个问题,红黑树被发明出来,主要是红黑树自平衡机制,能够保证O(logn)的时间复杂度。

红黑树自平衡机制是如何实现的

红黑树的性质

  1. 所有节点只有两种颜色,红色,黑色
  2. 根节点是黑色
  3. 所有叶子节点为NIL节点(黑色的空节点)
  4. 对于任意节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点数相同
  5. 如果一个节点是红色的,则子节点一定是黑色的 从性质5可以推导出,红色节点不能连续出现在一条路径上

自平衡的机制

左旋/右旋

左旋:以某个节点为旋转点,其右节点提升为旋转节点的父节点,同时右节点的左节点变更为旋转节点的右节点

image.png

右旋:以某个节点为旋转点,其左节点提升为旋转节点的父节点,左节点的右节点变更为旋转点的左节点

image.png

旋转的特点:只是变更了三个节点位置,同时有效的改变了树两侧的节点数,从而起到平衡的作用 旋转的特点只跟相邻的三个点两条线相关,只需要关注变更节点上三个属性变更即可。父节点,左右节点

颜色转换

红黑颜色转换是维持平衡的非常巧妙的方法。颜色转换主要是两种类型: 这两种类型父节点都是红色,因为如果是黑色,则不需要平衡。 image.png 情况1:叔叔节点为红色,则更新父节点和叔叔节点颜色为黑色,同时更新插入点指针为祖父节点,更新祖父节点为红色。

image.png 情况2:叔叔节点为黑色,以祖父节点为旋转点,旋转后,祖父节点更新为红色,父节点更新为黑色,这里只是举例右旋情况。 image.png

需要注意的是,根节点每次修复插入后,需要再次更新为黑色。这不会影响平衡。

通过变色和旋转规则,进行插入,保证了树的平衡。

如何理解红黑树插入平衡?

  1. 所谓红黑树的平衡是要保证红黑树的性质不变
  2. 插入的点为红色的点 根据这两个前提条件,就可以理解红黑树的插入平衡。通过分析可以知道,分为几种情况:
  3. 父节点为黑色,则不影响原有的平衡,无需变动
  4. 父节点为红色,叔叔节点为哨兵节点,则最终变化为黑色父节点,两个红色子节点,(同时利用左右旋转,转变祖父节点+左节点+左节点,或者祖父节点+右节点+右节点,左右旋转不改变颜色)
  5. 父节点为红色,叔叔节点为红色,则只需要改变父节点和叔叔节点为黑色,祖父节点为红色,因为这时改变了祖父节点的颜色,所以需要将检查点指向祖父节点

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)

红黑树的删除平衡?

红黑书的删除平衡与插入平衡类似,需要分析删除节点后,是否满足红黑树的平衡性质。先从结构上分析,有四种情况

  1. 叶子节点
  2. 只有左节点
  3. 只有右节点
  4. 左右节点都存在 这里有个规律,就是对于红黑树来说,所有系节点的水平投影组成的值序列是有序的。那么删除前后,都会满足这个性质。所以第四中情况,删除的节点可以用右子树中的最小值代替,这个称为后继节点,从而情况4转变为前面三种情况中的一种。

image.png 以下分析加上颜色

叶子节点

叶子节点为红色,则删除后不会影响平衡。 如果叶子节点为黑色,那么叔叔节点为根节点所在的分支一定是有且只有一个黑色节点,那么就有以下几种情况

image.png

第一中情况中,p如果是根节点则为黑色,如果是非根节点一定是红色 image.png

image.png

image.png

image.png

image.png

第二三种情况有左孩子或者右孩子,只需要替换即可,同时将颜色置为黑色

image.png

image.png 第四种情况,找到后继节点,转换为前面三种情况即可,