红黑树(Red Black Tree)基本性质 + 建树

定义


红黑树:一种特殊的二叉搜索树
二叉搜索树:一种树的类型,每个节点最多有两个子节点,其中其左节点一定小于当前节点,右节点一定大于当前节点

二叉树的缺点:如果给定的初始序列顺序不好,可能会建出类似于链表的结构,对搜索速度全无助益

红黑树(Red Black Tree)基本性质 + 建树_第1张图片
红黑树的目的:构建一棵趋于平衡的二叉搜索树,杜绝bad case的出现情况

性质


红黑树树节点的组成

  • 左孩子 left (树节点)
  • 右孩子 right (树节点)
  • 父节点 parent (树节点)
  • 颜色 color (红/黑)
  • 值 value (任意)

红黑树的特点

  • 没有一条路径会比其他路径长出2倍

合法红黑树的性质

  • 每个节点都是红色或者黑色的
  • 根节点是黑色
  • 叶节点*是黑色
  • 如果一个节点是红色的,他的两个孩子都应该是黑色
  • 对每个结点,从该节点到其所有后代的叶子节点的简单路径上,包含相同数目的黑色节点

其中,最后一个性质确保了红黑树的相对“平衡”
*为了便于处理边界条件,红黑树中没有节点的孩子会直接指向None,通过定义一个通用哨兵NIL来代指None

NIL's param settings
	parent = None
	color = BLACK
	left = None
	right = None
	value = None

红黑树(Red Black Tree)基本性质 + 建树_第2张图片
右图为红黑树的包含哨兵(nil)状态,为了方便,后图都将采用左图的方式绘制

基本操作(旋转)


旋转操作是红黑树的基本操作,旋转的示意图如下
红黑树(Red Black Tree)基本性质 + 建树_第3张图片

旋转需要满足如下要求:

  • 左旋:需要左旋的树节点,其右孩子不为NIL
  • 右旋:需要右旋的树节点,其左孩子不为NIL

至于其他节点,由于存在哨兵NIL,变得易于处理,以左旋为例:

  • 事先定义:旋转的节点为N,它的右孩子为R,R的左孩子为RL(是NIL也无所谓),其他节点不需要关注。
  • S1:断开N和R
  • S2:N的右孩子为RL
  • S3:R的左孩子为N

下面给出左旋和右旋的python代码

    def _left_rotate(self, node):
        if node.right == self.nil:
            print("can't left rotate")
            return

        right = node.right
        parent = node.parent
        isLeft = parent.left == node

        if self.root == node:
            self.root = right

        if isLeft:
            parent.left = right
        else:
            parent.right = right
        right.parent = parent

        node.right = right.left
        right.left.parent = node

        right.left = node
        node.parent = right

    def _right_rotate(self, node):
        if node.left == self.nil:
            print("can't right rotate")
            return

        left = node.left
        parent = node.parent
        isLeft = parent.left == node

        if self.root == node:
            self.root = left

        if isLeft:
            parent.left = left
        else:
            parent.right = left
        left.parent = parent

        node.left = left.right
        left.right.parent = node

        left.right = node
        node.parent = left

建树


说是建树,其实就是树节点的插入操作
目标:让插入的节点同时满足二叉搜索树的要求和合法红黑树的性质

第一个目标很容易达成,就是找到该节点应该插入的位置然后把它塞进去

    def insert(self, value):
    	# Node(value, color, parent, left, right)
        insert_node = Node(value, NC.RED, None, self.nil, self.nil)
        curr_node = self.root

        while True:
            if curr_node.right == self.nil and value >= curr_node.value:
                curr_node.right = insert_node
                break
            if curr_node.left == self.nil and value <= curr_node.value:
                curr_node.left = insert_node
                break
            curr_node = curr_node.right if value >= curr_node.value else curr_node.left

        insert_node.parent = curr_node
        # self._adjust(insert_node)

接下来看看第二个目标,回顾一下之前的5条性质

  • 每个节点都是红色或者黑色的
  • 根节点是黑色
  • 叶节点是黑色
  • 如果一个节点是红色的,他的两个孩子都应该是黑色
  • 对每个结点,从该节点到其所有后代的叶子节点的简单路径上,包含相同数目的黑色节点

对应下来

  • 可以满足
  • 可以通过最后 root.color = NC.BLACK 完成
  • 已经在哨兵NIL中设置完成
  • 不一定
  • 通过给新插入的节点赋值为红色完成(不会增加黑色节点,但会collide第四条性质)

