红黑树解读(一)


前言

红黑树是一种高效的二叉搜索树,平均查找性能要比普通的二叉树要高效,也经常出现在各种面试当中,但是红黑树的构建也确实相当复杂。相比其他博客一上来就介绍各种复杂的概念,本文从普通的二叉树开始,介绍如何构造一棵红黑树。


一、二叉搜索树是什么?

定义:

  1. 二叉搜索树的每个节点最多有两个节点,即二叉树的定义
  2. 二叉搜索树的根节点的左子树的值比根节点的值小
  3. 二叉搜索树的根节点的右子树的值比根节点的值大
  4. 左子树和右子树都是一颗二叉搜索树

缺陷:一个有序数组构建二叉搜索树时,会退化成一个链表,搜索时间效率变成O(n)

二、平衡树是什么?

为了修复二叉搜索树的缺陷,一个直观的想法就是约束左右子树的高度,不能让所有节点全部插入右子树,这就是平衡树。
定义:

  1. 平衡树的每个节点最多有两个节点,即二叉树的定义
  2. 平衡树的根节点的左子树高度和右子树的高度相差<=1
  3. 左子树和右子树都是一颗二平衡树

缺陷:很明显,在插入新节点的时候会不停的校验是否符合平衡树的定义,代码的复杂度增加了,插入操作和删除操作都需要重新进行平衡。

三、B树是什么?

m阶B树定义:

  1. 根节点最少有1个关键字
  2. 非根节点的关键字数目在 [floor((m - 1)/2) , m - 1]
  3. 节点关键字大小从左到右依次增加,每个关键字的左子树的关键字比右子树的小
  4. 所有叶子节点都是同一层
  5. m阶指的是节点的子树的最大个数,而非关键字个数

3阶B树如下:
在这里插入图片描述

四、红黑树是什么?

定义:

红黑树是什么?

  1. 一颗二叉搜索树
  2. 根节点是黑色
  3. 红黑树的叶子节点是一个空指针,标记为黑色
  4. 任意节点节点不是黑色就是红色
  5. 不存在连续两个红色节点
  6. 从根节点到任意叶子节点的路径上的黑色节点个数都相同

红黑树实质上是一颗4阶B树,一个黑色节点的子树如果是红色节点,可以把这个红色节点和黑色节点合并放到同一行,示例:
在这里插入图片描述
如上图,就是一颗4阶B树,也是一棵红黑树,这样我们可以分析一下红黑树的第5、6条的性质背后的原因:

  1. 如果出现连续的红色节点,那么某个节点就会可能出现5个子树,不满足4阶B树的性质
  2. 如果路径上黑色节点个数不同,那么B树的叶子节点就不可能在同一层了
  3. B树保证了不会出现类似二叉搜索树的极端情况,但同时也没有平衡树那么严格的平衡性

五、构造一棵红黑树

1.构造一棵二叉搜索树

节点数据结构定义如下(parent字段可暂不定义):

class Node:
    def __init__(self, left, right, parent, value):
        self.left = left
        self.right = right
        self.parent = parent
        self.value = value

二叉搜索树数据结构定义,关于代码的解读应该问题不大,就没有赘述了,代码中缺少删除节点、遍历操作以及输出二叉树结构的方法,为了节省篇幅以及重点突出,暂时只展示插入操作:

class Node:
    def __init__(self, left, right, parent, value):
        self.left = left
        self.right = right
        self.parent = parent
        self.value = value


class BinaryTree:
    def __init__(self, elements=None):
        self.root = None
        self.left = None
        self.right = None
        self.parent = None
        self.value = None

        if elements is None:
            elements = []

        for element in elements:
            self.insert(element)
            
    def _insert(self, sub_tree, value):
        if value < sub_tree.value:
            if sub_tree.left is None:
                sub_tree.left = Node(None, None, sub_tree, value)
            else:
                self._insert(sub_tree.left, value)
        else:
            if sub_tree.right is None:
                sub_tree.right = Node(None, None, sub_tree, value)
            else:
                self._insert(sub_tree.right, value)

    def insert(self, value):
        if self.root is None:
            self.root = Node(None, None, None, value)
        else:
            self._insert(self.root, value)

展示二叉搜索树插入代码的主要目的:
红黑树实质也是一棵二叉搜索树,所以插入代码是可以复用的,无非是插入操作不是简单将节点放在left和right指向上,需要染色以及旋转保持红黑树的性质而已

2.展示一棵二叉树

直接输出所有节点是简单,但是不直观。

在这里插入图片描述

