红黑树解读与Java实现

article/2025/9/21 0:34:03

红黑树解读与Java实现

概要

目录

  • 红黑树的介绍
  • 红黑树的应用
  • 红黑树的时间复杂度和相关证明
  • 红黑树的基本操作,左右旋
  • 红黑树的基本操作,添加与调整
  • 红黑树的基本操作,删除与调整

一、红黑树的介绍

什么是红黑树?

  • R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black),它是一种弱平衡二叉树(Weak AVL)。

红黑树有什么特性?

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

通过以上特性,确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

这里要讲一下 着色法则,着色法则的推论是:红黑树的高度最多为2log(N+1),因此,查找操作保证是一种对数操作。以下是红黑树图示:

在这里插入图片描述

二、红黑树应用

① Java的HashMap、TreeMap、TreeSet

② C++的STL中的set、map以及Linux的虚拟内存管理等

红黑树是变种的AVL树,是一种弱平衡二叉树,这里可能有人会有疑问,为何采用AVL(平衡二叉树)而要采用弱平衡性的红黑树,原因:

  • AVL树保证每个子树的高度差的绝对值都不超过1,是强平衡性,在实际的运用中,为了保证这一强平衡的性质,如果是运用在一个写操作密集的地方,那么,损耗的性能往往更大。
  • RB-TREE,通过弱平衡性,哪怕是在写操作密集的地方,调整平衡的操作进行的次数更少,性能损耗更少。

当然,如果要应用在读操作密集的地方,那么很有必要保证强平衡性而采用AVL树。

三、红黑树的时间复杂度

红黑树的时间复杂为O(log(N))

以下我将利用 “ 数学归纳法 ” 对红黑树的时间复杂度进行证明:

定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).

证明:

 "一棵含有n个节点的红黑树的高度至多为2log(n+1)" 的逆否命题是 "高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个"。即转化证明为:高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个

定义:从某个节点x出发(不包括该节点)到达一个叶节点的任意一条路径上,黑色节点的个数称为该节点的黑高度(x’s black height),记为bh(x)

找出条件:

① 根据红黑树的特性(5) ,即从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点"可知,从节点x出发到达的所有的叶节点具有相同数目的黑节点。这也就意味着,bh(x)的值是唯一的

② 根据红黑色的特性(4),即如果一个节点是红色的,则它的子节点必须是黑色的"可知,从节点x出发达到叶节点"所经历的黑节点数目">= “所经历的红节点的数目”。

由条件①②,假设x是根节点,则可以得出结论 “bh(x) >= h/2”。 进而我们只需要证明:

“高度为h的红黑树,它的包含的黑节点个数至少为 2bh(x)-1个”

以下是数学归纳法的证明过程:

(01) 当树的高度h=0时,内节点个数是0,bh(x) 为0,2bh(x)-1 也为 0。显然,原命题成立。(02) 当h>0,且树的高度为 h-1 时,它包含的节点个数至少为 2bh(x)-1-1。这个是根据(01)推断出来的!下面,由树的高度为 h-1 的已知条件推出“树的高度为 h 时,它所包含的节点树为 2bh(x)-1”。当树的高度为 h 时,对于节点x(x为根节点),其黑高度为bh(x)。对于节点x的左右子树,它们黑高度为 bh(x) 或者 bh(x)-1。根据(02)的已知条件,我们已知 "x的左右子树,即高度为 h-1 的节点,它包含的节点至少为 2bh(x)-1-1 个";所以,节点x所包含的节点至少为 ( 2bh(x)-1-1 ) + ( 2bh(x)-1-1 ) + 1 = 2^bh(x)-1。即节点x所包含的节点至少为 2bh(x)-1。因此,原命题成立。由(01)、(02)得出,"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。因此,“一棵含有n个节点的红黑树的高度至多为2log(n+1)”。

四、红黑树操作之左右旋

红黑树在进行插入、删除操作之后,肯定会有失去平衡的时刻,因此,需要对节点进行左、右旋的操作。

以下贴出红黑树左右旋转的实现:

