首先声明,这是个人看看完 该红黑树教程。掺杂了个人理解。不是面面俱到,所以如果有小伙伴点击进来想学红黑树,我建议先把视频看一遍,然后在看着我的笔记,默写出红黑树。
红黑树的本质
① 所有节点非红即黑
② 根节点必须为黑色
③ 所有的叶子节点都是黑色(NIL节点。多数情况下隐藏)
④ 没有两两连续的红色节点,也就是说红色节点的孩子节点必须是黑色
⑤ 任意一个节点,到叶子节点所经过的黑色节点数目一样多(简称 “黑高”一致)
以上的性质完成对应的2-3-4树(4阶的B - tree)如果不懂 还是建议把视频看完再来
左旋
private void rotateLeft(RBNode rbNode) {
//记录左边节点
RBNode rNode = rightOfNode(rbNode);
rbNode.right = null;
rNode.parent = parentOfNode(rbNode);
if(rNode.parent != null) {
RBNode p = rNode.parent;
if(leftOfNode(p) == rbNode) {
p.left = rNode;
}else {
p.right = rNode;
}
}else {
head = rNode;
}
RBNode rlNode = leftOfNode(rNode);
if(rlNode != null) {
rbNode.right = rlNode;
rlNode.parent = rbNode;
}
rNode.left = rbNode;
rbNode.parent = rNode;
}
右旋
private void rotateRight(RBNode rbNode) {
RBNode lNode = leftOfNode(rbNode);
rbNode.left = null;
lNode.parent = parentOfNode(rbNode);
if(lNode.parent != null) {
RBNode p = lNode.parent;
if(leftOfNode(p) == rbNode) {
p.left = lNode;
}else {
p.right = lNode;
}
}else {
head = lNode;
}
RBNode lrNode = rightOfNode(lNode);
if(lrNode != null) {
rbNode.left = lrNode;
lrNode.parent = rbNode;
}
lNode.right = rbNode;
rbNode.parent = lNode;
}
插入(我们已知插入节点必须为红色)
① 如果根节点为null, 那么插入节点为根节点,把颜色改成黑色
② 如果插入位置的父亲节点是 黑色 ,那么直接插入。
③ 如果插入位置的父亲节点是 红色,并且叔叔节点也是 红色, 那么把父亲节点和叔叔节点,染黑,把祖父节点 染红。递归处理祖父节点
④ 如果插入位置的父亲节点是 红色, 并且叔叔节点 黑色 或 NIL的情况分一下四种处理方式
- LL: 祖父节点变红,父亲节点变黑, 使 祖父 节点 右转
- LR: 让 父亲 节点 左转,变成 LL情况
- RR: 祖父节点变红,父亲节点变黑, 使 祖父 节点 左转
- RL: 让 父亲 节点 右转,变成 RR情况
注意: 在插入的时间,并不会造成情况 ④ 中,父亲节点为 红色,叔叔节点 存在并为黑色 的情况,因为存在这种情况下,“黑高是不一致的”,出现如上情况只有在 ③ 向上递归造成。
删除
① 被删除的节点没有子节点,如果是 红色 直接删除,黑色,先调节后删除
② 被删除的节点只有一个子节点,让子节点接替用来父亲节点的位置,并把颜色变成 黑色。因为如下图的得出的结果
③ 被删除的节点同时存在左右两个孩子节点
我们可以找到后继节点(大于当前节点的最小值,中序遍历下该节点后一个数),或者前驱节点(小于当前节点的最大值,中序遍历下该节点前一个值)。然后进行替换,在删除该 [前驱 / 后继]节点,该 [前驱 / 后继]节点 必定只存在以下两种情况,没有孩子节点,有一个孩子节点,并且在他的 反方向边。因为如果存在两个孩子,或者在同一个边,那么**[前驱 / 后继]节点**必定就不是该节点。所以可以回到 ① ② 处理
删除黑色节点的处理过程
首先到这里,我们必须保持一个重要的思想,==如何维持黑高?==这个是最为重要的问题,我们可以试着自己画图,通过自己逻辑看看怎么能在删除一个 **黑色节点** 的情况维持下 “黑高”,这其实是一个很有必要的过程。当然是在你熟悉了删除流程。找到真正的兄弟节点 (如果我们要直接删除5节点,或者是删除4(通过后继节点替换方法,也是删除 5节点))
如果你看了视频,就会知道我在画什么,通过这个图,我们知道其实 8节点,不是真正 5的兄弟节点,他的兄弟 是 7/7.5的节点。所以我们需要处理一下,找到真正兄弟节点。看左图,我们很容易得出,让 6节点 左旋即可,但是为了维持 黑高,需要让 8节点变成 黑色, 6节点变成 红色。 如下图
那么你会说,我不会每次都要画个2-3-4树来判断是不是他的真正兄弟吧,其实不用,真正的兄弟必定是 黑色, 如果是 红色。 那么就要经过处理,这是这是一个方向,相反方向就 反着来就行了
有没有奇怪,为什么黑色节点必然有兄弟, 不然 “黑高” 不一致 这句话 。因为从父亲角度来看,我有一边孩子 是黑色 黑高 为 1 ,如果我另外一边 没有一个 黑色 节点 或 没有一个 红色 带 两个 黑色 节点 的话。那么我的黑高 肯定不一致。如下图。已删除左下节点为需要调整节点
左边是不存在的结果,右边是所有存在的结果。不难看出,必定有兄弟。(右下角左后一个图)这里提示一下,为什么左黑只能带有相反侧红色节点,因为如果 同侧也有(相当于他不是真正【前驱/后续节点】)其实就是看你如何处理,反正红色节点 只能存在一边,左右都可以。牢记【前驱/后续节点】的定义
兄弟节点有得借
兄弟有的借的情况,又对应了
因为非真正兄弟,需要作出处理。然后转变如下图
想象一下,兄弟节点要借一个节点给你,同时也要保持这树左右两边的“黑高不会”。以上述删除 左下 黑色节点 为例。我们已知 左分支 黑高 - 1, 那么兄弟分支(右分支)如何在借节点的情况下,还保持自己黑高不变的同时,并维持着搜索树性质 我先把数字填上
如果你能读懂下面这段分析,那么你就会了接下来的操作
我们先不看数字,就看颜色。 左边少了一个 黑色 节点, 兄弟我直接给你一个,然后 黑色 节点。就完事了。 上面三个图,分别能给谁,把 自己孩子节点 染黑 送给对面,就满足了以上的操作。
我们先不看颜色,只看数字。 发现就算少了一个 5. 搜索二叉树的性质没有变。不用做处理
结合两者,我直接把孩子染色给你, 不管给6.5 还是 7.5。 搜素二叉树的性质就会被违背。如下图
那么结合这种情况,直接给你是不可以的了,这辈子都不可能。所以你需要通过父亲来调解。问问父亲该怎么做。
父亲 6:行吧,你少了一个黑色,我把自己变黑给你好了,这样你这边就不缺了(父爱的伟大)
兄弟 7:爸爸走了,那我去顶替爸爸的位置(长子为大)。他什么颜色我就什么什么。稍等一下,我考虑一下我的继承者。我的位置给谁坐。回到如下图
兄弟 7 节点要上去 他 左边的儿子 6.5太小了 没办法继承他的皇位(二叉搜索树的原理)。那么只能 7.5 来继承。那么就会存在 那么如上图所示 右边两个是可以直接解决问题的。
流程: 父亲 6 变黑色 下来 ,兄弟 7 替换 (父亲6位置 和 颜色)。 7.5字节(兄弟7 右儿子)变黑 继承 他父亲的位置。 【6.5节点不一定存在】。他呢 完成可以不动 。有她没她 一定的事。看下图。
情况一 兄弟节点存在一个比他大节点
情况二 兄弟节点两个孩子节点
【6.5节点】完全可以不动,但是因为减少操作次数,一个左旋操作就完成。就不必要麻烦了。
情况三 兄弟节点存在一个比他小节点(稍微麻烦一点)
我们又来演戏一下。
6 父亲 : 左孩子(5节点)你要走了, 那我就变成 黑色 代替 你的位置 。。。。
7 兄弟 : 父亲 6 不在位了, 那么我 先给自己找个继承者。再上去。但是发现,没有一个 够格坐他位置的人(没有比他大的节点)。
6.5 兄弟左节点 : 让我来吧,我来继承 父亲6 的 位置。(越权夺位)
按照上面的情景剧,就会变成如下图的结果
这就是我们最终想到得到的效果图。通过代码,其实有很多东西实现方法。可以不需要按照源码一样转来转去。那么正在的过程是怎么样子的呢。继续看下图
看到上面的图,看代码理解一下
兄弟节点没得借
兄弟没有节点给你了,但是你这边分支黑高减 1 了 ,那兄弟我 也把我的黑色节点 变成红色。相当于 我的分支也减1。 然后其他让父亲去处理就好了。相当于 左侧 因为 删减 黑高 -1 , 右侧 因为 变红 黑高 -1.。该子树确实平衡了,那么上面的子树呢?所以需要 把父亲当做需要调节平衡的节点
总结
红黑树,在死记硬背的过程当中,一定要理解最为重要的5大性质。在处理节点的过程当中一个要思考,如何处理才能保持住红黑树的平衡,就算我们的代码和别人的代码不一样,只要我们能维持红黑树的平和,那么这就是一棵合情合法合理的红黑树。你们大可不必看我的代码,其实别人的代码,其实也看不懂,一定要脑子里面有那幅图。才能处理好一棵红黑树。又回到一开始,如果想要学习红黑树。请看该红黑树教程
完整代码
package com.ccn.redblackTree;
/**
* @Author: CCN
* @Date: 2021-10-21 11:04
*/
public class RBTree<K extends Comparable<K> , V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private RBNode<K,V> head = null;
public RBNode<K, V> getHead() {
return head;
}
public void put(K key , V value) {
RBNode<K, V> rbNode = new RBNode<>();
rbNode.setColor(RED);
rbNode.setKey(key);
rbNode.setValue(value);
if (head == null) {
head = rbNode;
rbNode.setColor(BLACK);
return ;
}
RBNode p = null;
RBNode cur = head;
while(cur != null) {
p = cur;
int cmp = cur.key.compareTo(key);
if(cmp == 0) {
cur.setValue(value);
return;
}else if(cmp > 0) {
cur = cur.left;
}else {
cur = cur.right;
}
}
int cmp = p.key.compareTo(key);
if(cmp > 0) {
p.left = rbNode;
}else {
p.right = rbNode;
}
rbNode.parent = p;
if(isRed(p)) {
fixAfterPut(rbNode);
}
}
public V remove(K key) {
RBNode rbNode = searchNode(key);
if(rbNode == null) return null;
V oldVal = (V) rbNode.value;
//有两个子节点
if (rbNode.left != null && rbNode.right != null) {
//
RBNode subsequent = subsequent(rbNode);
rbNode.key = subsequent.key;
rbNode.value = subsequent.value;
rbNode = subsequent;
}
RBNode replacement = rbNode.left == null ? rbNode.right : rbNode.left;
//有一个子节点
if(replacement != null) {
//有一个子节点,那么删除节点必定是黑色,直接删除,把替换节点改一下颜色
RBNode parent = parentOfNode(rbNode);
if (parent == null) {
head = replacement;
}
else if(leftOfNode(parent) == rbNode) {
parent.left = replacement;
}else {
parent.right = replacement;
}
replacement.parent = parent;
rbNode.parent = rbNode.left = rbNode.right = null;
if (!isRed(rbNode)) {
fixAfterRemove(replacement);
}
}
//没有子节点,但是是删除根节点
else if (rbNode == head){
head = null;
}
//没有子节点
else {
if(!isRed(rbNode)) {
fixAfterRemove(rbNode);
}
RBNode parent = parentOfNode(rbNode);
if(leftOfNode(parent) == rbNode) {
parent.left = null;
}else {
parent.right = null;
}
rbNode.parent = null;
}
return oldVal;
}
private void fixAfterRemove(RBNode rbNode) {
while(rbNode != head && !isRed(rbNode)) {
//找到真正的兄弟
RBNode parent = parentOfNode(rbNode);
//黑色节点必然有兄弟,不然黑高不一致
RBNode brother = parent.left == rbNode ? parent.right : parent.left;
if (isRed(brother)) {
setColor(parent, RED);
setColor(brother, BLACK);
if(parent.left == brother) {
rotateLeft(parent);
}else {
rotateRight(parent);
}
parent = parentOfNode(rbNode);
brother = parent.left == rbNode ? parent.right : parent.left;
}
//兄弟没得借
if(brother.left == null && brother.right == null) {
//也就是兄弟也是一个人。因为删除节点是黑色,那么真正的兄弟节点必定也是黑色。让他变成红色,维持左右两边黑高
setColor(brother, RED);
//但是为了维持整个分支的黑高,需要找到一个最先为红色根节点,让他变成黑色/或者进行其他的平衡处理。
rbNode = parentOfNode(rbNode);
}
//兄弟有的借
else {
if (parent.right == brother) {
//对应234树 3节点 且 上黑 左下红的情况。特殊处理
if(brother.right == null) {
//因为 只有一个左孩子,如果借那么就全部都给了。要右转保留一个
setColor(brother, RED);
setColor(brother.left, BLACK);
rotateRight(brother);
//因为兄弟节点转动了,改变了真正兄弟节点的位置。重新找到兄弟节点
brother = parentOfNode(brother);
}
parent = parentOfNode(rbNode);
setColor(brother, parent.color);
setColor(brother.right, BLACK);
setColor(parent, BLACK);
rotateLeft(parent);
// rbNode = head 也等价于 break, 下一轮不做了
break;
}
else {
//情况三
if(brother.left == null) {
//因为 只有一个左孩子,如果借那么就全部都给了。要右转保留一个
setColor(brother, RED);
setColor(brother.right, BLACK);
rotateLeft(brother);
//因为兄弟节点转动了,改变了真正兄弟节点的位置。重新找到兄弟节点
brother = parentOfNode(brother);
}
//情况一
parent = parentOfNode(rbNode);
setColor(brother, parent.color);
setColor(brother.left, BLACK);
setColor(parent, BLACK);
rotateLeft(parent);
// rbNode = head 也等价于 break, 下一轮不做了
break;
}
}
}
setColor(rbNode, BLACK);
}
//找到前驱节点,小于当前节点最大值(中序遍历下,前一个元素)
private RBNode precursor(RBNode rbNode) {
RBNode rNode = rbNode.left;
//不存在右子节点,向上找
if(rNode == null) {
RBNode parent;
RBNode gParent;
do {
parent = parentOfNode(rbNode);
gParent = parentOfNode(parent);
}while (gParent != null && gParent.right != parent);
return gParent;
}
//存在右子节点,找右边树最左的树
else {
RBNode p = rbNode;
while(rNode != null) {
p = rNode;
rNode = rNode.right;
}
return p;
}
}
//找到后续节点,大于当前节点最小值(中序遍历下,后一个元素)
private RBNode subsequent(RBNode rbNode) {
RBNode rNode = rbNode.right;
//不存在右子节点,向上找
if(rNode == null) {
RBNode parent;
RBNode gParent;
do {
parent = parentOfNode(rbNode);
gParent = parentOfNode(parent);
}while (gParent != null && gParent.left != parent);
return gParent;
}
//存在右子节点,找右边树最左的树
else {
RBNode p = rbNode;
while(rNode != null) {
p = rNode;
rNode = rNode.left;
}
return p;
}
}
private void fixAfterPut(RBNode<K,V> rbNode) {
this.head.color = BLACK;
RBNode parent = parentOfNode(rbNode);
if(!isRed(parent)) return ;
RBNode gParent = parentOfNode(parent);
RBNode uncle = uncleOfNode(rbNode);
// 处理 父叔双红
if(isRed(parent) && isRed(uncle)) {
setColor(uncle, BLACK);
setColor(parent, BLACK);
// 父叔双红 一定要祖父节点。毕竟根节点为黑色
setColor(gParent, RED);
fixAfterPut(parentOfNode(parent));
return;
}
// 处理 LL LR
if(gParent.left == parent) {
// LR特殊处理
if(parent.right == rbNode) {
rotateLeft(parent);
fixAfterPut(parent);
return;
}
setColor(gParent, RED);
setColor(parent, BLACK);
setColor(rbNode, RED);
rotateRight(gParent);
}
// 处理 RR RL
else {
// RL
if(parent.left == rbNode) {
rotateRight(parent);
fixAfterPut(parent);
return;
}
setColor(gParent, RED);
setColor(parent, BLACK);
setColor(rbNode, RED);
rotateLeft(gParent);
}
}
private boolean isRed(RBNode rbNode) {
return rbNode == null ? BLACK : rbNode.color == RED;
}
private RBNode leftOfNode(RBNode rbNode) {
return rbNode.left;
}
private RBNode rightOfNode(RBNode rbNode) {
return rbNode.right;
}
private void setColor(RBNode rbNode, boolean color) {
if(rbNode == null) return;
rbNode.setColor(color);
}
private RBNode parentOfNode(RBNode rbNode) {
return rbNode.parent;
}
private RBNode uncleOfNode(RBNode rbNode) {
RBNode pNode = parentOfNode(rbNode);
RBNode gNode = parentOfNode(pNode);
if (gNode == null) return null;
if(leftOfNode(gNode) == pNode) return gNode.right;
return gNode.left;
}
private RBNode searchNode(K k) {
RBNode cur = head;
while(cur != null) {
int cmp = cur.key.compareTo(k);
if(cmp == 0) return cur;
else if(cmp > 0) cur = cur.left;
else cur = cur.right;
}
return cur;
}
private void rotateLeft(RBNode rbNode) {
RBNode rNode = rightOfNode(rbNode);
rbNode.right = null;
rNode.parent = parentOfNode(rbNode);
if(rNode.parent != null) {
RBNode p = rNode.parent;
if(leftOfNode(p) == rbNode) {
p.left = rNode;
}else {
p.right = rNode;
}
}else {
head = rNode;
}
RBNode rlNode = leftOfNode(rNode);
if(rlNode != null) {
rbNode.right = rlNode;
rlNode.parent = rbNode;
}
rNode.left = rbNode;
rbNode.parent = rNode;
}
private void rotateRight(RBNode rbNode) {
RBNode lNode = leftOfNode(rbNode);
rbNode.left = null;
lNode.parent = parentOfNode(rbNode);
if(lNode.parent != null) {
RBNode p = lNode.parent;
if(leftOfNode(p) == rbNode) {
p.left = lNode;
}else {
p.right = lNode;
}
}else {
head = lNode;
}
RBNode lrNode = rightOfNode(lNode);
if(lrNode != null) {
rbNode.left = lrNode;
lrNode.parent = rbNode;
}
lNode.right = rbNode;
rbNode.parent = lNode;
}
public static class RBNode<K extends Comparable<K>, V> {
private K key;
private V value;
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
public RBNode() {
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean getColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
}
}
测试代码
/**
* @author Jerssy
* @version V1.0
* @Description
* @create 2020-11-15 22:12
*/
public class TreeOperation<T> {
private final HashMap<String, Method> map= new HashMap<>();
/*
树的结构示例:
1
/ \
2 3
/ \ / \
4 5 6 7
*/
// 用于获得树的层数
public int getTreeDepth(T root) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
if (root == null){
return 0;
}
return (1 + Math.max(getTreeDepth(getTreeFields(root,"left")), getTreeDepth(getTreeFields(root,"right"))));
}
private void writeArray(T currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth,int arrayWidth) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 保证输入的树不为空
if (currNode == null) return;
String resNodeValue;
T key = getTreeFields(currNode, "key");
T value = getTreeFields(currNode, "value");
T color=getTreeFields(currNode, "color");
if (value!=null) {
resNodeValue=color!=null? ((Boolean) color ?"R-"+value:"B-"+value):value+"";
}
else if (key!=null) {
resNodeValue=color!=null? ((Boolean) color ? "\033[31;4m" + " " + key +" " + "\033[0m" : ""+ key) : key+ "";
}
else {
System.out.println("key/value"+"不能都为空!");
return;
}
res[rowIndex][columnIndex] = resNodeValue;
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth) return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
T left = getTreeFields(currNode, "left");
if (left != null) {
int columnI = Math.max(columnIndex - gap - resNodeValue.length()/2,0);
res[rowIndex + 1][columnI] = "/";
writeArray(left, rowIndex + 2, Math.max(columnI-gap,0), res, treeDepth,arrayWidth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
T right = getTreeFields(currNode, "right");
if (right != null) {
int columnI = Math.min(columnIndex + gap +resNodeValue.length()/2,arrayWidth-1);
res[rowIndex + 1][columnI] = "\\";
writeArray(right, rowIndex + 2, Math.min(columnI+gap,arrayWidth-1) , res, treeDepth,arrayWidth);
}
}
public void show(T root) {
if (root == null){
System.out.println("EMPTY!");
return;
}
System.out.println();
// 得到树的深度
int treeDepth = 0;
try {
//通过反射获取泛型类的属性和方法
Class<?> treeClass = root.getClass();
Field[] fields = treeClass.getDeclaredFields();
Method[] declaredMethods = treeClass.getDeclaredMethods();
for (Field field : fields) {
field.setAccessible(true);
String name = field.getName();
char[] cs=name.toCharArray();
cs[0]-=32;
for (Method method : declaredMethods) {
method.setAccessible(true);
if (method.getName().equals("get" + String.valueOf(cs))) {
Method declaredMethod = treeClass.getDeclaredMethod("get" + String.valueOf(cs));
declaredMethod.setAccessible(true);
map.put(name, declaredMethod);
}
}
}
treeDepth = getTreeDepth(root);
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
int grepWidth=arrayWidth/treeDepth;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth+grepWidth];
// 对数组进行初始化,默认为一个空格
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');
try {
writeArray(root, 0, arrayWidth / 2, res, treeDepth,arrayWidth+grepWidth);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line : res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i++) {
if (line[i]!=null){
sb.append(line[i]);
// if (line[i].length() > 1 && i <= line.length - 1) {
// i += line[i].length() > 4 ? line[i].length()-2 : line[i].length() - 1;
// }
}
}
String string = sb.toString();
System.out.println(sb);
}
}
private T getTreeFields(T node,String name) throws InvocationTargetException, IllegalAccessException {
if (map.containsKey(name)){
return (T) map.get(name).invoke(node);
}
return null;
}
}
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int idx = 0;
RBTree<Integer, String> redBlackTree = new RBTree<>();
TreeOperation<RBTree.RBNode> treeOperation = new TreeOperation<>();
System.out.println("构建开始");
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
if ("end".equals(s)) break;
redBlackTree.put(Integer.parseInt(s), null);
treeOperation.show(redBlackTree.getHead());
}
System.out.println("构建结束");
System.out.println("删除开始");
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
redBlackTree.remove(Integer.parseInt(s));
treeOperation.show(redBlackTree.getHead());
}
}
}