所以我们需要通过调整树结构和树节点颜色,来确保性质4的有效性,接下来将针对不同的树结构完成对合法红黑树的调整

在分析不同case之前,先给出一些定义

  • U:插入节点的叔节点 Uncle
  • P:插入节点的父节点 Parent
  • N:插入的节点 Node
  • G:插入节点的祖父节点 Grandparent

case1:根节点

这种情况比较简单,将该节点的颜色设置为黑色即可

case2:parent.color = BLACK

不需要下一步的处理了,如果父节点是黑色的,而插入的子节点默认为红色,不会影响性质4的有效性,无需调整树结构

所以!接下来的所有case当中,都会默认parent.color=RED!

case3:uncle.color = RED

直观来说,case3一定满足下图中4种情况中的一种(插入N前的树一定是valid的红黑树)

红黑树(Red Black Tree)基本性质 + 建树_第4张图片

这种情况下,我们改变P、G、U的颜色,从而在维持黑高(性质5)的情况下,让红色节点和红色节点不相接(性质4)

红黑树(Red Black Tree)基本性质 + 建树_第5张图片

但改变了G的颜色,使之成为RED节点之后,可能在G和G.parent之间存在性质4的冲突,所以将G看作新插入的节点N,重新判定当前情况

case4:uncle.color = BLACK

这种case下一定是下面4种状态之一,且一定出现在循环当中(因为P、G、U构成的子树在N插入前就不满足valid红黑树了)
也就是N的左右孩子一定非NIL,且有黑色节点

红黑树(Red Black Tree)基本性质 + 建树_第6张图片
不难发现,对于右边两种情况来说,只需要对G做一次旋转就可以让这棵子树平衡;而左边的两种情况又可以通过旋转P达到和左边一样的结构

因此,我们对case4再分为2种情况,一种为三角结构(左侧2图),一种为线性结构(右侧2图)

case4.1: triangle structure

目标:变为线性结构,可以和右侧两图统一处理
解决方式:

  • 图1:左旋P,交换P和N
  • 图2:右旋P,交换P和N

case4.2: line structure

以第三张图为例,给出了处理方案

在这里插入图片描述

在处理后满足下一个处理的Node颜色为BLACK,不需要再次判定其与其parent的性质4合法性,因此可以结束树结构的调整

以上4种case的python代码如下

    def _adjust(self, node):
        # case1: node is root
        if node == self.root:
            node.color = NC.BLACK
            return

        # if node.parent is BLACK, the RBT is already valid
        # so grandparent is ALWAYS BLACK if continue processing
        if node.parent.color == NC.BLACK:
            return

        # pick up uncle, parent and grandparent(if parent is red, there must be grandparent)
        uncle, parent, grandparent, subtree_type = self._get_relative_nodes_and_subtree_type(node)

        # case2: uncle is red (implies grandparent is BLACK, parent is RED)
        if uncle.color == NC.RED:
            # change the color of uncle, parent and grand
            uncle.color = NC.BLACK
            parent.color = NC.BLACK
            grandparent.color = NC.RED
            return self._adjust(grandparent)

        # case3: uncle is black (this case includes "without uncle" cause nil node is black)
        # case3.1: deal with triangle tree type first
        if subtree_type == SBT.TRI_R:
            self._right_rotate(parent)
            subtree_type = SBT.LIN_R
            node, parent = parent, node
        if subtree_type == SBT.TRI_L:
            self._left_rotate(parent)
            subtree_type = SBT.LIN_L
            node, parent = parent, node

        # case3.2: deal with line tree type
        if subtree_type == SBT.LIN_R:
            self._left_rotate(grandparent)
        if subtree_type == SBT.LIN_L:
            self._right_rotate(grandparent)
        parent.color = NC.BLACK
        grandparent.color = NC.RED

    @staticmethod
    def _get_relative_nodes_and_subtree_type(node):
        parent = node.parent
        grandparent = parent.parent
        if parent.left == node:
            if grandparent.left == parent:
                subtree_type = SBT.LIN_L
                uncle = grandparent.right
            else:
                subtree_type = SBT.TRI_R
                uncle = grandparent.left
        else:
            if grandparent.left == parent:
                subtree_type = SBT.TRI_L
                uncle = grandparent.right
            else:
                subtree_type = SBT.LIN_R
                uncle = grandparent.left
        return uncle, parent, grandparent, subtree_type

你可能感兴趣的:(算法整理,算法,数据结构,红黑树)