我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。

某些教程不区分普通红黑树和左倾红黑树的区别,直接将左倾红黑树拿来教学,并且称其为红黑树,因为左倾红黑树与普通的红黑树相比,实现起来较为简单,容易教学。在这里,我们区分开左倾红黑树和普通红黑树。

2-32-3-42-32-3-42-3-42-3
2-32-3

一、2-3 树

1.1. 2-3 树介绍

2-3John Edward Hopcroft3阶的B树BBalance

它不是一棵二叉树,是一棵三叉树。具有以下特征:

  1. 内部节点要么有1个数据元素和2个孩子,要么有2个数据元素和3个孩子,叶子节点没有孩子,但有1或2个数据元素。
  2. 所有叶子节点到根节点的长度一致。这个特征保证了完全平衡,非常完美的平衡。
  3. 每个节点的数据元素保持从小到大排序,两个数据元素之间的子树的所有值大小介于两个数据元素之间。
2-3

如图:

如果一个内部节点拥有一个数据元素、两个子节点,则此节点为2节点。如果一个内部节点拥有两个数据元素、三个子节点,则此节点为3节点。

可以说,所有平衡树的核心都在于插入和删除逻辑,我们主要分析这两个操作。

1.2. 2-3 树插入元素

在插入元素时,需要先找到插入的位置,使用二分查找从上自下查找树节点。

2-3
  1. 插入元素到一个2节点,直接插入即可,这样节点变成3节点。
  2. 插入元素到一个3节点,该3节点的父亲是一个2节点,先将节点变成临时的4节点,然后向上分裂调整一次。
  3. 插入元素到一个3节点,该3节点的父亲是一个3节点,先将节点变成临时的4节点,然后向上分裂调整,此时父亲节点变为临时4节点,继续向上分裂调整。

如图(来自维基百科):

核心在于插入3节点后,该节点变为临时4节点,然后进行分裂恢复树的特征。最坏情况为插入节点后,每一次分裂后都导致上一层变为临时4节点,直到树根节点,这样需要不断向上分裂。

临时4节点的分裂,细分有六种情况,如图:

2-3
2-3B树

1.3. 2-3 树删除元素

删除操作就复杂得多了,请耐心阅读理解。

2-32-32-3ABCBC2-3

基于上面的现实,我们来分析删除的不同情况,删除中间节点和叶子节点。

情况1:删除中间节点

删除的是非叶子节点,该节点一定是有两棵或者三棵子树的,那么从子树中找到其最小后继节点,该节点是叶子节点,用该节点替换被删除的非叶子节点,然后再删除这个叶子节点,进入情况2。

如何找到最小后继节点,当有两棵子树时,那么从右子树一直往左下方找,如果有三棵子树,被删除节点在左边,那么从中子树一直往左下方找,否则从右子树一直往左下方找。

情况2:删除叶子节点

2-3
2-3
  1. 重新分布:尝试从兄弟节点那里借值,然后重新调整节点。
  2. 合并:如果兄弟借不到值,合并节点(与父亲的元素),再向上递归处理。

看图说话:

如果被删除的叶子节点有兄弟是3节点,那么从兄弟那里借一个值填补被删除的叶子节点,然后兄弟和父亲重新分布调整位置。下面是重新分布的具体例子:

10080

如果兄弟们都是2节点呢,那么就合并节点:将父亲和兄弟节点合并,如果父亲是2节点,那么父亲就留空了,否则父亲就从3节点变成2节点,下面是合并的两个具体例子:

806090

70

但是,如果合并后,父亲节点变空了,也就是说有中间节点留空要怎么办,那么可以继续递归处理,如图:

中间节点是空的,那么可以继续从兄弟那里借节点或者和父亲合并,直到根节点,如果到达了根节点呢,如图:

递归到了根节点后,如果存在空的根节点,我们可以直接把该空节点删除即可,这时树的高度减少一层。

2-3B树

二、 左倾红黑树

2.1. 左倾红黑树介绍

2-3

其定义为:

  1. 根节点的链接是黑色的。
  2. 红链接均为左链接。
  3. 没有任何一个结点同时和两条红链接相连
  4. 任意一个节点到达叶子节点的所有路径,经过的黑链接数量相同,也就是该树是完美黑色平衡的。比如,某一个节点,它可以到达5个叶子节点,那么这5条路径上的黑链接数量一样。
2-3

2.2. 节点旋转和颜色转换

LLRBTreeLLRBTNode
// 定义颜色
const (
    RED   = true
    BLACK = false
)

// 左倾红黑树
type LLRBTree struct {
    Root *LLRBTNode // 树根节点
}


// 左倾红黑树节点
type LLRBTNode struct {
    Value       int64     // 值
    Times       int64      // 值出现的次数
    Left        *LLRBTNode // 左子树
    Right       *LLRBTNode // 右子树
    Color       bool       // 父亲指向该节点的链接颜色
}

// 新建一棵空树
func NewLLRBTree() *LLRBTree {
    return &LLRBTree{}
}

