文章目录
4,AVL-平衡树
BST存在的问题是, 树在插入的时候会导致倾斜, 不同的插入顺序会导致数的高度不一样, 而树的高度直接影响了树的查找效率。
最坏的情况所有的节点都在一条斜线上,这样树的高度为N。
基于BST存在的问题, 平衡查找二叉树(Balanced BST)产生了。
平衡树的插入和删除的时候, 会通过旋转操作将高度保持在LogN。
其中两款具有代表性的平衡术分别为AVL树(高度平衡树, 具备二叉搜索树的全部特性, 而目左右子树高度差不超过1 )和红黑树。
AVL树是如何实现平衡的呢?,具体是通过左旋或者右旋来实现的。具体如下图:
填加8 9 10 时 左右高度超过1 右旋
添加6 , 7 时 第二次根据8 右旋
添加 5 第三次根据9 右旋
添加4 第四次根据6旋转
添加 2 ,3 ,结果 4不平衡了, 第五次根据4旋转
添加1 之后, 5不平衡了, 第六次根据5旋转
平衡树转成红黑树
- 红黑数要求没有平衡数那么严格, 只要保证黑节点平衡: 每条道黑节点的数量相同
5, 2-3-4 树
5.1 概念介绍
2-3-4树是四阶的 B树(Balance Tree), 他属于一种多路查找树, 它的结构有以下限制:
所有叶子节点都拥有相同的深度。
节点只能是 2-节点、3-节点、4-节点之一。
- 2-节点:包含1个元素的节点,有2个子节点:
- 3-节点:包含2个元素的节点,有3个子节点:
- 4-节点:包含3个元素的节点,有4个子节点:
所有节点必须至少包含1个元素
元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点:
而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。
下图是一个典型的2-3-4树
- 2节点 : 5
- 3节点 : 7 9
- 4节点 : 10 11 12
2-3-4树的查间操作像普通的二叉搜索树一样,非常简单,但由于其结点元素数不确定,在一些编程语言中实现起来并不方便,实现一般使用它的等同------------红黑树。
5.2 添加情况
出现5节点时会发生裂变
发现: 都是从底层插入的, 超过4节点后裂变
5.3 , 和红黑树的等价关系
2节点
3节点
4节点
超过4节点裂变的情况
转换成红黑树
- 看出根节点到每条路径的黑节点都是3
6,红黑树
红黑树, Red-Black Tree [RBT]
是一个自平衡(不是绝对的平衡)的二叉查找树(BST), 树上的每个节点都遵循下面的规则:
- 每个节点要么是黑色,要么是红色。
- 根节点是黑色。
- 每个叶子节点 (NIL) 是黑色。
- 每个红色结点的两个子结点一定都是黑色。(不存在两个相邻的红色节点)
- 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
红黑树能自平衡, 它靠的是什么? 三种操作: 左旋、右旋和变色
操作 | 描述 |
---|---|
左旋 | 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点 右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变 |
右旋 | 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点, 左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。 |
变色 | 结点的颜色由红变黑或由黑变红。 |
定义红黑树类
public class RBTree<K extends Comparable<K>, V> {
//红色用false来表示
private static final boolean RED = false;
private static final boolean BLACK = true;
//红黑树的root节点
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
//红黑树对象内部类
//继承Comparable接口, 可以做比较
static class RBNode<K extends Comparable<K>,V>{
//父节点
private RBNode parent;
//左子节点
private RBNode left;
//右子节点
private RBNode right;
//颜色, 这里定义的是 黑--true 红--false
private boolean color;
private K key;
private V value;
public RBNode() {
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
//为了操作简化, 再添加一个parent,key,value
public RBNode(RBNode parent, K key, V value) {
this.parent = parent;
this.key = key;
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 isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
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;
}
}
}
6.1, 旋转操作
左旋
以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋
以某!个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
红黑树-旋转代码实现
1,左旋代码实现
左旋
只改变了两根线的指向,
- rl父节点指向p, p, 右子节点指向rl
- p的父节点指向pr, pr左子节点指向p
/**
* 实现左旋
* p pr
* /\ /\
* pl pr ==> p rr
* /\ /\
* rl rr pl rl
* 左旋操作:
* 不用变的: p-pl , pr-rr
* 需要调整: 1. pr-rl 调整为 p-rl ,
* 将rl调整为p的右子节点
* 将p调整为rl的父节点
* 2.判断p是否有父节点
* 有: 把pr.parent = p.parent
* p为p.parent的子节点, 到底是 左还是右呢
* if p.parent.left == p
* p.parent.left = pr
* else
* p.parent.right = pr
* 没有: 直接把pr设置成root节点
* 3.最后 把p 和 pr交换
* p.parent = pr; pr.left = p
*
* @param p, 需要旋转的节点
*/
//左旋方法
private void leftRotate(RBNode p){
if ( p != null ){
//获取到pr节点
RBNode pr = p.right;
//获取到pr的左子节点 rl
p.right =pr.left;
// pr = rl;
if ( pr.left != null ) {
//将rl调整为p的右子节点
// p.right = rl;
//将p调整为rl的父节点
pr.left.parent = p;
}
//2,判断p是否有父节点
pr.parent = p.parent; //不管p是否有父节点, 都设置为pr的父节点
if ( p.parent == null ){
//如果p.parent 为空, 说明p为root节点, 把root改为pr
root = pr ;
//如果p是p.parent的左子节点, 就变更为p.parent.left = pr
}else if ( p.parent.left == p ){
p.parent.left = pr;
}else {//如果p是p.parent的右子节点, 就变更为p.parent.right = pr
p.parent.right = pr;
}
//3 最后 把p父节点设置为pr, pr的左子节点设置为p
pr.left = p;
p.parent = pr;
}
}
2,右旋代码实现
/**
* 右旋实现
* p pl
* /\ /\
* pl pr ==> ll p
* /\ /\
* ll lr lr pr
* 不变的 p-pr , pl-lr, 两个2节点的右字节点不变
* 步骤1: p-pl 变成 p-lr
* 步骤2: 判断p是否有父节点 , pl-lr 变成 pr-p
* 步骤3: p-pl 变成 pl-p
* @param p
*/
//右旋方法
private void rightRotate(RBNode p){
if (p != null) {
RBNode pl = p.left;
p.left = pl.right;
//步骤1: p-pl 变成 p-lr , lr的父节点 = p
if (pl.right != null) {
// p.left = lr;
pl.right.parent = p;
}
//步骤2: 判断p是否有父节点,
pl.parent = p.parent ;
if (p.parent == null) {
root = pl;
}else if ( p.parent.left == p ){
p.parent.left = pl;
}else {
p.parent.right = pl;
}
//步骤3: p-pl 变成 pl-p
pl.right = p;
p.parent = pl;
}
}
6.2, 插入操作
1, 先完成插入节点
- a.找到插入的位置(父节点)
- b.将节点插入父节点相应的位置
//插入操作
private void put(K key,V value){
//1, 找到插入的父节点
RBNode t = root;
if ( t == null ){ //这种情况说明是第一次插入
root = new RBNode(null,key,value==null?key:value);
return;
}
//记录比较情况
int cmp;
RBNode parent;
do {
parent = t;
//比较当前根节点与传入的key大小来决定从哪边开始查找
cmp = key.compareTo((K) t.key);
if ( cmp > 0){ //从右侧查找
t = t.right ;
}else if ( cmp < 0 ){//从左侧查找
t = t.left ;
}else {// cmp =0 说明key相等了
//如果value为空就设置为key
t.setValue( value == null ? key:value);
return;
}
}while (t != null);
//2, 将新节点添加到父节点的子节点中
// a 创建要插入的节点
RBNode node = new RBNode(parent, key, value == null ? key : value);
if ( cmp > 0 ){
//如果比父节点大, 就放到右边
parent.right = node;
}else {
parent.left = node;
}
//旋转和变色 调整红黑树的平衡
}
2,接下来需要旋转和变色 调整红黑树的平衡
2-3-4数插入节点情况
2节点
如果是二节点插入1个节点, 只需判断放左右, 黑红数是平衡的
3节点
如果是3节点插入的话, 会出现6种情况
只有1, 6 不需要调整 , 3 ,4 旋转一次 变色就可以了
需要旋转两次的
图2先右旋再左旋
图5先左旋再右旋
4节点
会出现4种情况, 每种情况都需要调整
3.代码实现插入操作
需要调整的共8种情况
代码里面的例图
有右叔叔节点情况, 需变色(右叔叔跟父亲变为黑色, 爷爷变成红色), 这时平衡了
但是如果爷爷节点上面还有元素, 这时候就不平衡了;
这时的爷爷节点也是红色,就把爷爷节点当成新插入的元素再和上面再来一次变色处理(递归)
x = getParent(x)
做完这步之后 x = 2了
现在发现, 图6 旋转一次后 就和 图5 一模一样了 , 可以一起处理了
4 put方法
package com.ccc.util.treemap;
public class RBTree<K extends Comparable<K>, V> {
//红色用false来表示
private static final boolean RED = false;
private static final boolean BLACK = true;
//红黑树的root节点
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
//插入操作
public void put(K key,V value){
//1, 找到插入的父节点
RBNode t = root;
if ( t == null ){ //这种情况说明是第一次插入
root = new RBNode(null,key,value==null?key:value);
return;
}
//记录比较情况
int cmp;
RBNode parent;
if (key == null){
System.out.println("put 命令里 key 为空了");
throw new NullPointerException();
}
do {
parent = t;
//比较当前根节点与传入的key大小来决定从哪边开始查找
cmp = key.compareTo((K) t.key);
if ( cmp > 0){ //从右侧查找
t = t.right ;
}else if ( cmp < 0 ){//从左侧查找
t = t.left ;
}else {// cmp =0 说明key相等了
//如果value为空就设置为key
t.setValue( value == null ? key:value);
return;
}
}while (t != null);
//2, 将新节点添加到父节点的子节点中
// a 创建要插入的节点
RBNode node = new RBNode(parent, key, value == null ? key : value);
if ( cmp > 0 ){
//如果比父节点大, 就放到右边
parent.right = node;
}else {
parent.left = node;
}
//旋转和变色 调整红黑树的平衡
fixAfterPut(node);
}
//获取父节点
private RBNode getParent(RBNode node){
return node != null ? node.parent : null;
}
//获取爷节点
private RBNode getGrandfather(RBNode node){
return node != null ? node.parent.parent : null;
}
//获取左节点
private RBNode getLeft(RBNode node){
return node != null ? node.left : null;
}
//获取右节点
private RBNode getRight(RBNode node){
return node != null ? node.right : null;
}
//获取节点颜色
private boolean getColor(RBNode node){
//如果传过来的节点是空的, 就返回黑色, ----------------Treemap里删除的时候会用到这个表达式
return node == null ? BLACK : node.color;
}
//设置颜色
private void setColor(RBNode node, boolean color){
if (node != null) {
node.color = color;
}
}
/**
* 插入放点后的调整操作
* 2-3-4对应的操作
* 2书点: 新插入一个元素直接2节点合并不用调整
* 红黑树:新增一个红色 放点在黑色节点下不需要调整
* 3节点: 新插入一个元素在3节点下, 那么会出现6中情况(2两种不需要调整,4种需要调整)
* 红黑树: 插入的节点是 插入在上黑下红的结构中,插入在红色节点
* 4节点:新插入一个元素在4节点下,那么会出现4中情况都需要调整
* 红黑树:新增的节点是红色, 爷爷节点是黑色,父亲节点和叔叔节点是红色
* @param x
*/
private void fixAfterPut(RBNode<K,Object> x){
//插入的节点 肯定是红色
x.color = RED;
//2节点不用调整, 3, 4 节点才需要调整, 把2节点过滤了
while ( x !=null && x != root && x.parent.color ==RED ){ //为空为root都不需要调整, 只有2节点的父节点是黑色,其他都是红色, 所以这里把2节点排除了
//这里分为了两种情况,
if ( getParent(x) == getLeft(getGrandfather(x)) ){ //x的父节点在x爷爷左子节点时 对应 图1 , 2 ,5, 6
//需调整的变成了4种, 有叔叔节点: 图1,2 没有叔叔节点: 图5,6
//找到右叔叔节点, 1,2找到4 , 5,6 找到null
RBNode uncleR = getRight(getGrandfather(x));
//如果右叔叔的颜色是RED, 说明不为空 : 图 1,2
if (getColor(uncleR) == RED) {
//叔叔跟父亲变为黑色, 爷爷变成红色
//但是还有一种情况是爷爷节点上面还有元素, 这下就把(下面这个整体)爷爷节点当成x ,再来一遍 如图9
//变色+ 递归
setColor(uncleR,BLACK); //把右叔叔设置成黑色
setColor( getParent(x) , BLACK ); //把父节点设置成黑色
setColor(getGrandfather(x),RED);//把爷爷设置成红色
//递归处理, 就是先保证自身平衡, 再把这部分当做新元素和上面的节点变色处理
x = getGrandfather(x);
}else {
//说明没有右叔叔节点: 图 5,6
//判断x是父节点的左子节点还是右子节点
if ( x == getRight(getParent(x))) { //是父节点的右子节点: 图6
x = getParent(x);//现在x就转移到了2 , 如图10
leftRotate(x); // 旋转之后, 下面5, 和10 可以一起处理了
}
//把父节点变为黑色, 爷爷节点变为红色, 再根据爷爷节点右旋即可
setColor(getParent(x),BLACK);//父黑
setColor(getGrandfather(x),RED);//爷红
rightRotate(getGrandfather(x));//爷右旋
}
}else {//x的父节点在x爷爷右子节点时 对应 图3,4,7,8
//需调整的变成了4种, 有叔叔节点: 图3,4 没有叔叔节点: 图7,8 , 和上面的情况左右相反
//找到左叔叔节点, 图3,4找到2 , 7,8 找到null
RBNode uncleL = getLeft(getGrandfather(x));
//如果左叔叔的颜色是RED, 说明不为空 : 图 3,4
if (getColor(uncleL) == RED) {
//叔叔跟父亲变为黑色, 爷爷变成红色
//但是还有一种情况是爷爷节点上面还有元素, 这下就把(下面这个整体)爷爷节点当成x ,再来一遍 如图9
//变色+ 递归
setColor(uncleL,BLACK); //把左叔叔设置成黑色
setColor( getParent(x) , BLACK ); //把父节点设置成黑色
setColor(getGrandfather(x),RED);//把爷爷设置成红色
//递归处理, 就是先保证自身平衡, 再把这部分当做新元素和上面的节点变色处理
x = getGrandfather(x);
}else {
//说明没有左叔叔节点: 图 7,8
//判断x是父节点的左子节点还是右子节点
if ( x == getLeft(getParent(x))) { //是父节点的右子节点: 图7
x = getParent(x);//现在x就转移到了3
rightRotate(x); // 旋转之后, 下面7, 和8 可以一起处理了
}
//把父节点变为黑色, 爷爷节点变为红色, 再根据爷爷节点右旋即可
setColor(getParent(x),BLACK);//父黑
setColor(getGrandfather(x),RED);//爷红
leftRotate(getGrandfather(x));//爷右旋
}
}
}
// root 节点肯定为黑色
setColor(root , BLACK);
}
}
5测试put方法
运行结果
网站验证
Red/Black Tree Visualization (usfca.edu)
6.3, 删除操作
6.3.1 二叉树删除
二叉树删除操作的情况:
- 删除叶子节点,直接删除
- 删除的节点有一个子节点,那么用子节点来替代
- 如果删除的节点右两个子节点,此时需要找到前驱节点或者后继节点来替代,可以转换为 1、2的情况
删除节点方案:
- 找到前驱节点,复制前驱节点值覆盖预备删除的节点的值,然后删除前驱节点
- 找到后继节点,复制后继节点值覆盖预备删除的节点的值,然后删除后继节点
被删除的前驱或者后继节点只有两种情况
- 被删除的节点是叶子节点
- 被删除的节点有一个孩子节点
6.3.2查找前驱后继节点
/**
* 找到前驱节点
* @param node
* @return
*/
private RBNode getPrecursor(RBNode node){
if (node != null ) {
return null;
} else if (getLeft(node) != null) {
//如果他的左节点不为空, 就循环找他的左节点的右子节点
RBNode p = getLeft(node);
while ( getRight(p) != null){
p = getRight(p);
}
return p;
}else {
//如果这个node没有左节点, 那么就向上找前驱节点
//这种情况在 红黑树 ,2-3-4树中不会出现
RBNode p = node.parent;
RBNode n1 = node;
while ( p!= null && n1 == getLeft(p)){
n1 = p;
p = getParent(p);
}
return p;
}
}
/**
* 找到后继节点
* @param node
* @return
*/
private RBNode getSuccessor(RBNode node){
if (node != null ) {
return null;
} else if (getRight(node) != null) {
//如果他的左节点不为空, 就循环找他的左节点的右子节点
RBNode p = getRight(node);
while ( getLeft(p) != null){
p = getLeft(p);
}
return p;
}else {
//如果这个node没有左节点, 那么就向上找前驱节点
//这种情况在 红黑树 ,2-3-4树中不会出现
RBNode p = node.parent;
RBNode n1 = node;
while ( p!= null && n1 == getRight(p)){
n1 = p;
p = getParent(p);
}
return p;
}
}
6.3.3 . 红黑树删除代码实现
- 删除叶子节点,直接删除
- 删除的节点有一个子节点,那么用子节点来替代
- 如果删除的节点右两个子节点,此时需要找到前驱节点或者后继节点来替代,可以转换为 1、2的情况
/**
* 删除节点
* 1, 节点删除(可以看做普通的二叉树删除)
* 2, 删除后调整
* @param key
* @return
*/
public V remove(K key){
//1. 根据需要删除的key, 找到对应的node节点
RBNode node = getNode(key);
if ( node == null ){
return null;
}
V oldValue = (V) node.value;
//具体删除节点的方法
deleteNode(node);
return oldValue;
}
/**
* 根据key找到删除的对应node
* @param key
* @return
*/
private RBNode getNode(K key) {
if ( key == null ){
return null;
}
RBNode node = root;
while ( node != null ){
int cmp = node.key.compareTo(key);
if (cmp > 0) {
node = node.right ;
} else if (cmp < 0) {
node = node.left;
}else {
//找到了对应的节点
return node;
}
}
return null;
}
/**
* 删除节点
* 1.删除节点(普通的二叉树相同)
* a. 删除叶子节点, 直接删除
* b. 删除的节点有一个子节点, 用子节点来替代
* c. 删除的节点有两个子节点, 把这个节点赋值给前驱或后继节点, 再删除掉前去后继节点
* 将情况c转换成情况a, b
* 2.调整
* @param node
*/
private void deleteNode(RBNode node) {
//1, 先处理情况3
if (getLeft(node) != null && getRight(node) != null) {
//有两个子节点的情况
RBNode pNode = getSuccessor(node);//找到后继或前驱节点,这里使用后继
//用后继节点的值, 覆盖给删除节点
node.key = pNode.key;
node.value = pNode.value;
//这时要删除的节点就变成了 后继节点, 也就变成了情况2 ,或1了
node = pNode;
}
//找到后继节点的子节点(替代节点)
RBNode replacement = node.left==null ? node.right:node.left;
//2, 再情况2, 情况2 可能是情况3 转换来的
if (replacement != null) { //如果后继节点有子节点说明是情况2
replacement.parent = node.parent;
if ( getParent(node) == null ){//说明我们删除的节点是根节点
root = replacement;
} else if (getLeft(getParent(node)) == node) { //如果是node父节点的左节点, 就换成replacement
getParent(node).left = replacement;
}else { //如果是node父节点的右节点, 就换成replacement
getParent(node).right = replacement;
}
//要删除的node节点全部设置成空 , GC
node.right = node.parent = node.right = null ;
if (getColor(node) == BLACK) { //如果删除的节点是红色, 不需要调整
//做调整操作
fixAfterRemove(replacement);
}
}else if (node.parent == null){ // node 没有父节点说明是root节点
root = null;
}else { //就是情况1
//先调整再删除
if (node.color == BLACK) {
fixAfterRemove(node);
}
if (node.parent != null) { //如果他没有父节点再把指向关系去掉
getParent(node).left = getParent(node).right = null ;
}
node = null;
}
}
private void fixAfterRemove(RBNode node) {
}
6.3.4 删除后的调整
在整理红黑树节点的删除操作时我们需要先理解清楚红黑树删除和2-3-4树删除的等价关系,这样理解起 来才会比较容易
核心理论:红黑树删除操作的本质其实就是删除2-3-4树的叶子节点
情况一
情况2:删除的是非情况1的节点,根据我们前面介绍的删除的规则,会找到对应的前驱和后继节点,那 么最终删除的还是叶子节点
删除节点的调整操作:
1.情况一:自己能搞定的,对应叶子节点是3节点和4节点
2.情况二:自己搞不定,需要兄弟借,但是兄弟不借,找父亲借,父亲下来,然后兄弟找一个人去代替
父亲当家
这种情况就是兄弟节点是3节点或者4节点 找兄弟节点
如果找到的兄弟节点是红色其实还要调整
执行如下调整先,先变色,然后左旋
找兄弟节点借
然后沿着7节点左旋
3.情况三:跟兄弟借,兄弟也没有(情同手足,同时自损)
兄弟节点是2节点,同时当前节点的父节点是红色节点的情况
删除后直接变色就可以了 兄弟节点是2节点,同时当前节点的父节点是黑色节点
变更操作为如下,如果继续有父节点那么还要递归处理
最终的是实现代码为
/**
* 删除节点后的调整操作
* 2-3-4数操作
* 1. 删除3,4 节点, 自己能搞定
* 2. 删除2 节点, 自己搞不定, 需要兄弟借, 兄弟借
* 父亲下来, 兄弟找一个节点替换父亲节点位置
* 3. 删除2 节点, 自己搞不定, 兄弟不借
*
* @param node
*/
private void fixAfterRemove(RBNode node) {
// 情况2 和3
//如果是root节点直接改为黑色就可以了, 替换的节点是红色就不需要调整了
while (node != root && getColor(node) == BLACK ){
//判断node是父节点的左节点还是有节点
if ( node == getLeft(getParent(node))){
//1. 找到兄弟节点
RBNode bro = getParent(node).right;
// 判断找到的是不是真的兄弟节点 ---- 2-3-4 转红黑树3节点有两种情况
if (getColor(bro) == RED) {
//如果是红色, 找到的就不是真的兄弟节点, 需要一次变色加左旋转
setColor(bro,BLACK);
setColor(getParent(node),RED);
leftRotate(getParent(node));
bro = getParent(node).right;//找到真兄弟了
}
//判断能不能借
//兄弟节点一个子节点都没有, 不借
if (getColor(getLeft(bro)) == BLACK && getColor(getRight(bro)) == BLACK) {
//2没有子节点, 不借
setColor(bro,RED);
node = getParent(node);
}else {
//2兄弟借
//如果兄弟节点的子节点是左边, 需要先变色, 右旋一次
if (getColor(getRight(bro)) == BLACK) {
//右侧子节点为空, 那就有左子节点
setColor(bro,RED);
setColor(getLeft(bro),BLACK);
rightRotate(bro);
bro = getRight(getParent(node));
}
//需要根据父节点做一次左旋操作, 变色
setColor(bro,getColor(getParent(node)));
setColor(getParent(node),BLACK);
setColor(getRight(bro),BLACK);
leftRotate(getParent(node));
node =root; //结束循环, 针对3的处理
}
}else {
//1. 找到兄弟节点
RBNode bro = getParent(node).left;
// 判断找到的是不是真的兄弟节点 ---- 2-3-4 转红黑树3节点有两种情况
if (getColor(bro) == RED) {
//如果是红色, 找到的就不是真的兄弟节点, 需要一次变色加左旋转
setColor(bro,BLACK);
setColor(getParent(node),RED);
rightRotate(getParent(node));
bro = getParent(node).left;//找到真兄弟了
}
//判断能不能借
//兄弟节点一个子节点都没有, 不借
if (getColor(getLeft(bro)) == BLACK && getColor(getRight(bro)) == BLACK) {
//2没有子节点, 不借
setColor(bro,RED);
node = getParent(node);
}else {
//2兄弟借
//如果兄弟节点的子节点是左边, 需要先变色, 右旋一次
if (getColor(getLeft(bro)) == BLACK) {
//右侧子节点为空, 那就有左子节点
setColor(bro,RED);
setColor(getRight(bro),BLACK);
leftRotate(bro);
bro = getLeft(getParent(node));
}
//需要根据父节点做一次左旋操作, 变色
setColor(bro,getColor(getParent(node)));
setColor(getParent(node),BLACK);
setColor(getLeft(bro),BLACK);
rightRotate(getParent(node));
node =root; //结束循环, 针对3的处理
}
}
}
//1. 替换的节点为红色, 只需要变色为黑色集客
setColor(node,BLACK);
}
6.4测试删除结果 (前驱)
初始状态一致