上述图片是一个左旋转90度的二叉树,然后每个节点都是可以独占一行,可以利用一个二维数组来展示一棵二叉树,从上往下,每个节点可以构造一个列表: 节点深度 * \t + 节点的值。这样最终的二维数组就是 节点数 个列表注意:二叉树的中序遍历正好满足从上到下遍历的要求,最后,将获得的二维数组变换一下正常输出即可。

    def traverse(self, positions_list, sub_tree, depth):


        if sub_tree is None:
            return
        depth += 1
        self.traverse(positions_list, sub_tree.right, depth)
        # print(self.root.value)
        print(sub_tree)
        positions_list.append(["\t"] * depth + ["%-4d" % (sub_tree.value)])


        self.traverse(positions_list, sub_tree.left, depth)
        depth -= 1

    def __str__(self):
        if self.root is None:
            return 'Empty tree'

        depth = -1
        positions_list = []
        self.traverse(positions_list, self.root, depth)

        # print(positions_list)

        lines = []
        for line in itertools.zip_longest(*positions_list, fillvalue="\t"):
            # print(line)
            lines.append("".join(line[::-1]))

        return "\n".join(lines)

3.构造一棵红黑树

由二叉树插入部分代码可知,无非是插入左边还是右边,插入的新节点一定是在最后一层,而非中间节点,那么被插入的子节点所属的结构有四种情况:红黑红、红黑、黑红、黑
在这里插入图片描述
新的节点插入就有12种情形,插入左侧六种,插入右侧六种,这两大类无非是方向换了一下,插入的核心代码没有区别。

1.左侧六种情况

现在先假定插入的节点都在子节点的左侧,那么一共有六种情况,下图中蓝色方框中的红色节点就是要插入的节点,插入的节点默认是红色的
在这里插入图片描述

2. 第4、6种情况无需变色以及变换,直接插入即可

3. 第3、5种情况需要变色以及旋转

右旋:
右旋

代码:

    def right_rotation(self, root):
    # root节点指的是H节点,注意:(left或right)引用变化和parent引用变化一定是一对,不能单个出现
        if not root:
            return

        grandparent_node = root.parent
        parent_node = root
        child_node = root.left
	# H节点变红,H节点的左边指向N节点,N节点的父亲引用指向H节点
        parent_node.color = RED
        parent_node.left = child_node.right
        if child_node.right:
            child_node.right.parent = parent_node

        parent_node.parent = child_node
        child_node.right = parent_node

        child_node.color = BLACK
        if grandparent_node:
            child_node.parent = grandparent_node
            if grandparent_node.left and grandparent_node.left.value == parent_node.value:
                grandparent_node.left = child_node
            else:
                grandparent_node.right = child_node
        else:
            # 根节点的变化记得初始化root指针
            child_node.parent = None
            self.root = child_node

先右旋再左旋:
右旋 + 左旋右旋代码在上文,左旋代码如下,和右旋代码类似:

    def left_rotation(self, root):
    # root是I节点
        if not root:
            return

        grandparent_node = root.parent
        parent_node = root
        child_node = root.right

        parent_node.color = RED
        parent_node.right = child_node.left
        if child_node.left:
            child_node.left.parent = parent_node

        parent_node.parent = child_node
        child_node.left = parent_node

        child_node.color = BLACK
        if grandparent_node:
            child_node.parent = grandparent_node
            if grandparent_node.left and grandparent_node.left.value == parent_node.value:
                grandparent_node.left = child_node
            else:
                grandparent_node.right = child_node
        else:
            # 根节点的变化记得初始化root指针
            child_node.parent = None
            self.root = child_node

4. 第1、2种情况需要变色以及旋转以及向上溢出

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:1、2中情况都是插入左侧,其实插入右侧的情况也是一样的
上述操作展示了溢出操作全过程,代码如下:

    def left_overflow(self, sub_tree):
        """
        溢出操作,溢出的节点是红色的会放到上一层节点中,所以相当于以溢出节点作为根节点的子树,
        插入上一层的节点,即调用_insert方法插入即可
        :param sub_tree:
        :return:
        """
        if not sub_tree:
            return

        left_node = sub_tree.left
        right_node = sub_tree.right
        middle_node = sub_tree
        parent_node = sub_tree.parent

        left_node.color = BLACK
        right_node.color = BLACK
        if parent_node:
            middle_node.color = RED
            if parent_node.left and parent_node.left.value == middle_node.value:
                parent_node.left = None
                middle_node.parent = None
            else:
                parent_node.right = None
                middle_node.parent = None
            self._insert(parent_node, middle_node)
        else:
            middle_node.color = BLACK