// 节点的颜色
func IsRed(node *LLRBTNode) bool {
    if node == nil {
        return false
    }
    return node.Color == RED
}
LLRBTNodeValueTimes
ColorIsRed()

在元素添加和实现的过程中,需要做调整操作,有两种旋转操作,对某节点的右链接进行左旋转,或者左链接进行右旋转。

h

代码实现如下:

// 左旋转
func RotateLeft(h *LLRBTNode) *LLRBTNode {
    if h == nil {
        return nil
    }

    // 看图理解
    x := h.Right
    h.Right = x.Left
    x.Left = h
    x.Color = h.Color
    h.Color = RED
    return x
}
h

代码实现如下:

// 右旋转
func RotateRight(h *LLRBTNode) *LLRBTNode {
    if h == nil {
        return nil
    }

    // 看图理解
    x := h.Left
    h.Left = x.Right
    x.Right = h
    x.Color = h.Color
    h.Color = RED
    return x
}

由于左倾红黑树不允许一个节点有两个红链接,所以需要做颜色转换,如图:

代码如下:

// 颜色转换
func ColorChange(h *LLRBTNode) {
    if h == nil {
        return
    }
    h.Color = !h.Color
    h.Left.Color = !h.Left.Color
    h.Right.Color = !h.Right.Color
}

旋转和颜色转换作为局部调整,并不影响全局。

2.3. 添加元素实现

ColorRED

接着判断其父亲是否有两个红链接(如连续的两个左红链接或者左右红色链接),或者有右红色链接,进行颜色变换或旋转操作。

主要有以下这几种情况。

插入元素到2节点,直接让节点变为3节点,不过当右插入时需要左旋使得红色链接在左边,如图:

插入元素到3节点,需要做旋转和颜色转换操作,如图:

也就是说,在一个已经是红色左链接的节点,插入一个新节点的状态变化如下:

根据上述的演示图以及旋转,颜色转换等操作,添加元素的代码为:

// 左倾红黑树添加元素
func (tree *LLRBTree) Add(value int64) {
    // 跟节点开始添加元素,因为可能调整,所以需要将返回的节点赋值回根节点
    tree.Root = tree.Root.Add(value)
    // 根节点的链接永远都是黑色的
    tree.Root.Color = BLACK
}

// 往节点添加元素
func (node *LLRBTNode) Add(value int64) *LLRBTNode {
    // 插入的节点为空,将其链接颜色设置为红色,并返回
    if node == nil {
        return &LLRBTNode{
            Value: value,
            Color: RED,
        }
    }

    // 插入的元素重复
    if value == node.Value {
        node.Times = node.Times + 1
    } else if value > node.Value {
        // 插入的元素比节点值大,往右子树插入
        node.Right = node.Right.Add(value)
    } else {
        // 插入的元素比节点值小,往左子树插入
        node.Left = node.Left.Add(value)
    }

    // 辅助变量
    nowNode := node

    // 右链接为红色,那么进行左旋,确保树是左倾的
    // 这里做完操作后就可以结束了,因为插入操作,新插入的右红链接左旋后,nowNode节点不会出现连续两个红左链接,因为它只有一个左红链接
    if IsRed(nowNode.Right) && !IsRed(nowNode.Left) {
        nowNode = RotateLeft(nowNode)
    } else {
        // 连续两个左链接为红色,那么进行右旋
        if IsRed(nowNode.Left) && IsRed(nowNode.Left.Left) {
            nowNode = RotateRight(nowNode)
        }

        // 旋转后,可能左右链接都为红色,需要变色
        if IsRed(nowNode.Left) && IsRed(nowNode.Right) {
            ColorChange(nowNode)
        }
    }

    return nowNode
}

2.4. 添加元素算法分析

2log(n)n2-32-3log(n)2-32-3
AVL1.44log(n)AVLAVLlog(n)
log(n)

我们可以优化代码,使得在某一层旋转变色后,如果其父层没有连续的左红链接或者不需要变色,那么可以直接退出,不需要逐层判断是否需要旋转和变色。

AVLlog(n)
AVLAVL

在此,我们不再纠结两种平衡树哪种更好,因为代码实现中,两种平衡树都需要自底向上的递归操作,效率差别不大。。

2.5. 删除元素实现

2-3
  1. 情况1:如果删除的是非叶子节点,找到其最小后驱节点,也就是在其右子树中一直向左找,找到的该叶子节点替换被删除的节点,然后删除该叶子节点,变成情况2。
  2. 情况2:如果删除的是叶子节点,如果它是红节点,也就是父亲指向它的链接为红色,那么直接删除即可。否则,我们需要进行调整,使它变为红节点,再删除。
2-3

我们创造两种操作,如果删除的节点在左子树中,可能需要进行红色左移,如果删除的节点在右子树中,可能需要进行红色右移。

我们介绍红色左移的步骤:

hhb,dh2-3

dehdhh2-3

红色左移可以总结为下图(被删除的节点在左子树,且进入的树根h一定为红节点):

代码如下:

