前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >红黑树详解

红黑树详解

原创
作者头像
派大星在吗
发布2021-12-06 13:55:17
5020
发布2021-12-06 13:55:17
举报
文章被收录于专栏:我的技术专刊

二叉查找树

二叉查找树是最简单的实现,规定左子节点一定小于右子节点。

插入情况只需要对当前节点比大小,不断的递归直到遇到空节点插入。

查询和插入相同,不断比大小即可,在这一点上红黑树和二叉查找树是一致的。

通过这个性质可以轻松写出找到最大值、最小值,删除最大值、最小值的代码,就是一直遍历左或者右节点就能找到最值

代码语言:txt
复制
//最小值
代码语言:txt
复制
func min(x *node) *node {
代码语言:txt
复制
    if x.left == nil {
代码语言:txt
复制
        return x
代码语言:txt
复制
    }
代码语言:txt
复制
    return Min(x.left)
代码语言:txt
复制
}
代码语言:txt
复制
//最大值
代码语言:txt
复制
func max(x *node) *node {
代码语言:txt
复制
    if x.right == nil {
代码语言:txt
复制
        return x
代码语言:txt
复制
    }
代码语言:txt
复制
    return Max(x.right)
代码语言:txt
复制
}
代码语言:txt
复制
//删除最小值
代码语言:txt
复制
func deleteMin(x *node) *node {
代码语言:txt
复制
    if x.left == nil {
代码语言:txt
复制
        return nil
代码语言:txt
复制
    }
代码语言:txt
复制
    x.left = deleteMin(x.left)
代码语言:txt
复制
    x.n = size(x.left) + size(x.right) + 1
代码语言:txt
复制
}
代码语言:txt
复制
//删除最大值只需要改变代码中left为right

删除最大值或最小值就是找到那个节点然后断开,再把那个节点的子节点连接上它的父节点,因为最小值那个节点的子节点只可能有一个

稍微复杂一点的就是删除操作,对于任意一个节点,删除意味着断开这个点的所有连接,如果这个点有子节点,那么需要另外一个点来替代它,这个点添加进去后要使得二叉查找树性质不变。根据性质可以找到这个点就是被删除节点的右子节点的最小值

如图,删除4这个点,需要用它的右子节点6后的最小节点,也就是5来填充4这个位置,代码如下:

代码语言:txt
复制
 public Node delete(Node x, Key key) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return null;
代码语言:txt
复制
        }
代码语言:txt
复制
        int cmp = key.compareTo(x.key);
代码语言:txt
复制
        if (cmp > 0) {
代码语言:txt
复制
            x.right = delete(x.right, key);
代码语言:txt
复制
        } else if (cmp < 0) {
代码语言:txt
复制
            x.left = delete(x.left, key);
代码语言:txt
复制
        } else {
代码语言:txt
复制
            Node t = x;
代码语言:txt
复制
            x = min(t.right);
代码语言:txt
复制
            x.right = deleteMin(t.right);
代码语言:txt
复制
            x.left = t.left;
代码语言:txt
复制
        }
代码语言:txt
复制
        x.n = size(x.left) + size(x.right) + 1;
代码语言:txt
复制
        return x;
代码语言:txt
复制
    }

不断递归找到需要删除的点,然后断开该点的前后连接,删除它右子节点的最小节点来替代原来的位置

删除节点有一个核心思路就是要找到被删除节点的右子节点后的最小节点,在二叉查找树可以很容易地把点移动,但是对于红黑树来说,就不能简单的移动某个点,但是都需要经过这个过程。

二叉查找树的查询速度看起来和二叉查找相似,但是插入和删除不需要像数组那样移动元素,听起来效率挺高,但是存在最坏情况是:插入一些已经排序过的节点,比如0-100,在每次插入过程都插入右节点,这样就查找树就退化成普通链表。

2-3树

普通二叉查找树无法适应最坏情况,如果有一种树能够适应各种不同的数据情况,让运行情况都在对数级别,就能够解决二叉树查找树不稳定的缺点。

为了解决这种情况,保证树的平衡性,适当地改造一下二叉树,普通的二叉树只保存一个值和两个左右节点,现在将树改造成一个节点能够保存2个值,而有三条指针指向其他节点,形成左-