/** 对红黑树的节点(x)进行左旋转** 左旋示意图(对节点x进行左旋):*      px                              px*     /                               /*    x                               y*   /  \      --(左旋)-.           / \                #*  lx   y                         x  ry*     /   \                      /    \*    ly   ry                    lx     ly***/private void leftRotate(RBTNode<T> x) {// 设置x的右孩子为yRBTNode<T> y = x.right;// 将 “y的左孩子” 设为 “x的右孩子”;// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”x.right = y.left;if (y.left != null)y.left.parent = x;// 将 “x的父亲” 设为 “y的父亲”y.parent = x.parent;if (x.parent == null) {this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点} else {if (x.parent.left == x)x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”elsex.parent.right = y;    // 如果 x是它父节点的右孩子,则将y设为“x的父节点的右孩子”}// 将 “x” 设为 “y的左孩子”y.left = x;// 将 “x的父节点” 设为 “y”x.parent = y;}/** 对红黑树的节点(y)进行右旋转** 右旋示意图(对节点y进行右旋):*            py                               py*           /                                /*          y                                x*         /  \      --(右旋)-.            /  \                     #*        x   ry                          lx   y*       / \                                   / \                   #*      lx  rx                                rx  ry**/private void rightRotate(RBTNode<T> y) {// 设置x是当前节点的左孩子。RBTNode<T> x = y.left;// 将 “x的右孩子” 设为 “y的左孩子”;// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”y.left = x.right;if (x.right != null)x.right.parent = y;// 将 “y的父亲” 设为 “x的父亲”x.parent = y.parent;if (y.parent == null) {this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点} else {if (y == y.parent.right)y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”elsey.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”}// 将 “y” 设为 “x的右孩子”x.right = y;// 将 “y的父节点” 设为 “x”y.parent = x;}

以上是JAVA代码的左右旋实现,为了方便不了解JAVA的读者阅读,贴出来自《算法导论》的伪代码:

LEFT-ROTATE(T, x)  y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为xp[y] ← p[x]             // 将 “x的父亲” 设为 “y的父亲”if p[x] = nil[T]       then root[T] ← y                 // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点else if x = left[p[x]]  then left[p[x]] ← y    // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”else right[p[x]] ← y   // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”left[y] ← x             // 将 “x” 设为 “y的左孩子”p[x] ← y                // 将 “x的父节点” 设为 “y”/**********************************************/RIGHT-ROTATE(T, y)  x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为yp[x] ← p[y]             // 将 “y的父亲” 设为 “x的父亲”if p[y] = nil[T]       then root[T] ← x                 // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点else if y = right[p[y]]  then right[p[y]] ← x   // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”else left[p[y]] ← x    // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”right[x] ← y            // 将 “y” 设为 “x的右孩子”p[y] ← x                // 将 “y的父节点” 设为 “x”

以上就是左右旋的代理实现,以及图示,若不清除的读者可以拿出笔纸画一画,相信就能理解了!

五、红黑树之添加与修正

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

第一步: 将红黑树当作一颗二叉查找树,将节点插入。
​ 红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
​ 好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

第二步:将插入的节点着色为"红色"。
​ 为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
​ 将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
​ 第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
​ 对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
​ 对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
​ 对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
​ 对于"特性(4)",是有可能违背的!
​ 那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

下面看看代码到底是怎样实现这三步的。

 private void insert(RBTNode<T> node) {int cmp;RBTNode<T> y = null;RBTNode<T> x = this.mRoot;// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。while (x != null) {y = x;cmp = node.key.compareTo(x.key);if (cmp < 0)x = x.left;elsex = x.right;}node.parent = y;if (y != null) {cmp = node.key.compareTo(y.key);if (cmp < 0)y.left = node;elsey.right = node;} else {this.mRoot = node;}// 2. 设置节点的颜色为红色node.color = RED;// 3. 将它重新修正为一颗二叉查找树insertFixUp(node);}

以上的插入方法实现分为三步:

  • 二叉查找树的查找插入
  • 新节点着为红色
  • 修正

接下来主要是看修正操作是如何进行的?

 private void insertFixUp(RBTNode<T> node) {RBTNode<T> parent, gparent;// 若“父节点存在,并且父节点的颜色是红色”while (((parent = parentOf(node)) != null) && isRed(parent)) {gparent = parentOf(parent);//若“父节点”是“祖父节点的左孩子”if (parent == gparent.left) {// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.right;if ((uncle != null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是右孩子if (parent.right == node) {RBTNode<T> tmp;leftRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是左孩子。setBlack(parent);setRed(gparent);rightRotate(gparent);} else {    //若“z的父节点”是“z的祖父节点的右孩子”// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.left;if ((uncle != null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是左孩子if (parent.left == node) {RBTNode<T> tmp;rightRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是右孩子。setBlack(parent);setRed(gparent);leftRotate(gparent);}}// 将根节点设为黑色setBlack(this.mRoot);}

插入修复的理解:

  • ① 情况说明:被插入的节点是根节点。
    ​ 处理方法:直接把此节点涂为黑色。
  • ② 情况说明:被插入的节点的父节点是黑色。
    ​ 处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
  • ③ 情况说明:被插入的节点的父节点是红色。
    ​ 处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)
现象说明处理策略
Case 1当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。(01) 将“父节点”设为黑色。(02) 将“叔叔节点”设为黑色。(03) 将“祖父节点”设为“红色”。(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
Case 2当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子(01) 将“父节点”作为“新的当前节点”。(02) 以“新的当前节点”为支点进行左旋。
Case 3当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子(01) 将“父节点”设为“黑色”。(02) 将“祖父节点”设为“红色”。(03) 以“祖父节点”为支点进行右旋。

以下是分别三种CASE对应的图示:

  • CASE1

在这里插入图片描述

  • CASE2

在这里插入图片描述

  • CASE3

在这里插入图片描述

六、红黑树之删除与修正

第一步:将红黑树当作一颗二叉查找树,将节点删除。
​ 这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
​ ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
​ ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
​ ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
​ 因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

代码实现:

private void remove(RBTNode<T> node) {RBTNode<T> child, parent;boolean color;// 被删除节点的"左右孩子都不为空"的情况。if ((node.left != null) && (node.right != null)) {// 被删节点的后继节点。(称为"取代节点")// 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。RBTNode<T> replace = node;// 获取后继节点replace = replace.right;while (replace.left != null)replace = replace.left;// "node节点"不是根节点(只有根节点不存在父节点)if (parentOf(node) != null) {if (parentOf(node).left == node)parentOf(node).left = replace;elseparentOf(node).right = replace;} else {// "node节点"是根节点,更新根节点。this.mRoot = replace;}// child是"取代节点"的右孩子,也是需要"调整的节点"。// "取代节点"肯定不存在左孩子!因为它是一个后继节点。child = replace.right;parent = parentOf(replace);// 保存"取代节点"的颜色color = colorOf(replace);// "被删除节点"是"它的后继节点的父节点"if (parent == node) {parent = replace;} else {// child不为空if (child != null)setParent(child, parent);parent.left = child;replace.right = node.right;setParent(node.right, replace);}replace.parent = node.parent;replace.color = node.color;replace.left = node.left;node.left.parent = replace;if (color == BLACK)removeFixUp(child, parent);node = null;return;}if (node.left != null) {child = node.left;} else {child = node.right;}parent = node.parent;// 保存"取代节点"的颜色color = node.color;if (child != null)child.parent = parent;// "node节点"不是根节点if (parent != null) {if (parent.left == node)parent.left = child;elseparent.right = child;} else {this.mRoot = child;}if (color == BLACK)removeFixUp(child, parent);node = null;}

删除的修正操作:

private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {RBTNode<T> other;while ((node == null || isBlack(node)) && (node != this.mRoot)) {if (parent.left == node) {other = parent.right;if (isRed(other)) {// Case 1: x的兄弟w是红色的setBlack(other);setRed(parent);leftRotate(parent);other = parent.right;}if ((other.left == null || isBlack(other.left)) &&(other.right == null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的setRed(other);node = parent;parent = parentOf(node);} else {if (other.right == null || isBlack(other.right)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。setBlack(other.left);setRed(other);rightRotate(other);other = parent.right;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.right);leftRotate(parent);node = this.mRoot;break;}} else {other = parent.left;if (isRed(other)) {// Case 1: x的兄弟w是红色的setBlack(other);setRed(parent);rightRotate(parent);other = parent.left;}if ((other.left == null || isBlack(other.left)) &&(other.right == null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的setRed(other);node = parent;parent = parentOf(node);} else {if (other.left == null || isBlack(other.left)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。setBlack(other.right);setRed(other);leftRotate(other);other = parent.left;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.left);rightRotate(parent);node = this.mRoot;break;}}}if (node != null)setBlack(node);}

删除操作的情况总结:

现象说明处理策略
Case 1x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。(01) 将x的兄弟节点设为“黑色”。(02) 将x的父节点设为“红色”。(03) 对x的父节点进行左旋。(04) 左旋后,重新设置x的兄弟节点。
Case 2x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。(01) 将x的兄弟节点设为“红色”。(02) 设置“x的父节点”为“新的x节点”。
Case 3x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。(01) 将x兄弟节点的左孩子设为“黑色”。(02) 将x兄弟节点设为“红色”。(03) 对x的兄弟节点进行右旋。(04) 右旋后,重新设置x的兄弟节点。
Case 4x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。(01) 将x父节点颜色 赋值给 x的兄弟节点。(02) 将x父节点设为“黑色”。(03) 将x兄弟节点的右子节设为“黑色”。(04) 对x的父节点进行左旋。(05) 设置“x”为“根节点”。

删除修正的CASE的图示:

  • CASE1

在这里插入图片描述

  • CASE2

在这里插入图片描述

  • CASE3

在这里插入图片描述

  • CASE4

在这里插入图片描述

OK了!红黑树的核心原理以及介绍讲解基本完毕,以下贴出个人的代码实现以供学习:

package com.flyingstars.www.rb;/*** @param <T>* @author linxu* @date 2019/5/3* <p>红黑树的实现</p>* <p>继承比较器</p>*/public class RBTree<T extends Comparable<T>> {private RBTNode<T> mRoot;    // 根结点private static final boolean RED = false;private static final boolean BLACK = true;public class RBTNode<T extends Comparable<T>> {boolean color;        // 颜色T key;                // 关键字(键值)RBTNode<T> left;    // 左孩子RBTNode<T> right;    // 右孩子RBTNode<T> parent;    // 父结点public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {this.key = key;this.color = color;this.parent = parent;this.left = left;this.right = right;}public T getKey() {return key;}public String toString() {return "" + key + (this.color == RED ? "(R)" : "B");}}public RBTree() {mRoot = null;}private RBTNode<T> parentOf(RBTNode<T> node) {return node != null ? node.parent : null;}private boolean colorOf(RBTNode<T> node) {return node != null ? node.color : BLACK;}private boolean isRed(RBTNode<T> node) {return ((node != null) && (node.color == RED)) ? true : false;}private boolean isBlack(RBTNode<T> node) {return !isRed(node);}private void setBlack(RBTNode<T> node) {if (node != null)node.color = BLACK;}private void setRed(RBTNode<T> node) {if (node != null)node.color = RED;}private void setParent(RBTNode<T> node, RBTNode<T> parent) {if (node != null)node.parent = parent;}private void setColor(RBTNode<T> node, boolean color) {if (node != null)node.color = color;}/** 前序遍历"红黑树"*/private void preOrder(RBTNode<T> tree) {if (tree != null) {System.out.print(tree.key + " ");preOrder(tree.left);preOrder(tree.right);}}public void preOrder() {preOrder(mRoot);}/** 中序遍历"红黑树"*/private void inOrder(RBTNode<T> tree) {if (tree != null) {inOrder(tree.left);System.out.print(tree.key + " ");inOrder(tree.right);}}public void inOrder() {inOrder(mRoot);}/** 后序遍历"红黑树"*/private void postOrder(RBTNode<T> tree) {if (tree != null) {postOrder(tree.left);postOrder(tree.right);System.out.print(tree.key + " ");}}public void postOrder() {postOrder(mRoot);}/** (递归实现)查找"红黑树x"中键值为key的节点*/private RBTNode<T> search(RBTNode<T> x, T key) {if (x == null)return x;int cmp = key.compareTo(x.key);if (cmp < 0)return search(x.left, key);else if (cmp > 0)return search(x.right, key);elsereturn x;}public RBTNode<T> search(T key) {return search(mRoot, key);}/** (非递归实现)查找"红黑树x"中键值为key的节点*/private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) {while (x != null) {int cmp = key.compareTo(x.key);if (cmp < 0)x = x.left;else if (cmp > 0)x = x.right;elsereturn x;}return x;}public RBTNode<T> iterativeSearch(T key) {return iterativeSearch(mRoot, key);}/** 查找最小结点:返回tree为根结点的红黑树的最小结点。*/private RBTNode<T> minimum(RBTNode<T> tree) {if (tree == null)return null;while (tree.left != null)tree = tree.left;return tree;}public T minimum() {RBTNode<T> p = minimum(mRoot);if (p != null)return p.key;return null;}/** 查找最大结点:返回tree为根结点的红黑树的最大结点。*/private RBTNode<T> maximum(RBTNode<T> tree) {if (tree == null)return null;while (tree.right != null)tree = tree.right;return tree;}public T maximum() {RBTNode<T> p = maximum(mRoot);if (p != null)return p.key;return null;}/** 找结点(x)的后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"。*/public RBTNode<T> successor(RBTNode<T> x) {// 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。if (x.right != null)return minimum(x.right);// 如果x没有右孩子。则x有以下两种可能:// (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。// (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。RBTNode<T> y = x.parent;while ((y != null) && (x == y.right)) {x = y;y = y.parent;}return y;}/** 找结点(x)的前驱结点。即,查找"红黑树中数据值小于该结点"的"最大结点"。*/public RBTNode<T> predecessor(RBTNode<T> x) {// 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。if (x.left != null)return maximum(x.left);// 如果x没有左孩子。则x有以下两种可能:// (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。// (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。RBTNode<T> y = x.parent;while ((y != null) && (x == y.left)) {x = y;y = y.parent;}return y;}/** 对红黑树的节点(x)进行左旋转** 左旋示意图(对节点x进行左旋):*      px                              px*     /                               /*    x                               y*   /  \      --(左旋)-.           / \                #*  lx   y                         x  ry*     /   \                      /    \*    ly   ry                    lx     ly***/private void leftRotate(RBTNode<T> x) {// 设置x的右孩子为yRBTNode<T> y = x.right;// 将 “y的左孩子” 设为 “x的右孩子”;// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”x.right = y.left;if (y.left != null)y.left.parent = x;// 将 “x的父亲” 设为 “y的父亲”y.parent = x.parent;if (x.parent == null) {this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点} else {if (x.parent.left == x)x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”elsex.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”}// 将 “x” 设为 “y的左孩子”y.left = x;// 将 “x的父节点” 设为 “y”x.parent = y;}/** 对红黑树的节点(y)进行右旋转** 右旋示意图(对节点y进行左旋):*            py                               py*           /                                /*          y                                x*         /  \      --(右旋)-.            /  \                     #*        x   ry                           lx   y*       / \                                   / \                   #*      lx  rx                                rx  ry**/private void rightRotate(RBTNode<T> y) {// 设置x是当前节点的左孩子。RBTNode<T> x = y.left;// 将 “x的右孩子” 设为 “y的左孩子”;// 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”y.left = x.right;if (x.right != null)x.right.parent = y;// 将 “y的父亲” 设为 “x的父亲”x.parent = y.parent;if (y.parent == null) {this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点} else {if (y == y.parent.right)y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”elsey.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”}// 将 “y” 设为 “x的右孩子”x.right = y;// 将 “y的父节点” 设为 “x”y.parent = x;}/** 红黑树插入修正函数** 在向红黑树中插入节点之后(失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 插入的结点        // 对应《算法导论》中的z*/private void insertFixUp(RBTNode<T> node) {RBTNode<T> parent, gparent;// 若“父节点存在,并且父节点的颜色是红色”while (((parent = parentOf(node)) != null) && isRed(parent)) {gparent = parentOf(parent);//若“父节点”是“祖父节点的左孩子”if (parent == gparent.left) {// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.right;if ((uncle != null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是右孩子if (parent.right == node) {RBTNode<T> tmp;leftRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是左孩子。setBlack(parent);setRed(gparent);rightRotate(gparent);} else {    //若“z的父节点”是“z的祖父节点的右孩子”// Case 1条件:叔叔节点是红色RBTNode<T> uncle = gparent.left;if ((uncle != null) && isRed(uncle)) {setBlack(uncle);setBlack(parent);setRed(gparent);node = gparent;continue;}// Case 2条件:叔叔是黑色,且当前节点是左孩子if (parent.left == node) {RBTNode<T> tmp;rightRotate(parent);tmp = parent;parent = node;node = tmp;}// Case 3条件:叔叔是黑色,且当前节点是右孩子。setBlack(parent);setRed(gparent);leftRotate(gparent);}}// 将根节点设为黑色setBlack(this.mRoot);}/** 将结点插入到红黑树中** 参数说明:*     node 插入的结点        // 对应《算法导论》中的node*/private void insert(RBTNode<T> node) {int cmp;RBTNode<T> y = null;RBTNode<T> x = this.mRoot;// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。while (x != null) {y = x;cmp = node.key.compareTo(x.key);if (cmp < 0)x = x.left;elsex = x.right;}node.parent = y;if (y != null) {cmp = node.key.compareTo(y.key);if (cmp < 0)y.left = node;elsey.right = node;} else {this.mRoot = node;}// 2. 设置节点的颜色为红色node.color = RED;// 3. 将它重新修正为一颗二叉查找树insertFixUp(node);}/** 新建结点(key),并将其插入到红黑树中** 参数说明:*     key 插入结点的键值*/public void insert(T key) {RBTNode<T> node = new RBTNode<T>(key, BLACK, null, null, null);// 如果新建结点失败,则返回。if (node != null)insert(node);}/** 红黑树删除修正函数** 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;* 目的是将它重新塑造成一颗红黑树。** 参数说明:*     node 待修正的节点*/private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {RBTNode<T> other;while ((node == null || isBlack(node)) && (node != this.mRoot)) {if (parent.left == node) {other = parent.right;if (isRed(other)) {// Case 1: x的兄弟w是红色的setBlack(other);setRed(parent);leftRotate(parent);other = parent.right;}if ((other.left == null || isBlack(other.left)) &&(other.right == null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的setRed(other);node = parent;parent = parentOf(node);} else {if (other.right == null || isBlack(other.right)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。setBlack(other.left);setRed(other);rightRotate(other);other = parent.right;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.right);leftRotate(parent);node = this.mRoot;break;}} else {other = parent.left;if (isRed(other)) {// Case 1: x的兄弟w是红色的setBlack(other);setRed(parent);rightRotate(parent);other = parent.left;}if ((other.left == null || isBlack(other.left)) &&(other.right == null || isBlack(other.right))) {// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的setRed(other);node = parent;parent = parentOf(node);} else {if (other.left == null || isBlack(other.left)) {// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。setBlack(other.right);setRed(other);leftRotate(other);other = parent.left;}// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。setColor(other, colorOf(parent));setBlack(parent);setBlack(other.left);rightRotate(parent);node = this.mRoot;break;}}}if (node != null)setBlack(node);}/** 删除结点(node),并返回被删除的结点** 参数说明:*     node 删除的结点*/private void remove(RBTNode<T> node) {RBTNode<T> child, parent;boolean color;// 被删除节点的"左右孩子都不为空"的情况。if ((node.left != null) && (node.right != null)) {// 被删节点的后继节点。(称为"取代节点")// 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。RBTNode<T> replace = node;// 获取后继节点replace = replace.right;while (replace.left != null)replace = replace.left;// "node节点"不是根节点(只有根节点不存在父节点)if (parentOf(node) != null) {if (parentOf(node).left == node)parentOf(node).left = replace;elseparentOf(node).right = replace;} else {// "node节点"是根节点,更新根节点。this.mRoot = replace;}// child是"取代节点"的右孩子,也是需要"调整的节点"。// "取代节点"肯定不存在左孩子!因为它是一个后继节点。child = replace.right;parent = parentOf(replace);// 保存"取代节点"的颜色color = colorOf(replace);// "被删除节点"是"它的后继节点的父节点"if (parent == node) {parent = replace;} else {// child不为空if (child != null)setParent(child, parent);parent.left = child;replace.right = node.right;setParent(node.right, replace);}replace.parent = node.parent;replace.color = node.color;replace.left = node.left;node.left.parent = replace;if (color == BLACK)removeFixUp(child, parent);node = null;return;}if (node.left != null) {child = node.left;} else {child = node.right;}parent = node.parent;// 保存"取代节点"的颜色color = node.color;if (child != null)child.parent = parent;// "node节点"不是根节点if (parent != null) {if (parent.left == node)parent.left = child;elseparent.right = child;} else {this.mRoot = child;}if (color == BLACK)removeFixUp(child, parent);node = null;}/** 删除结点(z),并返回被删除的结点** 参数说明:*     tree 红黑树的根结点*     z 删除的结点*/public void remove(T key) {RBTNode<T> node;if ((node = search(mRoot, key)) != null)remove(node);}/** 销毁红黑树*/private void destroy(RBTNode<T> tree) {if (tree == null)return;if (tree.left != null)destroy(tree.left);if (tree.right != null)destroy(tree.right);tree = null;}public void clear() {destroy(mRoot);mRoot = null;}/** 打印"红黑树"** key        -- 节点的键值* direction  --  0,表示该节点是根节点;*               -1,表示该节点是它的父结点的左孩子;*                1,表示该节点是它的父结点的右孩子。*/private void print(RBTNode<T> tree, T key, int direction) {if (tree != null) {if (direction == 0)    // tree是根节点System.out.printf("%2d(B) is root\n", tree.key);else                // tree是分支节点System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree) ? "R" : "B", key, direction == 1 ? "right" : "left");print(tree.left, tree.key, -1);print(tree.right, tree.key, 1);}}public void print() {if (mRoot != null)print(mRoot, mRoot.key, 0);}
}

结言:关于红黑树的学习之路远不止于此,我后续仍会通过不断学习完善红黑树的博文,以上内容参考《算法导论》、《数据结构与算法分析》等资料。


http://chatgpt.dhexx.cn/article/cNRdD107.shtml

相关文章

JDK14的新特性:JFR,JMC和JFR事件流

文章目录 简介JFRJMC创建JFR分析JFR JFR事件JFR事件流总结 简介 Java Flight Recorder&#xff08;JFR&#xff09;是JVM的诊断和性能分析工具。它可以收集有关JVM以及在其上运行的Java应用程序的数据。JFR是集成到JVM中的&#xff0c;所以JFR对JVM的性能影响非常小&#xff0…

Cocos2d-x利用jni调用java层代码

jni的意思是java本地调用&#xff0c;通过jni可以实现java层代码和其他语言写得代码进行交互。在cocos2d-x中&#xff0c;如果想要在c层调用java层的代码&#xff0c;就是通过jni技术。通过调用java层的代码&#xff0c;我们就可以在Android平台下实现一些引擎没有提供给我们的…

红黑树,学习红黑树,jdk1.8之后的新知识

红黑二叉树 1.引言 HashMap的基本结构是数组&#xff0c;链表和红黑树。以数组为基本形态&#xff0c;数组中的元素先以链表形式储存&#xff0c;当链表的长度超过8时&#xff08;包含数组上的那个链表头&#xff09;就会将链表转换为红黑树&#xff0c;以加快修改和查询效率…

JavaScript 红黑树

一、树的平衡性 为了能以较快的时间O(logN)来操作一棵树,我们需要保证树总是平衡的: 至少大部分是平衡的,那么时间复杂度也是接近O(logN)的也就是说树中每个节点左边的子孙节点的个数应该尽可能的等于右边的子孙节点的个数.常见的平衡树有哪些呢?AVL树: AVL树是最早的一种平衡…

最详细的对红黑树性质理解

红黑树的性质&#xff1a; 在了解红黑树之前&#xff0c;建议先去了解一下什么是二叉搜索树。 因为红黑树属于二叉搜索树特殊的分支&#xff0c;所以建议先去了解一下二叉搜索树。 二叉搜索树&#xff1a;https://blog.csdn.net/Falling_stars_/article/details/115536511 红…

JDK1.8之后,HashMap转变红黑树

当链表长度大于8并且数组长度大于64时&#xff0c;才会转换为红黑树 根据源码注释&#xff1a; 1.TreeNodes占用空间是普通Nodes的两倍。 只有当bin&#xff08;bin就是bucket-桶&#xff0c;即HashMap中hashCode值一样的元素保存的地方&#xff09;包含足够多的节点时才会转成…

jdk1.8的HashMap中的红黑树插入,为什么是红黑树而不是AVL树

jdk1.8HashMap的源码 树节点 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unli…

浅谈红黑树

这篇文章的删除操作参考这篇博客博客地址 聊红黑树之前我想先来谈谈B树&#xff08;多路平衡查找树&#xff09;&#xff0c;为什么来聊这个东西呢&#xff0c;其实红黑树就是一个B树。了解B树后对红黑树的理解会比较有帮助。 首先是2、3、4节点。 红黑树中单个黑节点对应B树中…

红黑树的理解与代码实现

红黑树 我们知道对于二叉搜索树而言&#xff0c;无法保证树的平衡性&#xff0c;从而使得进行操作的时候时间复杂度在O(logn)与O(n)之间。这样是不稳定的。而2-3树则借助于3-结点和临时的4-结点&#xff0c;通过分解&#xff0c;解决了平衡问题。例如对于插入&#xff0c;在最终…

图解TreeMap的红黑树平衡操作fixAfterInsertion(),接着手撕红黑树添加节点

一、前言 啥也不想说&#xff0c;就卷、卷技术&#xff1b;手撕红黑树搞起。 1、红黑树简介 红黑树就是一种平衡的二叉查找树&#xff0c;其有五个特点&#xff1a; 1.每个节点要么是红⾊&#xff0c;要么是⿊⾊&#xff1b; 2. 根节点⼀定是⿊⾊的&#xff1b; 3. 每个叶⼦…

有人在jdk源码里下毒【class.newInstance() bug复现】

如图 在用反射的时候&#xff0c;发现这个方法被idea划横杠了 稍加思索后发现是这方法从jdk9开始弃用了&#xff0c;倒不影响使用&#xff0c;对象还是能正常射出来&#xff0c;就是看着很难受 &#xff08;最近刚把本地开发机从8升到11&#xff0c;难怪&#xff09; 说下我…

红黑树理解以及Java实现

红黑树本身并不复杂&#xff0c;只是在插入删除的时候情况比较多&#xff0c;如果强行记忆的话会显得比较困难&#xff0c;而且容易忘记。所以以前对红黑树一直没有很好的掌握。恰好这次借着复习数据结构的机会&#xff0c;静下心来仔细的学习了一下红黑树&#xff0c;并用Java…

Java 9 vs Java 8:引入模块化和JShell的全面升级

Java 9 是 Java 语言的一个重大版本升级&#xff0c;带来了许多新的特性和改进。 在本篇博客中&#xff0c;我将为您介绍 Java 9 的一些重要特性。 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; 目录 一、模块化二、JShell三…

HashMap 在 JDK 1.8 后新增的红黑树结构

读完本文你将了解到&#xff1a; 点击查看 Java 集合框架深入理解 系列 - - 乾杯传统 HashMap 的缺点HashMap 在 JDK 18 中新增的数据结构 红黑树HashMap 中关于红黑树的三个关键参数HashMap 在 JDK 18 中新增的操作桶的树形化 treeifyBinHashMap 在 JDK 18 中新增的操作 红黑树…

epoll底层红黑树使用部分源码剖析:为什么使用红黑树以及如何使用红黑树

我们知道epoll的底层使用了红黑树来管理文件描述符&#xff0c;为什么会选择红黑树这种结构呢&#xff1f; 以下是个人理解&#xff1a; epoll和poll的一个很大的区别在于&#xff0c;poll每次调用时都会存在一个将pollfd结构体数组中的每个结构体元素从用户态向内核态中的一…

HashMap在jdk1.8为何引入了红黑树?

原创不易,麻烦点个关注,点个赞,谢谢各位。 二叉查找树 二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若任…

从电路的角度理解特征阻抗

传输显得特征阻抗不是真实的电阻&#xff0c;微波技术课程会从波的角度描述特征阻抗&#xff0c;这次试图从电路的角度来理解 无损传输线是分布的L C 网络&#xff0c;假设是无限长传输线 从a,b两点看入的阻抗是相等的&#xff0c;所以可以简化成下图&#xff1a; 化简可得 这…

同轴电缆阻抗总结(电阻、阻抗、特性阻抗)

文章目录 同轴电缆电阻、阻抗、特性阻抗电阻阻抗&#xff08;Impedance&#xff09;特性阻抗 总结 同轴电缆 同轴电缆是一种电线及信号传输线&#xff0c;一般是由四层物料造成&#xff1a;最内里是一条导电铜线&#xff0c;线的外面有一层塑胶&#xff08;作绝缘体、电介质之…

传输线阻抗方程的推导

在传输线理论中&#xff0c;当一段特征阻抗为 Z 0 Z_0 Z0​ 的传输线的终端连接了一个阻抗为 Z L Z_L ZL​ 的负载时&#xff0c;看向这段传输线的输入阻抗 Z i n Z_{in} Zin​ 将不再是 Z 0 Z_0 Z0​。 传输线阻抗方程 (Transmission Line Impedance Equation) 就是计算…

PCB阻抗计算

阻抗匹配是指在能量传输时&#xff0c;要求负载阻抗要和传输线的特征阻抗相等&#xff0c;此时的传输不会产生反射&#xff0c;这表明所有能量都被负载吸收了。反之则在传输中有能量损失。在高速PCB设计中&#xff0c;阻抗的匹配与否关系到信号的质量优劣&#xff0c;下面简单介…