// 红色左移
// 节点 h 是红节点,其左儿子和左儿子的左儿子都为黑节点,左移后使得其左儿子或左儿子的左儿子有一个是红色节点
func MoveRedLeft(h *LLRBTNode) *LLRBTNode {
    // 应该确保 isRed(h) && !isRed(h.left) && !isRed(h.left.left)
    ColorChange(h)

    // 右儿子有左红链接
    if IsRed(h.Right.Left) {
        // 对右儿子右旋
        h.Right = RotateRight(h.Right)
        // 再左旋
        h = RotateLeft(h)
        ColorChange(h)
    }

    return h
}
hh

红色右移的步骤类似,如图(被删除的节点在右子树,且进入的树根h一定为红节点):

代码如下:

// 红色右移
// 节点 h 是红节点,其右儿子和右儿子的左儿子都为黑节点,右移后使得其右儿子或右儿子的右儿子有一个是红色节点
func MoveRedRight(h *LLRBTNode) *LLRBTNode {
    // 应该确保 isRed(h) && !isRed(h.right) && !isRed(h.right.left);
    ColorChange(h)

    // 左儿子有左红链接
    if IsRed(h.Left.Left) {
        // 右旋
        h = RotateRight(h)
        // 变色
        ColorChange(h)
    }

    return h
}
h

介绍完两种操作后,我们要明确一下到底是如何删除元素的。

2-32-3
树h树h树h

可以对照着大图,继续阅读下面的左倾红黑树删除元素代码:

// 左倾红黑树删除元素
func (tree *LLRBTree) Delete(value int64) {
    // 当找不到值时直接返回
    if tree.Find(value) == nil {
        return
    }

    if !IsRed(tree.Root.Left) && !IsRed(tree.Root.Right) {
        // 左右子树都是黑节点,那么先将根节点变为红节点,方便后面的红色左移或右移
        tree.Root.Color = RED
    }

    tree.Root = tree.Root.Delete(value)

    // 最后,如果根节点非空,永远都要为黑节点,赋值黑色
    if tree.Root != nil {
        tree.Root.Color = BLACK
    }
}
tree.Find(value)

当根节点的左右子树都为黑节点时,那么先将根节点变为红节点,方便后面的红色左移或右移。

tree.Root = tree.Root.Delete(value)

核心的从子树中删除元素代码如下:

// 对该节点所在的子树删除元素
func (node *LLRBTNode) Delete(value int64) *LLRBTNode {
    // 辅助变量
    nowNode := node
    // 删除的元素比子树根节点小,需要从左子树删除
    if value < nowNode.Value {
        // 因为从左子树删除,所以要判断是否需要红色左移
        if !IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left) {
            // 左儿子和左儿子的左儿子都不是红色节点,那么没法递归下去,先红色左移
            nowNode = MoveRedLeft(nowNode)
        }

        // 现在可以从左子树中删除了
        nowNode.Left = nowNode.Left.Delete(value)
    } else {
        // 删除的元素等于或大于树根节点

        // 左节点为红色,那么需要右旋,方便后面可以红色右移
        if IsRed(nowNode.Left) {
            nowNode = RotateRight(nowNode)
        }

        // 值相等,且没有右孩子节点,那么该节点一定是要被删除的叶子节点,直接删除
        // 为什么呢,反证,它没有右儿子,但有左儿子,因为左倾红黑树的特征,那么左儿子一定是红色,但是前面的语句已经把红色左儿子右旋到右边,不应该出现右儿子为空。
        if value == nowNode.Value && nowNode.Right == nil {
            return nil
        }

        // 因为从右子树删除,所以要判断是否需要红色右移
        if !IsRed(nowNode.Right) && !IsRed(nowNode.Right.Left) {
            // 右儿子和右儿子的左儿子都不是红色节点,那么没法递归下去,先红色右移
            nowNode = MoveRedRight(nowNode)
        }

        // 删除的节点找到了,它是中间节点,需要用最小后驱节点来替换它,然后删除最小后驱节点
        if value == nowNode.Value {
            minNode := nowNode.Right.FindMinValue()
            nowNode.Value = minNode.Value
            nowNode.Times = minNode.Times

            // 删除其最小后驱节点
            nowNode.Right = nowNode.Right.DeleteMin()
        } else {
            // 删除的元素比子树根节点大,需要从右子树删除
            nowNode.Right = nowNode.Right.Delete(value)
        }
    }

    // 最后,删除叶子节点后,需要恢复左倾红黑树特征
    return nowNode.FixUp()
}

这段核心代码十分复杂,会用到红色左移和右移,当删除的元素小于根节点时,我们明白要在左子树中删除,如:

// 删除的元素比子树根节点小,需要从左子树删除
    if value < nowNode.Value {
        // 因为从左子树删除,所以要判断是否需要红色左移
        if !IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left) {
            // 左儿子和左儿子的左儿子都不是红色节点,那么没法递归下去,先红色左移
            nowNode = MoveRedLeft(nowNode)
        }

        // 现在可以从左子树中删除了
        nowNode.Left = nowNode.Left.Delete(value)
    }
