Java基础(并不基础的红黑树)

前言

之前写了有关于Java集合的总结,在Java集合中HashMap、TreeMap的底层实现都涉及到了红黑树。之前也听说是师兄在面试的时候也被问到有关于红黑树的部分,所以还是简单学习一下。
先介绍一个很不错的网站,可以模拟红黑树的插入和删除:
红黑树网站

红黑树的性质

红黑树的本质就是一颗不太严格的二叉查找平衡树。
也就是说它具有查找树的性质——小的数在左子树上,大的数在右子树上,以及平衡树的特性——高度差不超过1(在红黑树中是任意到叶节点的路径上的黑节点个数相同)。
那么它相比一般的平衡二叉查找树有什么优势呢?(感谢杰哥指出)
总的来说就是,红黑树在插入的时候并不像AVL树那样需要频繁的旋转,这样插入效率较得到了不少提升。
总的来说红黑树有如下特点:

  • 根节点为黑色。
  • 叶子节点是为黑色的空节点(也就是说在原来的叶子节点下面都加上了两个黑色的空节点)。
  • 两个红色节点不能相邻(两个黑色节点可以)。
  • 任意一条到叶子节点的路径上,黑节点个数相同。

红黑树的插入

红黑树由于插入之后需要维护其自身的特性,所以有如下操作:

  • 变色:将一个节点的由红色变为黑色,或者由黑色变为红色。
  • 旋转:当插入一个节点导致二叉树不能维护自己的平衡的时候需要进行旋转,分为左旋转和右旋转。

关于插入操作,我们结合Java中的TreeMap源码来看一下(其中的rotateLeftrotateRight为左旋转函数和右旋转函数):

    /** From CLR */
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

第一步 判断父节点颜色

首先我们看到当进入这个函数的时候,把这个节点的颜色变为红色,这也符合我们之前说的当插入节点的时候把插入节点设置为红色。
我们看到源码中,最外面的while循环里面的条件是x != null && x != root && x.parent.color == RED,也就说当父亲节点是黑色以及插入节点是根节点的时候是不会进入这个循环的。
由此我们可以清楚的知道当插入节点的父亲节点为黑色或者插入节点是根节点的时候,可以不需要进行变色和旋转操作就可以维护红黑树的平衡。
那我们直接跳出循环外侧,可以看到最后一步是把根节点变为黑色,这是因为如果插入节点为根节点的话会使得根节点为红色,这里需要把它变成黑色(当然如果进入循环的话还有其他情况,这里说的是不进入循环的情况)。

结合这两张图来看一下,假设有如下红黑树,然后插入一个节点。
黑色根节点
插入1
可以看到插入之后其实是不会影响红黑树的特性的。

第二步 父节点的位置和叔节点的颜色

在进入循环之后(说明此时父节点的红色节点),我们看到首先上来的一个条件判断就是if(parentOf(x) == leftOf(parentOf(parentOf(x)))也就是说下一步的操作是判断插入节点的其祖父节点的左子节点还是其祖父节点的右子节点(此时父亲节点不可能是根节点,因为父亲节点是红色的)。我们可以看到无论是左子节点还是右子节点,代码都是相似的,区别就在于进行旋转的时候是左旋转还是右旋转。
这里我们假设父亲节点是祖父节点的左子节点,进入if判断。
进入if之后,下一行代码是rightOf(parentOf(parentOf(x)))也就是获得插入节点的叔节点的颜色(当叔节点是叶子节点的时候也为黑色,所以不会出现叔节点不存在的情况)。
接下来的分类依据是叔节点的颜色,我们先来看比较短的情况,也就是叔节点是红色的情况:

	//变色操作
	setColor(parentOf(x), BLACK);
	setColor(y, BLACK);
	setColor(parentOf(parentOf(x)), RED);
	x = parentOf(parentOf(x));

显然部分的代码就是我们上面提到的变色操作,由此我们可以得出结论当叔节点为红色的时候(因为我们看到无论父节点是左子节点还是右子节点,这几行代码都是一致的),我们把插入节点的父节点、叔节点变为黑色,祖父节点变为红色,然后把祖父节点当做插入节点再进行一次平衡。红色叔节点

第三步 黑色叔节点

那如果是黑色叔节点呢?
我们选取父节点是右子节点的情况看一下代码:

if (x == leftOf(parentOf(x))) {
    x = parentOf(x);
    rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));

这边可以看到,如果黑色叔节点,那么则需要进行旋转,进行旋转的时候又有讲究。为了好理解,我们先从简单的情况开始,所以我们直接跳过if,看后面三行代码。
首先代码中进行了变色,把父节点变为黑色,祖父节点变为红色,然后对祖父节点进行一次左旋转(在GIF中是先旋转再变色)。
我们来模拟一下,在如下图红黑树中插入66节点。
注意此时x依旧是插入的节点(66节点),然后继续while循环,发现此时父节点已经是黑色的了,所以直接退出循环。
黑色叔节点右节点

第四步 黑色叔节点和左节点

那么最后来看最复杂的情况,插入节点是左节点并且叔节点是黑色的(父节点是右子节点)。
相比前面的情况多出了一段代码:

