学习红黑树前,需要学习基础知识:二叉树,平衡二叉树,2-3查找树,可参考下面文章或其他文章
sheng的学习笔记-二叉树(BST)_coldstarry的博客-CSDN博客
sheng的学习笔记-平衡二叉树(AVL)和3+4重构_coldstarry的博客-CSDN博客
sheng的学习笔记-2-3查找树_coldstarry的博客-CSDN博客
红黑树基础知识
红黑树定义
红黑树是一棵二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子简单路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡
红黑树性质
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的两个子节点必须是黑色的。
(5) 对每个节点,从该节点到其所有后代叶子节点的简单路径上,都包含相同数目的黑色节点,换句话说,红黑树是黑色节点平衡的树。
在插入新元素的时候,新元素都是红色节点,插入进去后,根据上述的性质,做旋转和重新染色
红黑树图像
红黑树示意图
红黑树动图
可以用这个网站看下红黑树的动态处理是什么样的
红黑树与2-3树相互转换
红黑树的染色和旋转,逻辑很复杂,是通过与2-3树转换,推导出红黑色的旋转和染色
一.局部变换
考虑如果在2-节点上挂新的键并不会破坏2-3树的平衡结构。可是在3-节点上挂新的键,可能的变化却多达6种。这个临时的4-节点可能是根节点,可能是一个2-节点的左子节点或者右子节点,也可能是3-节点的左子节点、中子节点或者右子节点。2-3树插入算法的根本在于这些变换都是局部的:除了相关的节点和链接之外不必修改该或者检查树的其他部分。因此,每次变换中,变更的链接数量都不会超过一个很小的常数。(下面的部分是2-3树基础,不懂的要去看下这部分)
每一个变换都会将4-节点中的一个键送入它的父节点中,并重构相应的链接,但不必涉及树的其他部分。这也是2-3树的重要特性:一次聚聚变换不会影响树的有序和平衡性:
二、红黑树与2-3-4树的相互转换
尽管2-3-4树一种高效的结构并提供了自平衡特性,但是它的算法过于复杂以至于在一般的情况下我们并不愿意使用这样的结构作为首选方案。但是红黑树则不然,它的实现算法非常简单,代码量也不大。但理解整个红黑树的构造过程却需要一番仔细的探究。
红黑二叉查找树背后的基本思想是用标准的二叉查找树和一些额外信息来表示2-3-4树。红黑树和2-3-4树的转换关系,是为了理解在插入和删除过程中,红黑树重新染色和旋转逻辑
2-3-4树是指
支持2节点(可以有2个子节点),3节点(可以有3个子节点),4节点(可以有4个子节点)
红色和黑色节点相邻,转换为第二个图
一个黑的有两个红色子节点,转化为第三个图
所有红黑相间的,都是上黑下红
旋转
旋转分为左旋和右旋,具体看
sheng的学习笔记-平衡二叉树(AVL)和3+4重构_coldstarry的博客-CSDN博客
中详细的左旋和右旋
性能总结
新增节点
根据2-3-4树理解红黑树旋转
下图中注意,如果是第三个图,11,12,13这三个点,加上14,会将11和13从红色变为黑色,12变为红色
红黑树与2-3-4树转换图
插入一个节点的完整示意图
新插入z节点是红色节点,如果父节点是黑色的,不用管 (见红黑树与2-3-4树转换图 的图一),因为不影响黑色平衡,父节点是红色的,做以下旋转和染色
情况1:父节点是红色的,看叔叔节点(爷爷节点的另一个子节点),叔叔节点是红色的,就将:爷爷节点变为红色,叔叔和父亲节点都变成黑色,然后爷爷节点重新变成z节点进行判断 (见红黑树与2-3-4树转换图 的图三)
情况2:父节点是红色的,叔叔节点是黑色的,当前节点是右孩子,做 左旋,情况2中左旋后,2的右节点变成5,2成为7的左子节点
情况3:父节点是红色的,叔叔节点是黑色的,当前节点是左孩子(无论是通过情况2转换成情况3,还是本来就是情况3),将父节点改成黑色,爷爷节点改成红色,然后做右旋
完整的旋转和染色示意图
删除节点
红黑树和二叉树的删除比较
红黑树的删除,跟二叉树的删除很像(详见:sheng的学习笔记-二叉树(BST)_coldstarry的博客-CSDN博客):
- 删除的节点是叶子节点,直接删除
- 删除的节点有一个子节点,用子节点替代
- 删除的节点有两个子节点,找到后继节点,用后继节点替代删除节点,用后继节点的右节点替代后继节点
但红黑树的删除伴随着重新染色和旋转,这个旋转是为了红黑颜色符合红黑树的定义
红黑树的删除总体概要图和代码
下面是在删除节点后,进行旋转和染色修复
注意,算法导论中,引用T.nil来表示空节点,在红黑树中最终的叶子结点都是空节点,也就是T.nil,但是我实现的方式没有用这个,我的代码逻辑和算法导论的代码逻辑不一样,但是参考算法导论的思维进行编写。算法导论的写法更简洁,我写的复杂一点,不过把场景分开列举和编写代码
场景如下:
需要注意:
场景1(兄弟节点为红色)旋转后,是为了重新更新兄弟节点为黑色,走到场景二中。在兄弟节点w为黑色时,如果w子节点都是黑色,旋转后更新x重新走一次while循环。
如果w的子节点中有红色节点,必须是w的右节点为红色节点(否则就要走情况3,做一些旋转,把左节点的红色节点挪到右侧),然后将w右子节点(原红色)变黑再旋转
基本想法就是,左侧一个黑色节点被删除了,右侧肯定也要同步删除一个黑色节点,或者把右侧的一个红色节点变成黑色节点,达到左右的黑色节点个数平衡的目的。即:
- 场景1=》场景2=》重复循环
- 场景1=》场景3=》场景4=》终止
情况1:x的兄弟节点w是红色的
情况2:x的兄弟节点w是黑色且w的两个孩子都为黑色
情况3:x的兄弟节点w是黑色且w的左孩子为红色,右孩子是黑色
情况4:x的兄弟节点w是黑色且w的右孩子是红色的
代码
先搞个抽象类,便于实现不同细节的红黑树。注意,我在删除时用的是查找后继节点,有的算法是查找前驱节点
package algorithm.RBT;
//二叉树
public abstract class RBTAbstract {
public static final boolean RED = true;
public static final boolean BLACK = false;
public int count = 0;
// 二叉树的根节点
public RBTNode root;
/**
* 查询
*
* @return
*/
public abstract RBTNode find(int o);
/**
* 插入
*
* @param o
*/
public abstract void insert(RBTNode o);
/**
* 删除
*
* @param o
*/
public abstract boolean delete(RBTNode o);
/**
* 节点个数
*
* @return
*/
public int count() {
return this.count;
}
/**
* 打印整个树
*
* @return
*/
public abstract void inorderTreeWalk(RBTNode myNode);
}
class RBTNode {
public int data;
public boolean color;
public RBTNode leftNode;
public RBTNode rightNode;
public RBTNode parent;
public RBTNode(int key) {
data = key;
}
public int getKey() {
return data;
}
public void setData(int data) {
this.data = data;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public RBTNode getLeft() {
return leftNode;
}
public void setLeftNode(RBTNode leftNode) {
this.leftNode = leftNode;
}
public RBTNode getRight() {
return rightNode;
}
public void setRightNode(RBTNode rightNode) {
this.rightNode = rightNode;
}
public RBTNode getParent() {
return parent;
}
public void setParent(RBTNode parent) {
this.parent = parent;
}
@Override
public String toString() {
return "MyNode{" +
"data=" + data +
'}';
}
}
弄个调用类
package algorithm.RBT;
import java.util.Scanner;
public class RBTreeTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
MyRBT rbt = new MyRBT();
//构造初始红黑树
for (int i = 1; i < 10; i++) {
RBTNode key = new RBTNode(Integer.valueOf(i));
rbt.insert(key);
}
TreeOperation.show(rbt.root);
String operation = "insert";
while (true) {
System.out.println("请输入操作:");
operation = scanner.next();
System.out.println("请输入key:");
String strKey = scanner.next();
System.out.println("操作:" + operation + "数值:" + strKey);
RBTNode key = new RBTNode(Integer.valueOf(strKey));
if ("insert".equals(operation)) {
rbt.insert(key);
} else {
rbt.delete(key);
}
TreeOperation.show(rbt.root);
}
}
}
再来个打印类,可以用颜色打印出红黑树节点,便于观察,TreeOperation,根据节点颜色在控制台打印不同的颜色,便于观察
package algorithm.RBT;
public class TreeOperation {
/*
树的结构示例:
1
/ \
2 3
/ \ / \
4 5 6 7
*/
// 用于获得树的层数
public static int getTreeDepth(RBTNode root) {
return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
}
private static void writeArray(RBTNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
// 保证输入的树不为空
if (currNode == null) return;
// 先将当前节点保存到二维数组中
res[rowIndex][columnIndex] = currNode.getKey() + "-" + (currNode.isColor() ? "R" : "B") + "";
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth) return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
if (currNode.getLeft() != null) {
res[rowIndex + 1][columnIndex - gap] = "/";
writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
if (currNode.getRight() != null) {
res[rowIndex + 1][columnIndex + gap] = "\\";
writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
}
}
public static void show(RBTNode root) {
if (root == null) System.out.println("EMPTY!");
// 得到树的深度
int treeDepth = getTreeDepth(root);
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth];
// 对数组进行初始化,默认为一个空格
for (int i = 0; i < arrayHeight; i++) {
for (int j = 0; j < arrayWidth; j++) {
res[i][j] = " ";
}
}
// 从根节点开始,递归处理整个树
// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
writeArray(root, 0, arrayWidth / 2, res, treeDepth);
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line : res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i++) {
sb.append(line[i]);
if (line[i].length() > 1 && i <= line.length - 1) {
i += line[i].length() > 4 ? 2 : line[i].length() - 1;
}
}
String outPut = sb.toString();
// System.out.println(sb.toString());
int startIndex;
String color = "";
//每一行的值,都是 VALUE1-R VALUE2-B 这种形式,每一行做打印
while (true) {
startIndex = 0;
int index = outPut.indexOf("-");
color = "\33[1m\33[30m";//默认是黑色
if (-1 == index) {
// 没有-的值,原样输出打印
System.out.print(color + outPut);
break;
}
if (outPut.charAt(index + 1) == 'R') {
color = "\33[1m\33[31m";//红色
} else {
color = "\33[1m\33[30m";//黑色
}
// 根据值选择不同的颜色打印,不想每次看显示R还是B来判断是红色还是黑色,显示的直观一点
System.out.print(color + outPut.substring(startIndex, index) + " ");
// 一行可能有多个值,每个值都要重新判断是什么颜色的,并打印对应的颜色,打印完一个值,只保留未打印的剩余部分
outPut = outPut.substring(index + 2);
}
System.out.println();
}
}
}
红黑树实现类
package algorithm.RBT;
public class MyRBT extends RBTAbstract {
@Override
public RBTNode find(int o) {
RBTNode node = this.root;
while (node != null) {
if (o > node.data) {
node = node.rightNode;
} else if (o < node.data) {
node = node.leftNode;
} else {
return node;
}
}
return node;
}
@Override
public void insert(RBTNode o) {
//插入新元素
RBTNode x = this.root;
RBTNode parent = null;
// 新插入的节点都是红色节点
o.color = RED;
// 新节点的左右子节点,都是空的
o.leftNode = null;
o.rightNode = null;
// 如果树是空的,新的节点就是root,根节点必须是黑色节点
if (this.root == null) {
o.color = BLACK;
this.root = o;
return;
}
while (x != null) {
//新节点先给个临时变量y,用于在退出while循环时使用
parent = x;
if (o.data < x.data) {
// 新插入的节点小于当前节点,在当前节点的左子树中继续查找
x = x.leftNode;
} else if (o.data > x.data) {
// 新插入的节点大于当前节点,在当前节点的右子树中继续查找
x = x.rightNode;
} else {
// 如果已经有了这个节点,退出,不作任何操作,此处可自行处理是否做替换等
System.out.printf("插入重复节点,不做任何处理,data:" + o.data);
return;
}
}
if (parent == null) {
//不应该走到这个地方,如果是root,已经在最开始进行处理了
throw new RuntimeException("parent为null,需要人工处理" + o.data);
}
o.parent = parent;
if (o.data < parent.data) {
//新增节点比当前节点小,应该挂到当前节点的左节点
parent.leftNode = o;
} else if (o.data > parent.data) {
//新增节点比当前节点大,应该挂到当前节点的右节点
parent.rightNode = o;
} else {
// 新增节点等于当前节点值,已经在最上面的while循环中处理,不应该走到这个地方
throw new RuntimeException("parent为等于新插入节点,需要人工处理" + o.data);
}
//重新旋转和染色,让红黑树达到黑色节点平衡
insertFixup(o);
}
//重新旋转和染色
public void insertFixup(RBTNode z) {
RBTNode y = null;//z的叔叔节点
//只有入口节点父节点是红色,才需要重新染色处理,如果是黑色,不需要处理
while (isRedNode(z.parent)) {
//z的父节点是左子树
if (z.parent == z.parent.parent.leftNode) {
y = z.parent.parent.rightNode;
// 如果z的父节点和叔叔节点都是红色的,将父节点和叔叔节点改为黑色,爷爷节点改为红色
if (isRedNode(y)) {
// z叔叔节点改为黑色
y.color = BLACK;
// z父节点改为黑色
z.parent.color = BLACK;
// z爷爷节点改为红色
z.parent.parent.color = RED;
// z的爷爷节点给z,重新循环处理
z = z.parent.parent;
} else {
//非红色节点必是黑色节点,即z的叔叔节点是黑色节点
//如果z是右子树,需要做左旋
if (z == z.parent.rightNode) {
z = z.parent;
//对z左旋
leftRotate(z);
}
//将z的父节点染成黑色
z.parent.color = BLACK;
//将z的爷爷染成红色
z.parent.parent.color = RED;
rightRotate(z.parent.parent);
}
} else {
y = z.parent.parent.leftNode;
// 如果z的父节点和叔叔节点都是红色的,将父节点和叔叔节点改为黑色,爷爷节点改为红色
if (isRedNode(y)) {
// z叔叔节点改为黑色
y.color = BLACK;
// z父节点改为黑色
z.parent.color = BLACK;
// z爷爷节点改为红色
z.parent.parent.color = RED;
// z的爷爷节点给z,重新循环处理
z = z.parent.parent;
} else {
//非红色节点必是黑色节点,即z的叔叔节点是黑色节点
//如果z是左子树,需要做右旋
if (z == z.parent.leftNode) {
z = z.parent;
//对z右旋
rightRotate(z);
}
//将z的父节点染成黑色
z.parent.color = BLACK;
//将z的爷爷染成红色
z.parent.parent.color = RED;
leftRotate(z.parent.parent);
}
}
}
//由于上述过程中,根节点可能被染为红色,此处根节点显示设置为黑色
this.root.color = BLACK;
}
@Override
public boolean delete(RBTNode o) {
//如果根节点为空,不需要操作,直接返回
if (null == this.root) {
return false;
}
// 查找需要删除的节点
RBTNode current = find(o.data);
//没有查找到节点,返回为空
if (null == current) {
return false;
}
RBTNode parent = current.parent;
// 如果被删除的节点两个子节点都不为空,找到后继节点,
// 1)将后继节点的值赋给替换节点,这样替换节点的parent和左右两个节点的指针就不需要修改,颜色也不需要变动,完成后继节点对于删除节点的替换
// 2)后继节点会被删除掉,相当于变为待删除的是后继节点,但后继节点的左子节点肯定是空的,右子节点有可能有值,有可能为空
// 这地方比较巧妙,如果修改指针替换节点,需要判断后继节点是不是待删除节点的子节点,分为是子节点和不是子节点两个逻辑分别处理
if (current.rightNode != null && current.leftNode != null) {
//找到后继节点
RBTNode successor = minimum(current.rightNode);
// 将后继节点的值给待删除节点,
current.data = successor.data;
// 后继节点转为待删除节点
current = successor;
}
//注意,这地方不能写else,因为上面可能改动current,改动后还需要重新判断有几个子节点,如果有改动,就只可能有右子节点(或者没有子节点)
// 查找替代节点,看待删除节点右节点如果不为空,就选择右节点,否则选择左节点
RBTNode replacementNode = current.rightNode == null ? current.leftNode : current.rightNode;
if (null != replacementNode) {
//看待删除节点是左节点还是右节点,用于修改待删除节点父节点的指针
if (current.parent.leftNode == current) {
//待删除节点是左节点
current.parent.leftNode = replacementNode;
} else if (current.parent.rightNode == current) {
//待删除节点是右节点
current.parent.rightNode = replacementNode;
} else {
throw new RuntimeException("delete函数在删除结点(非叶子节点)时,无法判断自己是左节点还是右节点,node:" + current);
}
// 待删除节点的父指针给替代节点
replacementNode.parent = current.parent;
// 上述完成替代节点对于待删除节点的替换(包括值,父指针
// 如果待删除节点是黑色,破坏了黑色平衡,需要重新旋转和染色
if (isBlackNode(current)) {
deleteFixup(replacementNode);
}
} else if (current.parent == null) {
//删除根节点,而且也没有替代节点
root = null;
return true;
} else {
// 如果待删除节点的左右子节点都是null,待删除节点是叶子节点
//先重新进行染色和旋转,然后直接将待删除节点删除
if (isBlackNode(current)) {
deleteFixup(current);
}
if (current.parent.leftNode == current) {
current.parent.leftNode = null;
} else if (current.parent.rightNode == current) {
current.parent.rightNode = null;
} else {
throw new RuntimeException("delete函数在删除叶子结点时,无法判断自己是左节点还是右节点,node:" + current);
}
return true;
}
return true;
}
protected void deleteFixup(RBTNode x) {
while (x != this.root && isBlackNode(x)) {
// 修复节点是左子节点
if (x == x.parent.leftNode) {
//找到兄弟节点w
RBTNode w = x.parent.rightNode;
/** 图示的情况1
* B黑 B红(黑变红) D黑
* / \ =》 / \ =》 / \
* A黑(X) D红(兄弟节点w) A黑(X) D黑(w红变黑) B红 E黑
* / \ / \ / \
* C黑 E黑 C黑 E黑 A黑(X) C黑(新的w)
*/
if (isRedNode(w)) {
// 兄弟节点是红色节点,兄弟节点变为黑色,父节点变为红色,左旋,让兄弟节点变为黑色的
w.color = BLACK;
x.parent.color = RED;
leftRotate(x.parent);
// 上面左旋会改变x的父节点的右节点,所以要对兄弟节点w重新赋值
w = x.parent.rightNode;
}
/**
* 现在兄弟节点w是黑色节点
* 如果w(黑色)左右两个节点都是黑色的(就算w是叶子结点,左右节点都是空节点,也认为是黑色节点),
* 图示的情况2
* B(可能红可能黑) B(变为x继续判断)
* / \ =》 / \
* A黑(X) D黑(兄弟节点w) A黑(X) D红(w黑变红)
* / \ / \
* C黑 E黑 C黑 E黑
*/
if (isBlackNode(w.leftNode) && isBlackNode((w.rightNode))) {
//兄弟节点变成红色,父节点变为x重新判断
w.color = RED;
x = x.parent;
//此处其实不写continue也行,但想要明显的表示开始下个循环
continue;
} else {
/**
* 如果兄弟节点w的左子节点是红色,右子节点是黑色的
* w和w左节点互换颜色,右旋
* 图示的情况3
* B(可能红可能黑) B(可能红可能黑) B(可能红可能黑)
* / \ =》 / \ (右旋)=》 / \
* A黑(X) D黑(兄弟节点w) A黑(X) D红(黑变红w) A黑(X) C黑(变为新的w)
* / \ / \ \
* C红 E黑 C黑(红变黑) E黑 D红
* \
* E黑
*/
//下面的写法如果右子节点是黑色,左子节点不可能是黑色的,否则进入上面的流程
if (isBlackNode(w.rightNode)) {
if (isBlackNode(w.leftNode)) {
throw new RuntimeException("deleteFixup出现异常了,左节点和右节点都是黑的,不应该进入到这里,w:" + w);
}
w.color = RED;
w.leftNode.color = BLACK;
rightRotate(w);
w = x.parent.rightNode;
}
//此时w的右子节点必须为红色
if (isBlackNode(w.rightNode)) {
throw new RuntimeException("deleteFixup出现异常了,w右节点是黑的,不应该进入到这里,w:" + w);
}
/**
* 如果兄弟节点w的左子节点是红色,右子节点是黑色的
* w和w左节点互换颜色,右旋
* 图示的情况3
* B(可能红可能黑) B黑 D(可能红可能黑)
* / \ (B,D颜色交换)=》 / \ (左旋)=》 / \
* A黑(X) D黑(兄弟节点w) A黑(X) D(可能红可能黑) B黑 E黑(红变黑)
* / \ / \ / \
* C(可能红可能黑) E红 C(不管) E红 A黑(X) C(不管)
*
*
*/
w.color = x.parent.color;//B,D交换颜色
x.parent.color = BLACK;
w.rightNode.color = BLACK;//E从红色变成黑色
leftRotate(x.parent);//左旋
x = this.root;
}
} else {
//找到兄弟节点w
RBTNode w = x.parent.leftNode;
if (isRedNode(w)) {
// 兄弟节点是红色节点,兄弟节点变为黑色,父节点变为红色,右旋,让兄弟节点变为黑色的
w.color = BLACK;
x.parent.color = RED;
rightRotate(x.parent);
// 上面旋转会改变x的父节点的左节点,所以要对兄弟节点w重新赋值
w = x.parent.leftNode;
}
if (isBlackNode(w.leftNode) && isBlackNode((w.rightNode))) {
//兄弟节点变成红色,父节点变为x重新判断
w.color = RED;
x = x.parent;
//此处其实不写continue也行,但想要明显的表示开始下个循环
continue;
} else {
//下面的写法如果左子节点是黑色,右子节点不可能是黑色的,否则进入上面的流程
if (isBlackNode(w.leftNode)) {
if (isBlackNode(w.rightNode)) {
throw new RuntimeException("deleteFixup出现异常了,左节点和右节点都是黑的,不应该进入到这里,w:" + w);
}
w.color = RED;
w.rightNode.color = BLACK;
leftRotate(w);
w = x.parent.leftNode;
}
//此时w的右子节点必须为红色
if (isBlackNode(w.leftNode)) {
throw new RuntimeException("deleteFixup出现异常了,w右节点是黑的,不应该进入到这里,w:" + w);
}
w.color = x.parent.color;
x.parent.color = BLACK;
w.leftNode.color = BLACK;
rightRotate(x.parent);
x = this.root;
}
}
}
x.color = BLACK;
}
//找到最小子节点
protected RBTNode minimum(RBTNode myNode) {
while (myNode.leftNode != null) {
myNode = myNode.leftNode;
}
return myNode;
}
// 判断是否红色节点
protected boolean isRedNode(RBTNode o) {
// 如果是空节点,应该是黑色节点,因为所有的叶子结点都是空节点,并且都是黑色节点
if (o == null) {
return false;
}
return o.color == RED;
}
// 判断是否黑色节点
protected boolean isBlackNode(RBTNode o) {
return !isRedNode(o);
}
// 左旋
protected void leftRotate(RBTNode o) {
RBTNode x = o;
RBTNode y = x.rightNode;
//先处理x节点
//x的父节点给y
y.parent = x.parent;
if (x.parent == null) {
this.root = y;
} else if (x == x.parent.leftNode) {
x.parent.leftNode = y;
} else {
x.parent.rightNode = y;
}
// x的右节点改为y的左节点
x.rightNode = y.leftNode;
if (y.leftNode != null) {
y.leftNode.parent = x;
}
// y的左节点改为x
y.leftNode = x;
x.parent = y;
}
// 右旋
protected void rightRotate(RBTNode o) {
RBTNode y = o;
RBTNode x = y.leftNode;
//先处理y节点
//y的父节点给x
x.parent = y.parent;
if (y.parent == null) {
this.root = x;
} else if (y == y.parent.leftNode) {
y.parent.leftNode = x;
} else {
y.parent.rightNode = x;
}
// x的右节点改为y的左节点
y.leftNode = x.rightNode;
if (x.rightNode != null) {
x.rightNode.parent = y;
}
// x的右节点改为y
x.rightNode = y;
y.parent = x;
}
@Override
public int count() {
return super.count();
}
@Override
public void inorderTreeWalk(RBTNode myNode) {
}
}
查看结果:
参考文章
7-红黑树核心操作之-新增节点详解及代码实现_哔哩哔哩_bilibili
上面这个B站的哥们,讲的很好,就是后面的删除节点部分有点晕,需要参考算法导论的内容看
书:算法导论