nowNode.Left = nowNode.Left.Delete(value)!IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left)

如果删除的值不小于根节点,那么进入以下逻辑(可仔细阅读注释):

// 删除的元素等于或大于树根节点

        // 左节点为红色,那么需要右旋,方便后面可以红色右移
        if IsRed(nowNode.Left) {
            nowNode = RotateRight(nowNode)
        }

        // 值相等,且没有右孩子节点,那么该节点一定是要被删除的叶子节点,直接删除
        // 为什么呢,反证,它没有右儿子,但有左儿子,因为左倾红黑树的特征,那么左儿子一定是红色,但是前面的语句已经把红色左儿子右旋到右边,不应该出现右儿子为空。
        if value == nowNode.Value && nowNode.Right == nil {
            return nil
        }

        // 因为从右子树删除,所以要判断是否需要红色右移
        if !IsRed(nowNode.Right) && !IsRed(nowNode.Right.Left) {
            // 右儿子和右儿子的左儿子都不是红色节点,那么没法递归下去,先红色右移
            nowNode = MoveRedRight(nowNode)
        }

        // 删除的节点找到了,它是中间节点,需要用最小后驱节点来替换它,然后删除最小后驱节点
        if value == nowNode.Value {
            minNode := nowNode.Right.FindMinValue()
            nowNode.Value = minNode.Value
            nowNode.Times = minNode.Times

            // 删除其最小后驱节点
            nowNode.Right = nowNode.Right.DeleteMin()
        } else {
            // 删除的元素比子树根节点大,需要从右子树删除
            nowNode.Right = nowNode.Right.Delete(value)
        }
IsRed(nowNode.Left)nowNode = RotateRight(nowNode)
value == nowNode.Value && nowNode.Right == nil
!IsRed(nowNode.Right) && !IsRed(nowNode.Right.Left)
value == nowNode.Value
minNode := nowNode.Right.FindMinValue()nowNode.Right = nowNode.Right.DeleteMin()
// 对该节点所在的子树删除最小元素
func (node *LLRBTNode) DeleteMin() *LLRBTNode {
    // 辅助变量
    nowNode := node

    // 没有左子树,那么删除它自己
    if nowNode.Left == nil {
        return nil
    }

    // 判断是否需要红色左移,因为最小元素在左子树中
    if !IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left) {
        nowNode = MoveRedLeft(nowNode)
    }

    // 递归从左子树删除
    nowNode.Left = nowNode.Left.DeleteMin()

    // 修复左倾红黑树特征
    return nowNode.FixUp()
}
nowNode.FixUp()
// 修复左倾红黑树特征
func (node *LLRBTNode) FixUp() *LLRBTNode {
    // 辅助变量
    nowNode := node

    // 红链接在右边,左旋恢复,让红链接只出现在左边
    if IsRed(nowNode.Right) {
        nowNode = RotateLeft(nowNode)
    }

    // 连续两个左链接为红色,那么进行右旋
    if IsRed(nowNode.Left) && IsRed(nowNode.Left.Left) {
        nowNode = RotateRight(nowNode)
    }

    // 旋转后,可能左右链接都为红色,需要变色
    if IsRed(nowNode.Left) && IsRed(nowNode.Right) {
        ColorChange(nowNode)
    }

    return nowNode
}

如果不是删除内部节点,依然是从右子树继续递归:

// 删除的元素比子树根节点大,需要从右子树删除
    nowNode.Right = nowNode.Right.Delete(value)
FixUp()

删除操作很难理解,可以多多思考,红色左移和右移不断地递归都是为了确保删除叶子节点时,其是一个3节点。

2-3树

完整代码见最下面。

2.6. 删除元素算法分析

log(n)

2.7. 查找元素等实现

查找最小值,最大值,或者某个值,代码如下:

// 找出最小值的节点
func (tree *LLRBTree) FindMinValue() *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.FindMinValue()
}

func (node *LLRBTNode) FindMinValue() *LLRBTNode {
    // 左子树为空,表面已经是最左的节点了,该值就是最小值
    if node.Left == nil {
        return node
    }

    // 一直左子树递归
    return node.Left.FindMinValue()
}

// 找出最大值的节点
func (tree *LLRBTree) FindMaxValue() *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.FindMaxValue()
}

func (node *LLRBTNode) FindMaxValue() *LLRBTNode {
    // 右子树为空,表面已经是最右的节点了,该值就是最大值
    if node.Right == nil {
        return node
    }

    // 一直右子树递归
    return node.Right.FindMaxValue()
}

// 查找指定节点
func (tree *LLRBTree) Find(value int64) *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.Find(value)
}

func (node *LLRBTNode) Find(value int64) *LLRBTNode {
    if value == node.Value {
        // 如果该节点刚刚等于该值,那么返回该节点
        return node
    } else if value < node.Value {
        // 如果查找的值小于节点值,从节点的左子树开始找
        if node.Left == nil {
            // 左子树为空,表示找不到该值了,返回nil
            return nil
        }
        return node.Left.Find(value)
    } else {
        // 如果查找的值大于节点值,从节点的右子树开始找
        if node.Right == nil {
            // 右子树为空,表示找不到该值了,返回nil
            return nil
        }
        return node.Right.Find(value)
    }
}