if (x == leftOf(parentOf(x))) {
    x = parentOf(x);
    rotateRight(x);
}

在这种情况下,首先先对插入节点和其父节点做一次右旋转,然后这个时候的x已经变成了其父亲节点(在GIF中插入节点是65,其父亲节点是66),这就又把问题转化为了第三步中的问题,也就是说接下来需要对新的插入节点(此时x是66节点)的父节点和祖父节点进行变色,然后进行一次左旋转。
黑色叔节点左节点

插入总结

通过以上分析我们可以明白红黑树的插入过程。

  1. 首先判断插入节点是否是根节点、其父亲节点是否是黑色节点,如果是,则直接退出循环,把根节点变为黑色。
  2. 进入循环后,判断其叔节点是否是红色节点,如果是则进行变色,父节点变为黑色,祖父节点变为红色,然后把祖父节点当做插入节点再次循环。
  3. 如果叔节点是红色节点,判断插入节点的父节点是左节点还有右节点,然后如果在右节点中插入右节点和在左节点中插入左节点是一类,这种情况下找到插入节点的父节点和祖父节点进行变色,父节点变为黑色,祖父节点变为红色,然后对祖父节点进行一次旋转(右右节点左旋,左左节点右旋),然后退出循环(此时插入节点的父节点已经是黑色了)。
  4. 如果是左右节点或者右左节点,那么先对其父节点进行一次左(右)旋转,把x设置为其父节点,然后再执行3中的操作。

删除明天再写。
to Be continued

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 是一种平衡二叉,它的每个节点都带有颜色属性,可以是色或色。在中,任何一个节点的子节点都有相同的色节点,并且每个节点不是色就是色。 具有以下性质: 1. 每个节点或者是色,或者是色。 2. 根节点是色。 3. 每个叶子节点是色。 [注意:这里叶子节点,是指为空的叶子节点!] 4. 如果一个节点是色的,则它的子节点必须是色的。 5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的色节点。 由于的性质,它是一种高度平衡的二叉,查找、插入和删除的时间复杂度都是O(log n)。 Java实现大致如下: ```java public class RedBlackTree { private static final boolean RED = true; private static final boolean BLACK = false; private class Node { int key; Node left; Node right; boolean color; public Node(int key, boolean color) { this.key = key; this.color = color; } } private Node root; private boolean isRed(Node x) { if (x == null) return false; return x.color == RED; } private Node rotateLeft(Node h) { Node x = h.right; h.right = x.left; x.left = h; x.color = h.color; h.color = RED; return x ### 回答2: Java中的是一种自平衡的二叉查找,它的写法涉及到以下几个重要的概念和步骤: 1. 定义的节点类:节点类通常包括键值、左孩子、右孩子、父节点、颜色等属性。 2. 创建类:类包含根节点和一些基本操作,如插入、删除、查找等。 3. 实现的插入操作:在插入节点的时候,根据的性质进行适当的调整,使得仍然满足的要求。 - 首先,将节点插入到二叉查找中的合适位置,并将节点标记为色。 - 然后,根据性质,进行适当的旋转和变色操作,以确保的平衡性。 4. 实现的删除操作:在删除节点的时候,同样需要进行适当的旋转和变色操作,以保持的平衡性。 - 首先,根据二叉查找的删除操作,找到待删除的节点,并进行删除。 - 然后,根据的性质,进行适当的旋转和变色操作,以恢复的平衡性。 5. 实现的查找操作:可以通过递归或迭代的方式,在中进行查找操作,以找到指定的节点。 总体而言,编写的关键在于理解和应用的性质,并根据性质进行相应的旋转和变色操作,以保持的平衡性。此外,还需要根据具体的需求实现插入、删除和查找等基本操作。 ### 回答3: 是一种自平衡的二叉搜索,其主要特点是能够在各种操作下保持的平衡,使得最长路径不会超过最短路径的2倍。 在Java中实现,我们首先需要定义一个节点的类。该类需要包含以下属性:节点值、父节点、左子节点、右子节点和颜色(色或色)。除此之外,还需要定义一些辅助方法,如获取节点的兄弟节点、获取节点的祖父节点等。 接着,我们需要实现一些关键的操作方法,如插入节点、删除节点、左旋转、右旋转、变色等。插入节点的过程中,首先需要找到插入位置,并将节点插入到相应位置。然后,根据的性质进行调整,确保的平衡性。删除节点的过程中,首先需要找到要删除的节点,并根据其子节点的情况进行相应的删除和调整。 在实现这些操作时,我们需要注意以下几点: 1. 每个节点都有一个颜色属性,可以用一个boolean值表示,true表示色,false表示色。 2. 在插入和删除节点时进行必要的旋转操作,以保持的平衡。 3. 在插入节点时,如果父节点是色的,则需要进行颜色变换和旋转操作,以确保的平衡。 4. 在删除节点时,如果被删除的节点是色的,则直接删除即可;如果被删除的节点是色的,则需要进行额外的操作,以保持的平衡。 总之,的实现需要遵循一定的规则和原则,以确保的平衡性。这样可以在插入、删除等操作时保持较好的性能和效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值