中-右节点,这样的节点称为3-节点

如图,一棵树存在以上两种节点,3-节点中间的节点表示:左值<中节点<右值

对于这样的节点,在插入节点的时候需要一些变换才能保证树的平衡性

情况1:插入的节点是2-节点

直接将2-节点变成3-节点

情况2:插入的节点是个3-节点

如图,重新构造3-节点,浮动到父节点

情况3:父节点是3-节点,对父节点插入

父节点是3-节点,对其进行插入,会使得父节点分裂成3个2-节点

++上面只是展示了几种情况,实际上如果在树中间插入一个节点,在节点变换的时候还要考虑子节点的情况,添加一个新的3-节点,需要将原有的2-节点进行复制,维护两种不同的节点,增加了额外的开销,实在是有些得不偿失++

红黑树

对于2-3树需要新增3-节点的数据结构,虽然有缺点,但是理解实现起来并不复杂,完全可以用标准二叉树的结构,只需要增加额外的信息就可以来表示3-节点。

在内部使用一条红色的节点来表示3-节点,a这个节点的为红节点

代码语言:txt
复制
//节点表示代码 go语言实现,后面方一个java版本
代码语言:txt
复制
const (
代码语言:txt
复制
    BLACK = false
代码语言:txt
复制
    RED   = true
代码语言:txt
复制
)
代码语言:txt
复制
//节点
代码语言:txt
复制
type node struct {
代码语言:txt
复制
    key         int
代码语言:txt
复制
    val         interface{}
代码语言:txt
复制
    left, right *node
代码语言:txt
复制
    n           int
代码语言:txt
复制
    color       bool
代码语言:txt
复制
}
代码语言:txt
复制
func newRedNode(key int, val interface{}) *node {
代码语言:txt
复制
    return &node{
代码语言:txt
复制
        key:   key,
代码语言:txt
复制
        val:   val,
代码语言:txt
复制
        n:     1,
代码语言:txt
复制
        color: RED,
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
//红黑树结构
代码语言:txt
复制
type RedBlackTree struct {
代码语言:txt
复制
    root *node
代码语言:txt
复制
}
代码语言:txt
复制
func (b *RedBlackTree) size(x *node) int {
代码语言:txt
复制
    if x == nil {
代码语言:txt
复制
        return 0
代码语言:txt
复制
    }
代码语言:txt
复制
    return x.n
代码语言:txt
复制
}
代码语言:txt
复制
func (b *RedBlackTree) Size() int {
代码语言:txt
复制
    return b.size(b.root)
代码语言:txt
复制
}

n表示该节点下面还有多少节点,插入新节点总是红色,用布尔类型来表示红黑

把红黑树比作2-3树的表示方式,那么红黑树是平衡的,红黑树满足以下条件:

  1. 红链接均为左边(ps:方便3-节点的表示)
  2. 任何一个节点不能同时和红链接连接(ps:不然一个节点变成了4-树)
  3. 跟节点不能是红链接

以上这三条规则共同组成了红黑树的规则: ++任意一条空链接到root节点的距离都是相等的。(只算黑链接的距离,不计算红链接)++

红黑树的插入情况和上面图片中2-3树的三种情况一样,但是只需要维护红链接的规则即可,规则3很容易解决,只需要将root节点的连接设置为BLACK就行了,主要是考虑另外两种情况

违反规则1

对于(1)情况,只需要将节点插入就行了,不需要做其他操作,因为插入总是在树底插入,而且红链接也是在左边,符合规则。

对于(2)情况,插入是在右边,就违背了规则1,需要将(2)变为(1)相同的结构,需要一个操作叫旋转。

左旋代码很简单,只需改变一下连接关系

代码语言:txt
复制
func (b *RedBlackTree) rotateLeft(h *node) *node {
代码语言:txt
复制
    x := h.right
代码语言:txt
复制
    h.right = x.left
代码语言:txt
复制
    x.left = h
代码语言:txt
复制
    x.color = h.color
代码语言:txt
复制
    h.color = RED
代码语言:txt
复制
    x.n = h.n
代码语言:txt
复制
    h.n = b.size(h.left) + b.size(h.right) + 1
代码语言:txt
复制
    return x
代码语言:txt
复制
}

注意旋转要改变节点的数量,目标节点会增加

违反规则2

违反规则2在插入时候可能会出现(1)(2)两种情况,最终是要变为(3)这种情况(如果3情况是跟节点,只需要将头部的红色链接变为黑色),如果是(1)情况,需要转变为(2),再转换为(3),这个时候需要与刚才相反的操作——右旋

与左旋完全相反的操作

代码语言:txt
复制
func (b *RedBlackTree) rotateRight(h *node) *node {
代码语言:txt
复制
    x := h.left
代码语言:txt
复制
    h.left = x.right
代码语言:txt
复制
    x.right = h
代码语言:txt
复制
    x.color = h.color
代码语言:txt
复制
    h.color = RED
代码语言:txt
复制
    x.n = h.n
代码语言:txt
复制
    h.n = b.size(h.left) + b.size(h.right) + 1
代码语言:txt
复制
    return x
代码语言:txt
复制
}

++注意:左旋和右旋都是对子节点的判断++

总结一下插入操作只需要在插入节点后递归向上处理是否违反规则情况,在规则1处理后有可能会违反规则2,所以顺序处理:

  1. 如果左边不是为红链接,右节点为红————>左旋
  2. 如果左节点为红,左节点的左节点为红————>右旋转
  3. 如果左右都为红节点————> 变成黑色

代码:

代码语言:txt
复制
//判断节点颜色
代码语言:txt
复制
func isRed(x *node) bool {
代码语言:txt
复制
    if x == nil {
代码语言:txt
复制
        return false
代码语言:txt
复制
    }
代码语言:txt
复制
    return x.color == RED
代码语言:txt
复制
}
代码语言:txt
复制
//变换反色
代码语言:txt
复制
func reverseColor(x *node) {
代码语言:txt
复制
    x.color = !x.color
代码语言:txt
复制
}
代码语言:txt
复制
//如果跟节点为红,子节点为黑或者跟节点为黑子节点为红就取反色
代码语言:txt
复制
func flipColor(x *node) {
代码语言:txt
复制
    rootRed := isRed(x) && !isRed(x.left) && !isRed(x.right)
代码语言:txt
复制
    rootBlack := !isRed(x) && isRed(x.left) && isRed(x.right)
代码语言:txt
复制
    if rootRed || rootBlack {
代码语言:txt
复制
        reverseColor(x)
代码语言:txt
复制
        reverseColor(x.left)
代码语言:txt
复制
        reverseColor(x.right)
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
//插入方法的平衡树方法,基于上面叙述翻译
代码语言:txt
复制
func (b *RedBlackTree) balance(x *node) *node {
代码语言:txt
复制
    if isRed(x.right) && !isRed(x.left) {
代码语言:txt
复制
        x = b.rotateLeft(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    if isRed(x.left) && isRed(x.left.left) {
代码语言:txt
复制
        x = b.rotateRight(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    if isRed(x.left) && isRed(x.right) {
代码语言:txt
复制
        flipColor(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    return x
代码语言:txt
复制
}

插入方法

代码语言:txt
复制
//跟节点永远都是黑色,key用int表示方便一点
代码语言:txt
复制
func (b *RedBlackTree) Put(key int, val interface{}) {
代码语言:txt
复制
    b.root = b.put(b.root, key, val)
代码语言:txt
复制
    b.root.color = BLACK
代码语言:txt
复制
}
代码语言:txt
复制
func (b *BlackReadTree) put(x *node, key int, val interface{}) *node {
代码语言:txt
复制
    if x == nil {
代码语言:txt
复制
        return newRedNode(key, val)
代码语言:txt
复制
    }
代码语言:txt
复制
    compare := x.key - key
代码语言:txt
复制
    if compare > 0 {
代码语言:txt
复制
        x.left = b.put(x.left, key, val)
代码语言:txt
复制
    } else if compare < 0 {
代码语言:txt
复制
        x.right = b.put(x.right, key, val)
代码语言:txt
复制
    } else {
代码语言:txt
复制
        x.val = val
代码语言:txt
复制
    }
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}

插入方法和普通二叉树查找树一致的,只是最后需要平衡树操作,所以需要递归自下而上平衡每个节点,这一个平衡操作不管是插入删除都需要用到。

删除

红黑树的删除是最复杂的操作,相比于红黑树插入只需要找到节点然后自下而上平衡即可。删除操作需要找到目标点,然后像二叉查找树一样删除右子节点的最小节点来填充被删除的部分,但却并不能随意的删除一个节点,因为这样做会导致一个空链接出现,破坏了红黑树的完美平衡性。

首先要介绍删除最小节点和最大节点的方法,因为删除操作中需要用到该方法,通过以下几种情况来了解删除操作可能发生的情况。

情况1:删除的节点是一个2-节点

删除一个2-节,不能直接把这个节点删除,因为删除一个节点后会导致红黑树完美平衡被破坏,所以需要一个操作: ++ ++

,为了保证平衡所以需要像右边借一个节点来左边替代被删除节点的位置。

但是像如图这种情况明显借不到一个节点能够代替被删除节点,那么只能把它变为4-节点,然后删除一个节点使得4-节点变为3-节点,相当于降低树的层数。

如果可以借到节点呢?

如果右边有一个红色的节点,可以经过两次旋转操作就借到了节点,也不会破坏树的平衡性。

情况2:删除的节点是3-节点

如果是3-节点直接删除即可

以上是删除2-节点最简单的情况(3-节点很简单),如果稍微复杂一点呢?

对于这样一棵树删除a节点就比较复杂了,如果只看abc这三个节点,和情况1是一模一样的,可以变换为b-c形式,但是却不能直接和剩下的节点连接。

这种情况说明了一个问题,在删除最小节点时候不能在找到了该节点的时候再做变换,而是从一开始就要准备删除节点时候的变换。换句话来说:++借一个节点要趁早,不要等到找到了最小节点才去右边借,而是一开始能借就借,借不到就变为4-节点++,节点怎么变化只要符合二叉树原理即可。这时候你会问,如果后面能借到节点,前面借的节点怎么办?

没关系,用balance还回去就行了,一个借4-节点如果没有被使用,只需要把链接都变为黑色就变回了4-节点,这一点和插入使用的balance方法是一样的。

流程为:

  1. 判断左子是不是2-节点(当前节点的left和left.left是不是黑色,空节点为黑色)
  2. 如果是一个2-节点,就判断能不能从右边借一个节点(right.left是不是红链接)
  3. 如果能借到节点就进过两次旋转,如果借不到就变成一个4-节点,等待下面删除
  4. 不断递归调用balance方法平衡树

其中需要注意的一点就是,为了保证平衡操作能够正常运行,不管有没有++借++到节点,都将他们变为4-节点(左右子节点都是红链接),这样可以保证balance操作的时候能够正确平衡节点

那么整个操作流程图为:

  1. 从跟节点c开始,判断是需要借一个节点的,先把节点变为4-节点,但是没有借到,继续向下走
  2. 到了e节点,判断是需要借一个节点,先把节点变为4-节点(需要将自己的链接变为黑色,不然就是5-节点),发现借不到,继续向下
  3. 找到了a节点,发现a节点的链接已经是红色了,直接删除即可
  4. 递归到b节点开始平衡,按照平衡规则右边不能出现红链接,左旋
  5. 递归到e节点开始平衡,按照平衡规则右边不能出现红链接,左旋

++除此之外,删除一个最小节点并没有太多情况,因为如果一颗完美平衡的红黑树,在每一层往下走的时候,就只有能借到节点、借不到节点、不需要借的情况,仔细思考一下。从右子节点拿一个红链接的节点对树的平衡没有什么影响,能拿就拿,不能拿就降低树的层数(变为3-节点),反正如果借用的节点没有用到,balance会还回去,这就是删除最小节点的思路++

代码

代码语言:txt
复制
func (b *RedBlackTree) borrowLeft(x *node) *node {
代码语言:txt
复制
    //节点变换为4-节点 
代码语言:txt
复制
    flipColor(x)
代码语言:txt
复制
    //能借到节点就经过两次旋转
代码语言:txt
复制
    if !isRed(x.left) && isRed(x.right.left) {
代码语言:txt
复制
        x.right = b.rotateRight(x.right)
代码语言:txt
复制
        x = b.rotateLeft(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}
代码语言:txt
复制
func (b *BlackReadTree) DeleteMin() {
代码语言:txt
复制
    if !isRed(b.root.left) && !isRed(b.root.right) {
代码语言:txt
复制
        b.root.color = BLACK
代码语言:txt
复制
    }
代码语言:txt
复制
    b.root = b.deleteMin(b.root)
代码语言:txt
复制
    if !b.Empty() {
代码语言:txt
复制
        b.root.color = RED
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
//主要逻辑
代码语言:txt
复制
func (b *RedBlackTree) deleteMin(x *node) *node {
代码语言:txt
复制
    if x.left == nil {
代码语言:txt
复制
        return nil
代码语言:txt
复制
    }
代码语言:txt
复制
    //判断是否需要借一个节点
代码语言:txt
复制
    if !isRed(x.left) && !isRed(x.left.left) {
代码语言:txt
复制
        x = b.borrowLeft(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    x.left = b.deleteMin(x.left)
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}
代码语言:txt
复制
func (b *RedBlackTree) Empty() bool {
代码语言:txt
复制
    return b.root == nil
代码语言:txt
复制
}

删除最大值和删除最小值类似,删除最小值需要向右边借用一个节点,而删除最大值向左边借节点,向左边借节点只需要一次循转,但是左边有两种情况

  1. 在自己的左边,右旋自己
  2. 在父节点的左边有红链接,右旋父节点

整个流程和删除最小值区别主要是在于对于“借”节点的判断,代码如下:

代码语言:txt
复制
func (b *RedBlackTree) borrowRight(x *node) *node {
代码语言:txt
复制
    flipColor(x)
代码语言:txt
复制
    //如果左边能借向左边借
代码语言:txt
复制
    if isRed(x.left.left) {
代码语言:txt
复制
        x = b.rotateRight(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}
代码语言:txt
复制
func (b *RedBlackTree) DeleteMax() {
代码语言:txt
复制
    if !isRed(b.root.left) && !isRed(b.root.right) {
代码语言:txt
复制
        b.root.color = BLACK
代码语言:txt
复制
    }
代码语言:txt
复制
    b.root = b.deleteMax(b.root)
代码语言:txt
复制
    if !b.Empty() {
代码语言:txt
复制
        b.root.color = RED
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
func (b *RedBlackTree) deleteMax(x *node) *node {
代码语言:txt
复制
    //自己左边是红链接直接旋转
代码语言:txt
复制
    if isRed(x.left) {
代码语言:txt
复制
        x = b.rotateRight(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    if x.right == nil {
代码语言:txt
复制
        return nil
代码语言:txt
复制
    }
代码语言:txt
复制
    if !isRed(x.right) && !isRed(x.right.left) {
代码语言:txt
复制
        x = b.borrowRight(x)
代码语言:txt
复制
    }
代码语言:txt
复制
    x.right = b.deleteMax(x.right)
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}

删除最大和最小键可以得出一个思路,不断的借用一个节点来保证当前节点不是2-节点,而是一个3-节点或者4-节点,这样可以安全的删除一个节点而不用担心删除一个2-节点导致树平衡问题,然后自底向上平衡树。

最复杂的红黑树删除来了,删除操作结合删除最大值、最小值和普通二叉查找树的思想,由于每一轮向下遍历树的时候是不确定删除的节点最终是在树的哪个位置(删除最大最小值可以确定),所以需要结合删除最值的特性。

  • 二叉查找树特性:找到删除节点后,需要拿到右子节点的最小值填充,如果右子节点不存在可以直接删除
  • 每一轮遍历的时候,将删除最大值最小值判断结合起来,能向左边借就向左边借节点,能向右边借就向右边借,最后再平衡

融合代码如下:

代码语言:txt
复制
func (b *RedBlackTree) delete(x *node, key int) *node {
代码语言:txt
复制
    //借右节点
代码语言:txt
复制
    if key <x.key {
代码语言:txt
复制
        if !isRed(x.left) && !isRed(x.left.left) {
代码语言:txt
复制
            x = b.borrowLeft(x)
代码语言:txt
复制
        }
代码语言:txt
复制
        x.left = b.delete(x.left, key)
代码语言:txt
复制
    } else {
代码语言:txt
复制
        //借右边第一种情况
代码语言:txt
复制
        if isRed(x.left) {
代码语言:txt
复制
            x = b.rotateRight(x)
代码语言:txt
复制
        }
代码语言:txt
复制
        //二叉查找树无右节点删除情况
代码语言:txt
复制
        if key == x.key && x.right == nil {
代码语言:txt
复制
            return nil
代码语言:txt
复制
        }
代码语言:txt
复制
        //借右边第二种情况
代码语言:txt
复制
        if !isRed(x.right) && !isRed(x.right.left) {
代码语言:txt
复制
            x = b.borrowRight(x)
代码语言:txt
复制
        }
代码语言:txt
复制
        //二叉查找树删除方法
代码语言:txt
复制
        if key == x.key {
代码语言:txt
复制
            x.key = min(x.right).key
代码语言:txt
复制
            x.val = min(x.right).val
代码语言:txt
复制
            x.right = b.deleteMin(x.right)
代码语言:txt
复制
        //继续递归
代码语言:txt
复制
        } else {
代码语言:txt
复制
            x.right = b.delete(x.right, key)
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    return b.balance(x)
代码语言:txt
复制
}

完整删除方法

代码语言:txt
复制
func (b *RedBlackTree) Delete(key int) {
代码语言:txt
复制
    if !isRed(b.root.left) && !isRed(b.root.right) {
代码语言:txt
复制
        b.root.color = BLACK
代码语言:txt
复制
    }
代码语言:txt
复制
    b.root = b.delete(b.root, key)
代码语言:txt
复制
    if !b.Empty() {
代码语言:txt
复制
        b.root.color = RED
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
func min(x *node) *node {
代码语言:txt
复制
    if x.left != nil {
代码语言:txt
复制
        return min(x.left)
代码语言:txt
复制
    }
代码语言:txt
复制
    return x
代码语言:txt
复制
}

最后贴一个JAVA代码

代码语言:txt
复制
public class RedBlackTreeMap<Key extends Comparable<Key>, Value> {
代码语言:txt
复制
    private final boolean RED = true;
代码语言:txt
复制
    private final boolean BLACK = false;
代码语言:txt
复制
    private Node root;
代码语言:txt
复制
    private class Node {
代码语言:txt
复制
        Key key;
代码语言:txt
复制
        Value val;
代码语言:txt
复制
        Node left, right;
代码语言:txt
复制
        boolean color;
代码语言:txt
复制
        int n;
代码语言:txt
复制
        public Node(Key key, Value val, boolean color, int n) {
代码语言:txt
复制
            this.key = key;
代码语言:txt
复制
            this.val = val;
代码语言:txt
复制
            this.color = color;
代码语言:txt
复制
            this.n = n;
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    public void put(Key key, Value val) {
代码语言:txt
复制
        root = put(root, key, val);
代码语言:txt
复制
        root.color = BLACK;
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node put(Node x, Key key, Value val) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return new Node(key, val, RED, 1);
代码语言:txt
复制
        }
代码语言:txt
复制
        int cmp = key.compareTo(x.key);
代码语言:txt
复制
        if (cmp < 0) {
代码语言:txt
复制
            x.left = put(x.left, key, val);
代码语言:txt
复制
        } else if (cmp > 0) {
代码语言:txt
复制
            x.right = put(x.right, key, val);
代码语言:txt
复制
        } else {
代码语言:txt
复制
            x.val = val;
代码语言:txt
复制
        }
代码语言:txt
复制
        if (isRed(x.right) && !isRed(x.left)) {
代码语言:txt
复制
            x = rotateLeft(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        if (isRed(x.left) && isRed(x.left.left)) {
代码语言:txt
复制
            x = rotateRight(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        if (isRed(x.left) && isRed(x.right)) {
代码语言:txt
复制
            flipColor(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        return x;
代码语言:txt
复制
    }
代码语言:txt
复制
    public Value get(Key key) {
代码语言:txt
复制
        return get(root, key);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Value get(Node x, Key key) {
代码语言:txt
复制
        while (x != null) {
代码语言:txt
复制
            int cmp = key.compareTo(x.key);
代码语言:txt
复制
            if (cmp < 0) {
代码语言:txt
复制
                x = x.left;
代码语言:txt
复制
            } else if (cmp > 0) {
代码语言:txt
复制
                x = x.right;
代码语言:txt
复制
            } else {
代码语言:txt
复制
                return x.val;
代码语言:txt
复制
            }
代码语言:txt
复制
        }
代码语言:txt
复制
        return null;
代码语言:txt
复制
    }
代码语言:txt
复制
    public void deleteMin() {
代码语言:txt
复制
        if (isEmpty()) {
代码语言:txt
复制
            throw new RuntimeException();
代码语言:txt
复制
        }
代码语言:txt
复制
        if (!isRed(root.left) && !isRed(root.right)) {
代码语言:txt
复制
            root.color = RED;
代码语言:txt
复制
        }
代码语言:txt
复制
        root = deleteMin(root);
代码语言:txt
复制
        if (!isEmpty()) {
代码语言:txt
复制
            root.color = BLACK;
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    public void deleteMax() {
代码语言:txt
复制
        if (isEmpty()) {
代码语言:txt
复制
            throw new RuntimeException();
代码语言:txt
复制
        }
代码语言:txt
复制
        if (!isRed(root.left) && !isRed(root.right)) {
代码语言:txt
复制
            root.color = RED;
代码语言:txt
复制
        }
代码语言:txt
复制
        root = deleteMax(root);
代码语言:txt
复制
        if (!isEmpty()) {
代码语言:txt
复制
            root.color = BLACK;
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    public void delete(Key key) {
代码语言:txt
复制
        if (isEmpty()) {
代码语言:txt
复制
            throw new RuntimeException();
代码语言:txt
复制
        }
代码语言:txt
复制
        if (!isRed(root.left) && !isRed(root.right)) {
代码语言:txt
复制
            root.color = RED;
代码语言:txt
复制
        }
代码语言:txt
复制
        root = delete(root, key);
代码语言:txt
复制
        if (!isEmpty()) {
代码语言:txt
复制
            root.color = BLACK;
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node delete(Node x, Key key) {
代码语言:txt
复制
        if (key.compareTo(x.key) < 0) {
代码语言:txt
复制
            if (!isRed(x.left) && !isRed(x.left.left)) {
代码语言:txt
复制
                x = moveLeft(x);
代码语言:txt
复制
            }
代码语言:txt
复制
            x.left = delete(x.left, key);
代码语言:txt
复制
        } else {
代码语言:txt
复制
            if (isRed(x.left)) {
代码语言:txt
复制
                x = rotateLeft(x);
代码语言:txt
复制
            }
代码语言:txt
复制
            if (key.compareTo(x.key) == 0 && x.right == null) {
代码语言:txt
复制
                return null;
代码语言:txt
复制
            }
代码语言:txt
复制
            if (!isRed(x.right) && !isRed(x.right.left)) {
代码语言:txt
复制
                x = moveRight(x);
代码语言:txt
复制
            }
代码语言:txt
复制
            if (key.compareTo(x.key) == 0) {
代码语言:txt
复制
                x.val = get(x.right, min(x.right).key);
代码语言:txt
复制
                x.key = min(x).key;
代码语言:txt
复制
                x.right = deleteMin(x.right);
代码语言:txt
复制
            } else {
代码语言:txt
复制
                x.right = delete(x.right, key);
代码语言:txt
复制
            }
代码语言:txt
复制
        }
代码语言:txt
复制
        return balance(x);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node deleteMin(Node x) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return null;
代码语言:txt
复制
        }
代码语言:txt
复制
        if (!isRed(x.left) && !isRed(x.left.left)) {
代码语言:txt
复制
            x = moveLeft(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        x.left = deleteMin(x.left);
代码语言:txt
复制
        return balance(x);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node deleteMax(Node x) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return null;
代码语言:txt
复制
        }
代码语言:txt
复制
        if (!isRed(x.right) && !isRed(x.right.left)) {
代码语言:txt
复制
            x = moveRight(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        x.right = deleteMax(x.right);
代码语言:txt
复制
        return balance(x);
代码语言:txt
复制
    }
代码语言:txt
复制
    public int size() {
代码语言:txt
复制
        return size(root);
代码语言:txt
复制
    }
代码语言:txt
复制
    private int size(Node x) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return 0;
代码语言:txt
复制
        }
代码语言:txt
复制
        return x.n;
代码语言:txt
复制
    }
代码语言:txt
复制
    public boolean isEmpty() {
代码语言:txt
复制
        return size() == 0;
代码语言:txt
复制
    }
代码语言:txt
复制
    private void flipColor(Node x) {
代码语言:txt
复制
        boolean rootRed = x.color == RED && x.left.color == BLACK && x.right.color == BLACK;
代码语言:txt
复制
        boolean rootBlack = x.color == BLACK && x.left.color == RED && x.right.color == RED;
代码语言:txt
复制
        if (rootBlack || rootRed) {
代码语言:txt
复制
            x.color = !x.color;
代码语言:txt
复制
            x.left.color = !x.left.color;
代码语言:txt
复制
            x.right.color = !x.right.color;
代码语言:txt
复制
        }
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node rotateLeft(Node x) {
代码语言:txt
复制
        Node h = x.right;
代码语言:txt
复制
        x.right = h.left;
代码语言:txt
复制
        h.left = x;
代码语言:txt
复制
        h.color = x.color;
代码语言:txt
复制
        x.color = RED;
代码语言:txt
复制
        h.n = x.n;
代码语言:txt
复制
        x.n = size(h.left) + size(h.right) + 1;
代码语言:txt
复制
        return h;
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node rotateRight(Node x) {
代码语言:txt
复制
        Node h = x.left;
代码语言:txt
复制
        x.left = h.right;
代码语言:txt
复制
        h.right = x;
代码语言:txt
复制
        h.color = x.color;
代码语言:txt
复制
        x.color = RED;
代码语言:txt
复制
        h.n = x.n;
代码语言:txt
复制
        x.n = size(h.left) + size(h.right) + 1;
代码语言:txt
复制
        return h;
代码语言:txt
复制
    }
代码语言:txt
复制
    private boolean isRed(Node x) {
代码语言:txt
复制
        if (x == null) {
代码语言:txt
复制
            return false;
代码语言:txt
复制
        }
代码语言:txt
复制
        return x.color == RED;
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node moveLeft(Node x) {
代码语言:txt
复制
        flipColor(x);
代码语言:txt
复制
        if (isRed(x.right.left)) {
代码语言:txt
复制
            x.right = rotateRight(x.right);
代码语言:txt
复制
            x = rotateLeft(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        return balance(x);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node moveRight(Node x) {
代码语言:txt
复制
        flipColor(x);
代码语言:txt
复制
        if (isRed(x.left.left)) {
代码语言:txt
复制
            x = rotateRight(x);
代码语言:txt
复制
        }
代码语言:txt
复制
        return balance(x);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node balance(Node h) {
代码语言:txt
复制
        if (isRed(h.right)) {
代码语言:txt
复制
            h = rotateLeft(h);
代码语言:txt
复制
        }
代码语言:txt
复制
        if (isRed(h.left) && isRed(h.left.left)) {
代码语言:txt
复制
            h = rotateRight(h);
代码语言:txt
复制
        }
代码语言:txt
复制
        if (isRed(h.left) && isRed(h.right)) {
代码语言:txt
复制
            flipColor(h);
代码语言:txt
复制
        }
代码语言:txt
复制
        h.n = size(h.left) + size(h.right) + 1;
代码语言:txt
复制
        return h;
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node min(Node x) {
代码语言:txt
复制
        if (x.left == null) {
代码语言:txt
复制
            return x;
代码语言:txt
复制
        }
代码语言:txt
复制
        return min(x.left);
代码语言:txt
复制
    }
代码语言:txt
复制
    private Node max(Node x) {
代码语言:txt
复制
        if (x.right == null) {
代码语言:txt
复制
            return x;
代码语言:txt
复制
        }
代码语言:txt
复制
        return max(x.right);
代码语言:txt
复制
    }

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 二叉查找树
  • 2-3树
    • 情况1:插入的节点是2-节点
      • 情况2:插入的节点是个3-节点
        • 情况3:父节点是3-节点,对父节点插入
        • 红黑树
          • 违反规则1
            • 违反规则2
              • 插入方法
                • 删除
                  • 情况1:删除的节点是一个2-节点
                    • 如果可以借到节点呢?
                      • 情况2:删除的节点是3-节点
                        • 代码
                          • 融合代码如下:
                            • 完整删除方法
                              • 最后贴一个JAVA代码
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档