// 中序遍历
func (tree *LLRBTree) MidOrder() {
    tree.Root.MidOrder()
}

func (node *LLRBTNode) MidOrder() {
    if node == nil {
        return
    }

    // 先打印左子树
    node.Left.MidOrder()

    // 按照次数打印根节点
    for i := 0; i <= int(node.Times); i++ {
        fmt.Println(node.Value)
    }

    // 打印右子树
    node.Right.MidOrder()
}

查找操作逻辑与通用的二叉查找树一样,并无区别。

2.8. 验证是否是一棵左倾红黑树

如何确保我们的代码实现的就是一棵左倾红黑树呢,可以进行验证:

// 验证是不是棵左倾红黑树
func (tree *LLRBTree) IsLLRBTree() bool {
    if tree == nil || tree.Root == nil {
        return true
    }

    // 判断树是否是一棵二分查找树
    if !tree.Root.IsBST() {
        return false
    }

    // 判断树是否遵循2-3树,也就是红链接只能在左边,不能连续有两个红链接
    if !tree.Root.Is23() {
        return false
    }

    // 判断树是否平衡,也就是任意一个节点到叶子节点,经过的黑色链接数量相同
    // 先计算根节点到最左边叶子节点的黑链接数量
    blackNum := 0
    x := tree.Root
    for x != nil {
        if !IsRed(x) { // 是黑色链接
            blackNum = blackNum + 1
        }
        x = x.Left
    }

    if !tree.Root.IsBalanced(blackNum) {
        return false
    }
    return true
}

// 节点所在的子树是否是一棵二分查找树
func (node *LLRBTNode) IsBST() bool {
    if node == nil {
        return true
    }

    // 左子树非空,那么根节点必须大于左儿子节点
    if node.Left != nil {
        if node.Value > node.Left.Value {
        } else {
            fmt.Printf("father:%#v,lchild:%#v,rchild:%#v\n", node, node.Left, node.Right)
            return false
        }
    }

    // 右子树非空,那么根节点必须小于右儿子节点
    if node.Right != nil {
        if node.Value < node.Right.Value {
        } else {
            fmt.Printf("father:%#v,lchild:%#v,rchild:%#v\n", node, node.Left, node.Right)
            return false
        }
    }

    // 左子树也要判断是否是平衡查找树
    if !node.Left.IsBST() {
        return false
    }

    // 右子树也要判断是否是平衡查找树
    if !node.Right.IsBST() {
        return false
    }

    return true
}

// 节点所在的子树是否遵循2-3树
func (node *LLRBTNode) Is23() bool {
    if node == nil {
        return true
    }

    // 不允许右倾红链接
    if IsRed(node.Right) {
        fmt.Printf("father:%#v,rchild:%#v\n", node, node.Right)
        return false
    }

    // 不允许连续两个左红链接
    if IsRed(node) && IsRed(node.Left) {
        fmt.Printf("father:%#v,lchild:%#v\n", node, node.Left)
        return false
    }

    // 左子树也要判断是否遵循2-3树
    if !node.Left.Is23() {
        return false
    }

    // 右子树也要判断是否是遵循2-3树
    if !node.Right.Is23() {
        return false
    }

    return true
}

// 节点所在的子树是否平衡,是否有 blackNum 个黑链接
func (node *LLRBTNode) IsBalanced(blackNum int) bool {
    if node == nil {
        return blackNum == 0
    }

    if !IsRed(node) {
        blackNum = blackNum - 1
    }

    if !node.Left.IsBalanced(blackNum) {
        fmt.Println("node.Left to leaf black link is not ", blackNum)
        return false
    }

    if !node.Right.IsBalanced(blackNum) {
        fmt.Println("node.Right to leaf black link is not ", blackNum)
        return false
    }

    return true
}

运行请看完整代码。

2.9. 完整程序

package main

import "fmt"

// 左倾红黑树实现
// Left-leaning red-black tree

// 定义颜色
const (
    RED   = true
    BLACK = false
)

// 左倾红黑树
type LLRBTree struct {
    Root *LLRBTNode // 树根节点
}

// 新建一棵空树
func NewLLRBTree() *LLRBTree {
    return &LLRBTree{}
}

// 左倾红黑树节点
type LLRBTNode struct {
    Value int64      // 值
    Times int64      // 值出现的次数
    Left  *LLRBTNode // 左子树
    Right *LLRBTNode // 右子树
    Color bool       // 父亲指向该节点的链接颜色
}

// 节点的颜色
func IsRed(node *LLRBTNode) bool {
    if node == nil {
        return false
    }
    return node.Color == RED
}

// 左旋转
func RotateLeft(h *LLRBTNode) *LLRBTNode {
    if h == nil {
        return nil
    }

    // 看图理解
    x := h.Right
    h.Right = x.Left
    x.Left = h
    x.Color = h.Color
    h.Color = RED
    return x
}