5.右侧六种情况

在这里插入图片描述
代码的核心部分也是左旋、右旋以及染色,只不过进入的条件和左侧不一样。

6.插入操作

RED = "red"
BLACK = "black"
class ColorNode:
    def __init__(self, left, right, parent, value, color):
        self.left = left
        self.right = right
        self.parent = parent
        self.value = value
        self.color = color

    def __str__(self):
        left_value = self.left.value if self.left else "None"
        right_value = self.right.value if self.right else "None"
        parent_value = self.parent.value if self.parent else "None"
        return "Node(value: %s, color:%s, left:%s, right:%s, parent:%s)" % (
            self.value, self.color, left_value, right_value, parent_value)

class RedBlackTree:
    def __init__(self, elements=None):
        self.root = None
        self.left = None
        self.right = None
        self.parent = None
        self.value = None

        if elements is None:
            elements = []

        for element in elements:
            self.insert(element)

    def right_rotation(self, root):
    	# 前文已描述
        pass

    def left_rotation(self, root):
        # 前文已描述
        pass

    def left_overflow(self, sub_tree):
        # 前文已描述
        pass

    def _insert(self, sub_tree, node):
    	"""
        此处的代码和二叉搜索树的代码框架大致相同,无非真正插入时,根据被插入节点的子结构的情况
        选择合适的方法进行插入
        :param sub_tree: 
        :param node: 
        :return: 
        """
        if node.value < sub_tree.value:
            if sub_tree.left:
                self._insert(sub_tree.left, node)
                return

            # 两种情况:节点是黑色,插入在节点的左侧
            if sub_tree.color == BLACK:
                sub_tree.left = node
                node.parent = sub_tree
                return

            # 1种情况,节点红色,节点的父节点的右子树不存在或者是黑色
            if sub_tree.color == RED and (not sub_tree.parent.right or sub_tree.parent.right.color == BLACK):
                sub_tree.left = node
                node.parent = sub_tree
                self.right_rotation(sub_tree.parent)
                return


            # 1种情况,节点红色,节点的父节点的左子树不存在或者是黑色
            if sub_tree.color == RED and (not sub_tree.parent.left or sub_tree.parent.left.color == BLACK):
                sub_tree.left = node
                node.parent = sub_tree

                parent_node = sub_tree.parent
                self.right_rotation(sub_tree)

                self.left_rotation(parent_node)
                return

            # 节点红色,节点的父节点的左右子树存在,且都是红色,但是前面两个条件都过滤了红色这个条件
            if sub_tree.color == RED and sub_tree.parent.right and sub_tree.parent.left:
                sub_tree.left = node
                node.parent = sub_tree
                self.left_overflow(sub_tree.parent)

        else:
            if sub_tree.right:
                self._insert(sub_tree.right, node)
                return

            # 两种情况:节点是黑色,插入在节点的右侧
            if sub_tree.color == BLACK:
                sub_tree.right = node
                node.parent = sub_tree
                return


            # 1种情况,节点红色,节点的父节点的左子树不存在或者黑色
            if sub_tree.color == RED and (not sub_tree.parent.left or sub_tree.parent.left.color == BLACK):
                sub_tree.right = node
                node.parent = sub_tree
                parent_node = sub_tree.parent
                self.left_rotation(parent_node)
                return


            # 1种情况,节点红色,节点的父节点的右子树不存在或者黑色
            if sub_tree.color == RED and (not sub_tree.parent.right or sub_tree.parent.right.color == BLACK):
                sub_tree.right = node
                node.parent = sub_tree
                parent_node = sub_tree.parent
                self.left_rotation(sub_tree)

                self.right_rotation(parent_node)
                return


            # 节点红色,节点的父节点的左右子树存在而且都是红色
            if sub_tree.color == RED and sub_tree.parent.right and sub_tree.parent.left:
                sub_tree.right = node
                node.parent = sub_tree
                self.left_overflow(sub_tree.parent)

    def insert(self, value):
        if self.root is None:
            self.root = ColorNode(None, None, None, value, BLACK)
        else:
            node = ColorNode(None, None, None, value, RED)
            self._insert(self.root, node)

红黑树的输出展示和二叉树一样,就不再赘述了。

总结

以上就是今天要讲的内容,本文从二叉搜索树开始,然后引出红黑树,介绍了红黑树插入的各种情况,最后还介绍了如何输出一个直观的二叉树。删除操作可能在后续篇章给出。

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值