// 右旋转
func RotateRight(h *LLRBTNode) *LLRBTNode {
    if h == nil {
        return nil
    }

    // 看图理解
    x := h.Left
    h.Left = x.Right
    x.Right = h
    x.Color = h.Color
    h.Color = RED
    return x
}

// 红色左移
// 节点 h 是红节点,其左儿子和左儿子的左儿子都为黑节点,左移后使得其左儿子或左儿子的左儿子有一个是红色节点
func MoveRedLeft(h *LLRBTNode) *LLRBTNode {
    // 应该确保 isRed(h) && !isRed(h.left) && !isRed(h.left.left)
    ColorChange(h)

    // 右儿子有左红链接
    if IsRed(h.Right.Left) {
        // 对右儿子右旋
        h.Right = RotateRight(h.Right)
        // 再左旋
        h = RotateLeft(h)
        ColorChange(h)
    }

    return h
}

// 红色右移
// 节点 h 是红节点,其右儿子和右儿子的左儿子都为黑节点,右移后使得其右儿子或右儿子的右儿子有一个是红色节点
func MoveRedRight(h *LLRBTNode) *LLRBTNode {
    // 应该确保 isRed(h) && !isRed(h.right) && !isRed(h.right.left);
    ColorChange(h)

    // 左儿子有左红链接
    if IsRed(h.Left.Left) {
        // 右旋
        h = RotateRight(h)
        // 变色
        ColorChange(h)
    }

    return h
}

// 颜色变换
func ColorChange(h *LLRBTNode) {
    if h == nil {
        return
    }
    h.Color = !h.Color
    h.Left.Color = !h.Left.Color
    h.Right.Color = !h.Right.Color
}

// 左倾红黑树添加元素
func (tree *LLRBTree) Add(value int64) {
    // 跟节点开始添加元素,因为可能调整,所以需要将返回的节点赋值回根节点
    tree.Root = tree.Root.Add(value)
    // 根节点的链接永远都是黑色的
    tree.Root.Color = BLACK
}

// 往节点添加元素
func (node *LLRBTNode) Add(value int64) *LLRBTNode {
    // 插入的节点为空,将其链接颜色设置为红色,并返回
    if node == nil {
        return &LLRBTNode{
            Value: value,
            Color: RED,
        }
    }

    // 插入的元素重复
    if value == node.Value {
        node.Times = node.Times + 1
    } else if value > node.Value {
        // 插入的元素比节点值大,往右子树插入
        node.Right = node.Right.Add(value)
    } else {
        // 插入的元素比节点值小,往左子树插入
        node.Left = node.Left.Add(value)
    }

    // 辅助变量
    nowNode := node

    // 右链接为红色,那么进行左旋,确保树是左倾的
    // 这里做完操作后就可以结束了,因为插入操作,新插入的右红链接左旋后,nowNode节点不会出现连续两个红左链接,因为它只有一个左红链接
    if IsRed(nowNode.Right) && !IsRed(nowNode.Left) {
        nowNode = RotateLeft(nowNode)
    } else {
        // 连续两个左链接为红色,那么进行右旋
        if IsRed(nowNode.Left) && IsRed(nowNode.Left.Left) {
            nowNode = RotateRight(nowNode)
        }

        // 旋转后,可能左右链接都为红色,需要变色
        if IsRed(nowNode.Left) && IsRed(nowNode.Right) {
            ColorChange(nowNode)
        }
    }

    return nowNode
}

// 找出最小值的节点
func (tree *LLRBTree) FindMinValue() *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.FindMinValue()
}

func (node *LLRBTNode) FindMinValue() *LLRBTNode {
    // 左子树为空,表面已经是最左的节点了,该值就是最小值
    if node.Left == nil {
        return node
    }

    // 一直左子树递归
    return node.Left.FindMinValue()
}

// 找出最大值的节点
func (tree *LLRBTree) FindMaxValue() *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.FindMaxValue()
}

func (node *LLRBTNode) FindMaxValue() *LLRBTNode {
    // 右子树为空,表面已经是最右的节点了,该值就是最大值
    if node.Right == nil {
        return node
    }

    // 一直右子树递归
    return node.Right.FindMaxValue()
}

// 查找指定节点
func (tree *LLRBTree) Find(value int64) *LLRBTNode {
    if tree.Root == nil {
        // 如果是空树,返回空
        return nil
    }

    return tree.Root.Find(value)
}

func (node *LLRBTNode) Find(value int64) *LLRBTNode {
    if value == node.Value {
        // 如果该节点刚刚等于该值,那么返回该节点
        return node
    } else if value < node.Value {
        // 如果查找的值小于节点值,从节点的左子树开始找
        if node.Left == nil {
            // 左子树为空,表示找不到该值了,返回nil
            return nil
        }
        return node.Left.Find(value)
    } else {
        // 如果查找的值大于节点值,从节点的右子树开始找
        if node.Right == nil {
            // 右子树为空,表示找不到该值了,返回nil
            return nil
        }
        return node.Right.Find(value)
    }
}

// 中序遍历
func (tree *LLRBTree) MidOrder() {
    tree.Root.MidOrder()
}

func (node *LLRBTNode) MidOrder() {
    if node == nil {
        return
    }

    // 先打印左子树
    node.Left.MidOrder()

    // 按照次数打印根节点
    for i := 0; i <= int(node.Times); i++ {
        fmt.Println(node.Value)
    }

    // 打印右子树
    node.Right.MidOrder()
}

// 修复左倾红黑树特征
func (node *LLRBTNode) FixUp() *LLRBTNode {
    // 辅助变量
    nowNode := node

    // 红链接在右边,左旋恢复,让红链接只出现在左边
    if IsRed(nowNode.Right) {
        nowNode = RotateLeft(nowNode)
    }

    // 连续两个左链接为红色,那么进行右旋
    if IsRed(nowNode.Left) && IsRed(nowNode.Left.Left) {
        nowNode = RotateRight(nowNode)
    }

    // 旋转后,可能左右链接都为红色,需要变色
    if IsRed(nowNode.Left) && IsRed(nowNode.Right) {
        ColorChange(nowNode)
    }

    return nowNode
}

// 对该节点所在的子树删除最小元素
func (node *LLRBTNode) DeleteMin() *LLRBTNode {
    // 辅助变量
    nowNode := node

    // 没有左子树,那么删除它自己
    if nowNode.Left == nil {
        return nil
    }

    // 判断是否需要红色左移,因为最小元素在左子树中
    if !IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left) {
        nowNode = MoveRedLeft(nowNode)
    }

    // 递归从左子树删除
    nowNode.Left = nowNode.Left.DeleteMin()

    // 修复左倾红黑树特征
    return nowNode.FixUp()
}

// 左倾红黑树删除元素
func (tree *LLRBTree) Delete(value int64) {
    // 当找不到值时直接返回
    if tree.Find(value) == nil {
        return
    }

    if !IsRed(tree.Root.Left) && !IsRed(tree.Root.Right) {
        // 左右子树都是黑节点,那么先将根节点变为红节点,方便后面的红色左移或右移
        tree.Root.Color = RED
    }

    tree.Root = tree.Root.Delete(value)

    // 最后,如果根节点非空,永远都要为黑节点,赋值黑色
    if tree.Root != nil {
        tree.Root.Color = BLACK
    }
}

// 对该节点所在的子树删除元素
func (node *LLRBTNode) Delete(value int64) *LLRBTNode {
    // 辅助变量
    nowNode := node
    // 删除的元素比子树根节点小,需要从左子树删除
    if value < nowNode.Value {
        // 因为从左子树删除,所以要判断是否需要红色左移
        if !IsRed(nowNode.Left) && !IsRed(nowNode.Left.Left) {
            // 左儿子和左儿子的左儿子都不是红色节点,那么没法递归下去,先红色左移
            nowNode = MoveRedLeft(nowNode)
        }

        // 现在可以从左子树中删除了
        nowNode.Left = nowNode.Left.Delete(value)
    } else {
        // 删除的元素等于或大于树根节点

        // 左节点为红色,那么需要右旋,方便后面可以红色右移
        if IsRed(nowNode.Left) {
            nowNode = RotateRight(nowNode)
        }

        // 值相等,且没有右孩子节点,那么该节点一定是要被删除的叶子节点,直接删除
        // 为什么呢,反证,它没有右儿子,但有左儿子,因为左倾红黑树的特征,那么左儿子一定是红色,但是前面的语句已经把红色左儿子右旋到右边,不应该出现右儿子为空。
        if value == nowNode.Value && nowNode.Right == nil {
            return nil
        }

        // 因为从右子树删除,所以要判断是否需要红色右移
        if !IsRed(nowNode.Right) && !IsRed(nowNode.Right.Left) {
            // 右儿子和右儿子的左儿子都不是红色节点,那么没法递归下去,先红色右移
            nowNode = MoveRedRight(nowNode)
        }

        // 删除的节点找到了,它是中间节点,需要用最小后驱节点来替换它,然后删除最小后驱节点
        if value == nowNode.Value {
            minNode := nowNode.Right.FindMinValue()
            nowNode.Value = minNode.Value
            nowNode.Times = minNode.Times

            // 删除其最小后驱节点
            nowNode.Right = nowNode.Right.DeleteMin()
        } else {
            // 删除的元素比子树根节点大,需要从右子树删除
            nowNode.Right = nowNode.Right.Delete(value)
        }
    }

    // 最后,删除叶子节点后,需要恢复左倾红黑树特征
    return nowNode.FixUp()
}

// 验证是不是棵左倾红黑树
func (tree *LLRBTree) IsLLRBTree() bool {
    if tree == nil || tree.Root == nil {
        return true
    }

    // 判断树是否是一棵二分查找树
    if !tree.Root.IsBST() {
        return false
    }

    // 判断树是否遵循2-3树,也就是红链接只能在左边,不能连续有两个红链接
    if !tree.Root.Is23() {
        return false
    }

    // 判断树是否平衡,也就是任意一个节点到叶子节点,经过的黑色链接数量相同
    // 先计算根节点到最左边叶子节点的黑链接数量
    blackNum := 0
    x := tree.Root
    for x != nil {
        if !IsRed(x) { // 是黑色链接
            blackNum = blackNum + 1
        }
        x = x.Left
    }

    if !tree.Root.IsBalanced(blackNum) {
        return false
    }
    return true
}

// 节点所在的子树是否是一棵二分查找树
func (node *LLRBTNode) IsBST() bool {
    if node == nil {
        return true
    }

    // 左子树非空,那么根节点必须大于左儿子节点
    if node.Left != nil {
        if node.Value > node.Left.Value {
        } else {
            fmt.Printf("father:%#v,lchild:%#v,rchild:%#v\n", node, node.Left, node.Right)
            return false
        }
    }

    // 右子树非空,那么根节点必须小于右儿子节点
    if node.Right != nil {
        if node.Value < node.Right.Value {
        } else {
            fmt.Printf("father:%#v,lchild:%#v,rchild:%#v\n", node, node.Left, node.Right)
            return false
        }
    }

    // 左子树也要判断是否是平衡查找树
    if !node.Left.IsBST() {
        return false
    }

    // 右子树也要判断是否是平衡查找树
    if !node.Right.IsBST() {
        return false
    }

    return true
}

// 节点所在的子树是否遵循2-3树
func (node *LLRBTNode) Is23() bool {
    if node == nil {
        return true
    }

    // 不允许右倾红链接
    if IsRed(node.Right) {
        fmt.Printf("father:%#v,rchild:%#v\n", node, node.Right)
        return false
    }

    // 不允许连续两个左红链接
    if IsRed(node) && IsRed(node.Left) {
        fmt.Printf("father:%#v,lchild:%#v\n", node, node.Left)
        return false
    }

    // 左子树也要判断是否遵循2-3树
    if !node.Left.Is23() {
        return false
    }

    // 右子树也要判断是否是遵循2-3树
    if !node.Right.Is23() {
        return false
    }

    return true
}

// 节点所在的子树是否平衡,是否有 blackNum 个黑链接
func (node *LLRBTNode) IsBalanced(blackNum int) bool {
    if node == nil {
        return blackNum == 0
    }

    if !IsRed(node) {
        blackNum = blackNum - 1
    }

    if !node.Left.IsBalanced(blackNum) {
        fmt.Println("node.Left to leaf black link is not ", blackNum)
        return false
    }

    if !node.Right.IsBalanced(blackNum) {
        fmt.Println("node.Right to leaf black link is not ", blackNum)
        return false
    }

    return true
}

func main() {
    tree := NewLLRBTree()
    values := []int64{2, 3, 7, 10, 10, 10, 10, 23, 9, 102, 109, 111, 112, 113}
    for _, v := range values {
        tree.Add(v)
    }

    // 找到最大值或最小值的节点
    fmt.Println("find min value:", tree.FindMinValue())
    fmt.Println("find max value:", tree.FindMaxValue())

    // 查找不存在的99
    node := tree.Find(99)
    if node != nil {
        fmt.Println("find it 99!")
    } else {
        fmt.Println("not find it 99!")
    }

    // 查找存在的9
    node = tree.Find(9)
    if node != nil {
        fmt.Println("find it 9!")
    } else {
        fmt.Println("not find it 9!")
    }

    tree.MidOrder()

    // 删除存在的9后,再查找9
    tree.Delete(9)
    tree.Delete(10)
    tree.Delete(2)
    tree.Delete(3)
    tree.Add(4)
    tree.Add(3)
    tree.Add(10)
    tree.Delete(111)
    node = tree.Find(9)
    if node != nil {
        fmt.Println("find it 9!")
    } else {
        fmt.Println("not find it 9!")
    }

    if tree.IsLLRBTree() {
        fmt.Println("is a llrb tree")
    } else {
        fmt.Println("is not llrb tree")
    }
}

运行:

find min value: &{2 0 <nil> <nil> false}
find max value: &{113 0 0xc0000941e0 <nil> false}
not find it 99!
find it 9!
2
3
7
9
10
10
10
10
23
102
109
111
112
113
not find it 9!
is a llrb tree

PS:我们的程序是递归程序,如果改写为非递归形式,效率和性能会更好,在此就不实现了,理解左倾红黑树添加和删除的总体思路即可。

三、应用场景

valuekey:value

Java 语言基础类库中的 HashMap,TreeSet,TreeMap 都有使用到,C++ 语言的 STL 标准模板库中,map 和 set 类也有使用到。很多中间件也有使用到,比如 Nginx,但 Golang 语言标准库并没有它。

最后,上述应用场景使用的红黑树都是普通红黑树,并不是本文所介绍的左倾红黑树。

左倾红黑树作为红黑树的一个变种,只是被设计为更容易理解而已,变种只能是变种,工程上使用得更多的还是普通红黑树,所以我们仍然需要学习普通的红黑树,请看下一章节。

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。