红黑树
红黑树的概述
红黑树的特性
- 所有节点都有两种颜色:红与黑
- 所有的null节点视为黑色
- 红色节点之间不能相邻
- 根节点是黑色
- 从根到任意一个叶子节点,路径中的黑色节点数也一样(黑色完美平衡)
这里给大家解释一下叶子节点其实就是二叉树节点的最后一层,简单理解起来的话确实是这样
判断是否是红黑树
很明显上图不是一个红黑树,违反了规则3,红色节点不能相邻
很明显上图也不是一个红黑树,违反了规定5,从根节点到任意一个叶子节点路径中的黑色节点数一样,可以看出来1和3的黑色节点只有6和2,但是7的节点却有6,8,7三个数,9的节点也有三个数6,8,9,故而不满足红黑树条件
上图就是一个红黑树了,1到根节点的黑色节点有两个,8到根节点的黑色节点也是有两个
上面这个图就有一定的迷惑性了,看着的话像一个红黑树,其条件也确实满足,但是没有考虑到一件事情,那就是null节点,条件2明确说了所有的null节点视为黑色,所以上图又可以变换为下面的图片
从此图就可以看出来1下面的叶子节点就是一个很明显的错误,与8下面的叶子节点到根节点的数量不符合.
下面就得出一个重要结论:红色的叶子节点可以单独出现,可以在左侧叶子节点,也可以在右侧的叶子节点,但是黑色的叶子节点就必须成对出现,否则就需要考虑条件2
红黑树基础代码
public class RedBlackTree {
//枚举
enum Color {
RED,BLACK;
}
//根节点
private Node root;
//红黑树节点
private static class Node {
int key;
Object value;
Node left;
Node right;
Node parent; //父节点
Color color;
//判断当前节点是否是左孩子
boolean isLeftChild(){
//如果父节点都为null的话,根本就不可能会有左孩子,同样如果父节点的左孩子不是当前节点的话也可以判断当前节点不是左孩子
return parent != null && parent.left == this;
}
//返回当前节点的叔叔节点
Node uncle(){
//判断如果当前节点的父节点和爷爷节点有一个为null的话,就可以判断当前节点并没有叔叔节点
if(parent == null || parent.parent == null){
return null;
}
//判断父节点是否是爷爷节点的左孩子,如果是返回爷爷节点的右孩子
if(parent.isLeftChild()){
return parent.parent.right;
}else{
return parent.parent.left;
}
}
//返回当前的兄弟节点
Node sibling(){
//判断当前节点的父节点如果为null了的话,就不会有兄弟节点了
if(parent == null){
return null;
}
//判断如果当前节点是父节点的左孩子,则返回右节点,反之返回左节点
if(this.isLeftChild()){
return parent.right;
}else{
return parent.left;
}
}
}
//判断当前节点是否是红色
boolean isRed(Node node){
//判断如果当前节点不为null,并且当前节点的颜色等于红色的时候才是红色节点
return node != null && node.color == Color.RED;
}
//判断当前节点是否是黑色节点
boolean isBlack(Node node){
//如果不是红色节点就是黑色节点
return !isRed(node);
}
}
右旋
可以由上图看出要旋转的节点是8(粉色),5(黄色)是8粉色节点的左孩子,6(绿色)是黄色节点的右孩子
旋转之后
/**
* 右旋函数
* 1.对于parent的处理
* 2.旋转后行嗯的父子关系
* @param pink
*/
private void rightRotate(Node pink){
//找到粉色节点的父节点
Node parent = pink.parent;
//pink节点的左孩子是yellow节点
Node yellow = pink.left;
//green节点是yellow节点的右侧孩子
Node green = yellow.right;
//判断当前绿色节点不为null的话,绿色节点的父节点就应该等于粉色节点
if(green != null){
green.parent = pink;
}
//旋转之后让黄色节点的右孩子等于粉色节点
yellow.right = pink;
//旋转之后黄色节点的父节点等于粉色节点的父节点
yellow.parent = pink.parent;
//旋转之后粉色节点的左孩子等于绿色节点
pink.left = green;
//旋转之后粉色节点的父节点等于黄色节点
pink.parent = yellow;
//判断如果粉色节点的父节点为null的话,根节点就变为黄色节点
if(parent == null ){
root = yellow;
}
//如果父节点的左孩子是粉色节点的话
else if(parent.left == pink){
//父节点的左孩子就等于黄色节点
parent.left = yellow;
}else{
//父节点的右孩子就等于黄色节点
parent.right = yellow;
}
}
左旋
/**
* 左旋函数
*
* @param pink
*/
private void leftRotate(Node pink) {
// 找到粉色节点的父节点
Node parent = pink.parent;
// 粉色节点的右孩子是黄色节点
Node yellow = pink.right;
// 绿色节点是黄色节点的左侧孩子
Node green = yellow.left;
// 如果绿色节点不为null,将绿色节点的父节点设为粉色节点
if (green != null) {
green.parent = pink;
}
// 旋转后,将黄色节点的左孩子设为粉色节点
yellow.left = pink;
// 将黄色节点的父节点设为粉色节点的父节点
yellow.parent = pink.parent;
// 旋转后,将粉色节点的右孩子设为绿色节点
pink.right = green;
// 将粉色节点的父节点设为黄色节点
pink.parent = yellow;
// 如果粉色节点的父节点为null,将黄色节点设为新的根节点
if (parent == null) {
root = yellow;
}
// 如果粉色节点是其父节点的左孩子,更新父节点的左孩子为黄色节点
else if (parent.left == pink) {
parent.left = yellow;
}
// 如果粉色节点是其父节点的右孩子,更新父节点的右孩子为黄色节点
else {
parent.right = yellow;
}
}
新增
/**
* 新增或者更新
* 正常增,遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key,Object value){
Node p = this.root;
Node parent = null;//设置父节点为null
while ( p!= null){
parent = p;//将父节点跟新为当前节点
if(p.key > key){
p = p.left;
}else if(p.key < key){
p = p.right;
}else{
p.value = value; //跟新的逻辑
return;
}
}
//创建出来的需要添加的节点
Node inserted = new Node(key, value);
//判断如果父节点为null的话,根节点就是此时需要添加的节点
if(parent == null){
root = inserted;
}
//如果key比父节点的key还小的话,父节点的左孩子就是添加的节点
else if(key < parent.key){
parent.left = inserted;
}else
//如果key比父节点的key大的话,父节点的右孩子就是添加的节点
{
parent.right = inserted;
}
}
写道如上代码,普通的二叉树添加就完成了,但是此时还需要遇到红红不平衡的时候进行调整
但是此时就遇到了下面这种情况
/**
* 新增或者更新
* 正常增,遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key,Object value){
Node p = this.root;
Node parent = null;//设置父节点为null
while ( p!= null){
parent = p;//将父节点跟新为当前节点
if(p.key > key){
p = p.left;
}else if(p.key < key){
p = p.right;
}else{
p.value = value; //跟新的逻辑
return;
}
}
//创建出来的需要添加的节点
Node inserted = new Node(key, value);
//判断如果父节点为null的话,根节点就是此时需要添加的节点
if(parent == null){
root = inserted;
}
//如果key比父节点的key还小的话,父节点的左孩子就是添加的节点
else if(key < parent.key){
parent.left = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}else
//如果key比父节点的key大的话,父节点的右孩子就是添加的节点
{
parent.right = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}
//遇到红红不平衡进行调整
fixRedRed(inserted);
}
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
}
上述代码完成了条件1,条件2,接下来需要完成条件3了
条件3
可以看的出来上树是平衡的
第一步:此时添加1,就可以得到下图
当时上面的图添加之后1节点和2节点违反了条件3
第二步:违反条件3之后,此时最好的解决办法就是把2节点换成黑色
但是此时1的叔叔节点4又不满足条件5了
第三步:此时最好的解决办法就是将叔叔节点4,也变为黑色
但是此时对于1这个叶子节点,和6,8,10,12,14,16这个节点违反了条件5
第四步:此时的解决办法只能是将3载设置为红色节点
满足条件5之后又不满足条件3了
第五步:此时就可以递归了,将父亲节点变为黑色,叔叔节点也变为黑色
此时不满足条件4了,所以直接将9这个节点变为黑色
此时上图就满足了红黑树的五个条件
条件3的代码如下
/**
* 新增或者更新
* 正常增,遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key,Object value){
Node p = this.root;
Node parent = null;//设置父节点为null
while ( p!= null){
parent = p;//将父节点跟新为当前节点
if(p.key > key){
p = p.left;
}else if(p.key < key){
p = p.right;
}else{
p.value = value; //跟新的逻辑
return;
}
}
//创建出来的需要添加的节点
Node inserted = new Node(key, value);
//判断如果父节点为null的话,根节点就是此时需要添加的节点
if(parent == null){
root = inserted;
}
//如果key比父节点的key还小的话,父节点的左孩子就是添加的节点
else if(key < parent.key){
parent.left = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}else
//如果key比父节点的key大的话,父节点的右孩子就是添加的节点
{
parent.right = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}
//遇到红红不平衡进行调整
fixRedRed(inserted);
}
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
}
}
条件4
叔叔为黑色
条件1
条件4中的条件1
第一步:将3变为黑色,5变为红色
但是上图优惠让5的右孩子不满足条件5
此时3就是黄色节点,5就是粉色节点
第二步:解决办法就只能是右旋了
代码如下
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
return;
}else{
//条件4中的条件1,如果父节点为爷爷节点的左孩子,当前传过来的节点也是左孩子的话
if(parent.isLeftChild() && x.isLeftChild()){
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//进行右旋
rightRotate(grabdparent);
}
}
}
条件2
插入节点是父节点的右孩子
第一步:先进行左旋
接下来的步骤就和前面的条件1一样了
代码如下
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
return;
}else{
//条件4中的条件1,如果父节点为爷爷节点的左孩子,当前传过来的节点也是左孩子的话
if(parent.isLeftChild() && x.isLeftChild()){ //条件1
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//进行右旋
rightRotate(grabdparent);
}else if(parent.isLeftChild() && !x.isLeftChild()){ //条件2
//先进行左旋
leftRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//接着右旋即可
rightRotate(grabdparent);
}
}
}
条件3
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
return;
}else{
//条件4中的条件1,如果父节点为爷爷节点的左孩子,当前传过来的节点也是左孩子的话
if(parent.isLeftChild() && x.isLeftChild()){ //条件1
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//进行右旋
rightRotate(grabdparent);
}else if(parent.isLeftChild() && !x.isLeftChild()){ //条件2
//先进行左旋
leftRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//接着右旋即可
rightRotate(grabdparent);
}else if(!parent.isLeftChild() && !x.isLeftChild() ){
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//左旋
leftRotate(grabdparent);
}
}
}
条件4
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
return;
}else{
//条件4中的条件1,如果父节点为爷爷节点的左孩子,当前传过来的节点也是左孩子的话
if(parent.isLeftChild() && x.isLeftChild()){ //条件1
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//进行右旋
rightRotate(grabdparent);
}else if(parent.isLeftChild() && !x.isLeftChild()){ //条件2
//先进行左旋
leftRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//接着右旋即可
rightRotate(grabdparent);
}else if(!parent.isLeftChild() && !x.isLeftChild() ) {
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//左旋
leftRotate(grabdparent);
} else{
//将父节点进行右旋
rightRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//左旋
leftRotate(grabdparent);
}
}
}
删除
再删除之前先写两个辅助方法
//查找删除节点
private Node find(int key){
Node p = root;
while (p!=null){
if(key < p.key){
p = p.left;
}else if(key > p.key){
p = p.right;
}else{
return p;
}
}
return null;
}
//查找剩余节点
private Node findReplaced(Node deleted){
//当删除节点的左右孩子都不存在时
if(deleted.left == null && deleted.right == null){
return null;
}
//当删除节点没有左孩子,只有右孩子的话执行下面这句
if(deleted.left == null && deleted.right != null){
return deleted.right;
}
//当删除节点只有左孩子,没有右孩子的话
if(deleted.left != null && deleted.right ==null){
return deleted.left;
}
//当删除节点有两个孩子的时候需要找到后继节点
Node s = deleted.right;
//当删除节点的左孩子不等于null的时候,后继节点就一直循环,直到找到后继节点为止
while (s.left != null){
s = s.left;
}
return s;
}
执行删除任务
/**
* 删除,正常删除,会用到李代桃僵的技巧,遇到嘿嘿不平衡进行调整
* @param key
*/
public void remove(int key){
//查找删除的节点
Node deleted = find(key);
//如果删除节点为null的时候直接return掉,就不往下执行了
if(deleted == null){
return;
}
//进行删除操作
doRemove(deleted);
}
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
return;
}
//有两个孩子
}
接下来就需要考虑有几个孩子的情况,来分别执行对应代码
两个孩子
其实本质上就是将删除节点和删除后剩下的节点的key值和value值进行交换
代码如下
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
return;
}
//有两个孩子=======>有一个孩子或没有孩子的情况了
int t = deleted.key; //找到删除节点的key
deleted.key = replaced.key; //找到删除后留剩下的节点key,其实也就是找到后继节点,将后继节点的key变为删除节点的key
replaced.key = t; //删除后剩下节点的key等于删除节点的key,只是也就是做一个交换,将两个节点的key
//下面的三行代码是用来交换值的
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
//接下来删除要删除的那个节点,就可以将有两个孩子的情况转换成为只有一个孩子的情况,或者是转换成没有孩子的情况
doRemove(replaced);
}
没有孩子根节点
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
if(deleted == root){
root = null;
}
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
return;
}
//有两个孩子=======>有一个孩子或没有孩子的情况了
int t = deleted.key; //找到删除节点的key
deleted.key = replaced.key; //找到删除后留剩下的节点key,其实也就是找到后继节点,将后继节点的key变为删除节点的key
replaced.key = t; //删除后剩下节点的key等于删除节点的key,只是也就是做一个交换,将两个节点的key
//下面的三行代码是用来交换值的
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
//接下来删除要删除的那个节点,就可以将有两个孩子的情况转换成为只有一个孩子的情况,或者是转换成没有孩子的情况
doRemove(replaced);
}
一个孩子根节点
使用李代桃僵的方式进行替换
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
if(deleted == root){
root = null;
}
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
if(deleted == root){
//用剩余节点替换了根节点的key,value,根节点孩子为null,颜色保持黑色不变
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
}
return;
}
//有两个孩子=======>有一个孩子或没有孩子的情况了
int t = deleted.key; //找到删除节点的key
deleted.key = replaced.key; //找到删除后留剩下的节点key,其实也就是找到后继节点,将后继节点的key变为删除节点的key
replaced.key = t; //删除后剩下节点的key等于删除节点的key,只是也就是做一个交换,将两个节点的key
//下面的三行代码是用来交换值的
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
//接下来删除要删除的那个节点,就可以将有两个孩子的情况转换成为只有一个孩子的情况,或者是转换成没有孩子的情况
doRemove(replaced);
}
没有孩子和一个孩子不在根节点的情况
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
Node parent = deleted.parent;//找到被删除节点的父节点
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
if(deleted == root){
root = null;
}else{
if(isBlack(deleted)){
//复杂调整
}else{
//红色叶子,无需任何处理
}
//如果被删除的节点是父节点的左侧,将左侧的孩子置为null
if(deleted.isLeftChild()){
parent.left = null;
}else{
//如果被删除的节点是父节点的右侧,将右侧的孩子置为null
parent.right = null;
}
//将被删除节点的父节点也置为空
deleted.parent = null;
}
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
if(deleted == root){
//用剩余节点替换了根节点的key,value,根节点孩子为null,颜色保持黑色不变
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
}else{
//判断如果被删除的节点不是根节点,并且被删除的节点在父节点的左侧的话,将父节点的左侧置为删除剩下的节点
if(deleted.isLeftChild()){
parent.left = replaced;
}else{
//判断如果被删除的节点不是根节点,并且被删除的节点在父节点的右侧的话,将父节点的右侧置为删除剩下的节点
parent.right = replaced;
}
//被删除剩下的节点的父节点就变为了被删除节点的父节点
replaced.parent = parent;
//下面三行代码的意思实际上就是将被删除节点进行垃圾回收
deleted.left = null;
deleted.right = null;
deleted.parent = null;
//当被删除的节点和剩下的节点都是黑色的时候
if(isBlack(deleted) && isBlack(replaced)){
//复杂处理
}else{
//当删除的节点是黑色,但是剩下的节点是红色的话,将剩下的这个红色节点变为黑色
replaced.color = Color.BLACK;
}
}
return;
}
//有两个孩子=======>有一个孩子或没有孩子的情况了
int t = deleted.key; //找到删除节点的key
deleted.key = replaced.key; //找到删除后留剩下的节点key,其实也就是找到后继节点,将后继节点的key变为删除节点的key
replaced.key = t; //删除后剩下节点的key等于删除节点的key,只是也就是做一个交换,将两个节点的key
//下面的三行代码是用来交换值的
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
//接下来删除要删除的那个节点,就可以将有两个孩子的情况转换成为只有一个孩子的情况,或者是转换成没有孩子的情况
doRemove(replaced);
}
复杂调整的函数
复杂函数又被分为了三种情况
- 被调整节点的兄弟为红,此时两个侄子定为黑色节点
- 被调整节点的兄弟为黑,两个侄子都为黑色节点
- 被调整节点的兄弟为黑,至少一个红色侄子
条件1
被调整节点的兄弟为红,此时两个侄子定位黑色节点
//如果删除的节点和被删除的节点都是黑色节点的情况下,进行复杂调整的函数
private void fixDoubleBlack(Node x){
if(x == root){
return;
}
//找到父节点
Node parent = x.parent;
//找到兄弟节点
Node sibling = x.sibling();
//条件1
if(isRed(sibling)){
if(x.isLeftChild()){
leftRotate(parent);
}else{
rightRotate(parent);
}
parent.color = Color.RED;
sibling.color = Color.BLACK;
fixDoubleBlack(x);
return;
}
}
条件2条件3
被调整节点的兄弟是黑色,两个侄子也是黑色
//条件2
if(sibling != null){
if(isBlack(sibling.left) && isBlack(sibling.right)){
sibling.color = Color.RED;
if(isRed(parent)){
parent.color = Color.BLACK;
}else{
fixDoubleBlack(parent);
}
}
//条件3兄弟是黑色,侄子是红色
else{
if(sibling.isLeftChild() && isRed(sibling.left)){
rightRotate(parent);
sibling.left.color = Color.BLACK;
sibling.color = parent.color;
}else if(sibling.isLeftChild() && isRed(sibling.right)){
sibling.right.color = parent.color;
leftRotate(sibling);
rightRotate(parent);
}else if(!sibling.isLeftChild() && isRed(sibling.left)){
sibling.left.color = parent.color;
rightRotate(sibling);
leftRotate(parent);
}else{
leftRotate(parent);
sibling.right.color = Color.BLACK;
sibling.color = parent.color;
}
parent.color = Color.BLACK;
}
}else{
fixDoubleBlack(parent);
}
总代码
public class RedBlackTree {
//枚举
enum Color {
RED,BLACK;
}
//根节点
private Node root;
//红黑树节点
private static class Node {
int key;
Object value;
Node left;
Node right;
Node parent; //父节点
Color color;
public Node(int key, Object value) {
this.key = key;
}
//判断当前节点是否是左孩子
boolean isLeftChild(){
//如果父节点都为null的话,根本就不可能会有左孩子,同样如果父节点的左孩子不是当前节点的话也可以判断当前节点不是左孩子
return parent != null && parent.left == this;
}
//返回当前节点的叔叔节点
Node uncle(){
//判断如果当前节点的父节点和爷爷节点有一个为null的话,就可以判断当前节点并没有叔叔节点
if(parent == null || parent.parent == null){
return null;
}
//判断父节点是否是爷爷节点的左孩子,如果是返回爷爷节点的右孩子
if(parent.isLeftChild()){
return parent.parent.right;
}else{
return parent.parent.left;
}
}
//返回当前的兄弟节点
Node sibling(){
//判断当前节点的父节点如果为null了的话,就不会有兄弟节点了
if(parent == null){
return null;
}
//判断如果当前节点是父节点的左孩子,则返回右节点,反之返回左节点
if(this.isLeftChild()){
return parent.right;
}else{
return parent.left;
}
}
}
//判断当前节点是否是红色
boolean isRed(Node node){
//判断如果当前节点不为null,并且当前节点的颜色等于红色的时候才是红色节点
return node != null && node.color == Color.RED;
}
//判断当前节点是否是黑色节点
boolean isBlack(Node node){
//如果不是红色节点就是黑色节点
return !isRed(node);
}
/**
* 右旋函数
* 1.对于parent的处理
* 2.旋转后行嗯的父子关系
* @param pink
*/
private void rightRotate(Node pink){
//找到粉色节点的父节点
Node parent = pink.parent;
//pink节点的左孩子是yellow节点
Node yellow = pink.left;
//green节点是yellow节点的右侧孩子
Node green = yellow.right;
//判断当前绿色节点不为null的话,绿色节点的父节点就应该等于粉色节点
if(green != null){
green.parent = pink;
}
//旋转之后让黄色节点的右孩子等于粉色节点
yellow.right = pink;
//旋转之后黄色节点的父节点等于粉色节点的父节点
yellow.parent = pink.parent;
//旋转之后粉色节点的左孩子等于绿色节点
pink.left = green;
//旋转之后粉色节点的父节点等于黄色节点
pink.parent = yellow;
//判断如果粉色节点的父节点为null的话,根节点就变为黄色节点
if(parent == null ){
root = yellow;
}
//如果父节点的左孩子是粉色节点的话
else if(parent.left == pink){
//父节点的左孩子就等于黄色节点
parent.left = yellow;
}else{
//父节点的右孩子就等于黄色节点
parent.right = yellow;
}
}
/**
* 左旋函数
*
* @param pink
*/
private void leftRotate(Node pink) {
// 找到粉色节点的父节点
Node parent = pink.parent;
// 粉色节点的右孩子是黄色节点
Node yellow = pink.right;
// 绿色节点是黄色节点的左侧孩子
Node green = yellow.left;
// 如果绿色节点不为null,将绿色节点的父节点设为粉色节点
if (green != null) {
green.parent = pink;
}
// 旋转后,将黄色节点的左孩子设为粉色节点
yellow.left = pink;
// 将黄色节点的父节点设为粉色节点的父节点
yellow.parent = pink.parent;
// 旋转后,将粉色节点的右孩子设为绿色节点
pink.right = green;
// 将粉色节点的父节点设为黄色节点
pink.parent = yellow;
// 如果粉色节点的父节点为null,将黄色节点设为新的根节点
if (parent == null) {
root = yellow;
}
// 如果粉色节点是其父节点的左孩子,更新父节点的左孩子为黄色节点
else if (parent.left == pink) {
parent.left = yellow;
}
// 如果粉色节点是其父节点的右孩子,更新父节点的右孩子为黄色节点
else {
parent.right = yellow;
}
}
/**
* 新增或者更新
* 正常增,遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key,Object value){
Node p = this.root;
Node parent = null;//设置父节点为null
while ( p!= null){
parent = p;//将父节点跟新为当前节点
if(p.key > key){
p = p.left;
}else if(p.key < key){
p = p.right;
}else{
p.value = value; //跟新的逻辑
return;
}
}
//创建出来的需要添加的节点
Node inserted = new Node(key, value);
//判断如果父节点为null的话,根节点就是此时需要添加的节点
if(parent == null){
root = inserted;
}
//如果key比父节点的key还小的话,父节点的左孩子就是添加的节点
else if(key < parent.key){
parent.left = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}else
//如果key比父节点的key大的话,父节点的右孩子就是添加的节点
{
parent.right = inserted;
//设置此时添加节点的父节点就是上面设置好的父节点
inserted.parent = parent;
}
//遇到红红不平衡进行调整
fixRedRed(inserted);
}
/**
* 遇到红红不平衡的进行调整
* @param x
*/
private void fixRedRed(Node x){
//1.判断如果插入节点为根节点的话,直接变黑即可
if(x == root){
x.color = Color.BLACK;
return;
}
//2.插入节点的父亲若为黑色,树的红黑性质不变,无需调整
if(isBlack(x.parent)){
return;
}
//找打父亲节点
Node parent = x.parent;
//找到叔叔节点
Node uncle = x.uncle();
//找到爷爷节点
Node grabdparent = parent.parent;
if(isRed(uncle)){
//将父节点的颜色设置为黑色
parent.color = Color.BLACK;
//将叔叔节点的颜色设置为黑色
uncle.color = Color.BLACK;
//将爷爷节点的颜色设置为红色
grabdparent.color = Color.RED;
//将爷爷节点传入到当前函数中,一直递归循环,直到将根节点设置为黑色
fixRedRed(grabdparent);
return;
}else{
//条件4中的条件1,如果父节点为爷爷节点的左孩子,当前传过来的节点也是左孩子的话
if(parent.isLeftChild() && x.isLeftChild()){ //条件1
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//进行右旋
rightRotate(grabdparent);
}else if(parent.isLeftChild() && !x.isLeftChild()){ //条件2
//先进行左旋
leftRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//接着右旋即可
rightRotate(grabdparent);
}else if(!parent.isLeftChild() && !x.isLeftChild() ) {
//将父节点变为黑色
parent.color = Color.BLACK;
//将爷爷节点变为红色
grabdparent.color = Color.RED;
//左旋
leftRotate(grabdparent);
} else{
//将父节点进行右旋
rightRotate(parent);
//将当前节点设置为黑色
x.color = Color.BLACK;
//将爷爷节点设置为红色
grabdparent.color = Color.RED;
//左旋
leftRotate(grabdparent);
}
}
}
/**
* 删除,正常删除,会用到李代桃僵的技巧,遇到嘿嘿不平衡进行调整
* @param key
*/
public void remove(int key){
//查找删除的节点
Node deleted = find(key);
//如果删除节点为null的时候直接return掉,就不往下执行了
if(deleted == null){
return;
}
//进行删除操作
doRemove(deleted);
}
//如果删除的节点和被删除的节点都是黑色节点的情况下,进行复杂调整的函数
private void fixDoubleBlack(Node x){
if(x == root){
return;
}
//找到父节点
Node parent = x.parent;
//找到兄弟节点
Node sibling = x.sibling();
//条件1
if(isRed(sibling)){
if(x.isLeftChild()){
leftRotate(parent);
}else{
rightRotate(parent);
}
parent.color = Color.RED;
sibling.color = Color.BLACK;
fixDoubleBlack(x);
return;
}
//条件2
if(sibling != null){
if(isBlack(sibling.left) && isBlack(sibling.right)){
sibling.color = Color.RED;
if(isRed(parent)){
parent.color = Color.BLACK;
}else{
fixDoubleBlack(parent);
}
}
//条件3兄弟是黑色,侄子是红色
else{
if(sibling.isLeftChild() && isRed(sibling.left)){
rightRotate(parent);
sibling.left.color = Color.BLACK;
sibling.color = parent.color;
}else if(sibling.isLeftChild() && isRed(sibling.right)){
sibling.right.color = parent.color;
leftRotate(sibling);
rightRotate(parent);
}else if(!sibling.isLeftChild() && isRed(sibling.left)){
sibling.left.color = parent.color;
rightRotate(sibling);
leftRotate(parent);
}else{
leftRotate(parent);
sibling.right.color = Color.BLACK;
sibling.color = parent.color;
}
parent.color = Color.BLACK;
}
}else{
fixDoubleBlack(parent);
}
}
private void doRemove(Node deleted) {
//查找到删除剩下的节点
Node replaced = findReplaced(deleted);
Node parent = deleted.parent;//找到被删除节点的父节点
if(replaced == null){ //如果删除后留下的剩余节点为空的话,直接return,不再继续向下执行
if(deleted == root){
root = null;
}else{
if(isBlack(deleted)){
//复杂调整
fixDoubleBlack(deleted);
}else{
//红色叶子,无需任何处理
}
//如果被删除的节点是父节点的左侧,将左侧的孩子置为null
if(deleted.isLeftChild()){
parent.left = null;
}else{
//如果被删除的节点是父节点的右侧,将右侧的孩子置为null
parent.right = null;
}
//将被删除节点的父节点也置为空
deleted.parent = null;
}
return;
}
//有一个孩子的情况
if(deleted.left == null || deleted.right == null){
if(deleted == root){
//用剩余节点替换了根节点的key,value,根节点孩子为null,颜色保持黑色不变
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
}else{
//判断如果被删除的节点不是根节点,并且被删除的节点在父节点的左侧的话,将父节点的左侧置为删除剩下的节点
if(deleted.isLeftChild()){
parent.left = replaced;
}else{
//判断如果被删除的节点不是根节点,并且被删除的节点在父节点的右侧的话,将父节点的右侧置为删除剩下的节点
parent.right = replaced;
}
//被删除剩下的节点的父节点就变为了被删除节点的父节点
replaced.parent = parent;
//下面三行代码的意思实际上就是将被删除节点进行垃圾回收
deleted.left = null;
deleted.right = null;
deleted.parent = null;
//当被删除的节点和剩下的节点都是黑色的时候
if(isBlack(deleted) && isBlack(replaced)){
//复杂处理
fixDoubleBlack(replaced);
}else{
//当删除的节点是黑色,但是剩下的节点是红色的话,将剩下的这个红色节点变为黑色
replaced.color = Color.BLACK;
}
}
return;
}
//有两个孩子=======>有一个孩子或没有孩子的情况了
int t = deleted.key; //找到删除节点的key
deleted.key = replaced.key; //找到删除后留剩下的节点key,其实也就是找到后继节点,将后继节点的key变为删除节点的key
replaced.key = t; //删除后剩下节点的key等于删除节点的key,只是也就是做一个交换,将两个节点的key
//下面的三行代码是用来交换值的
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
//接下来删除要删除的那个节点,就可以将有两个孩子的情况转换成为只有一个孩子的情况,或者是转换成没有孩子的情况
doRemove(replaced);
}
//查找删除节点
private Node find(int key){
Node p = root;
while (p!=null){
if(key < p.key){
p = p.left;
}else if(key > p.key){
p = p.right;
}else{
return p;
}
}
return null;
}
//查找剩余节点
private Node findReplaced(Node deleted){
//当删除节点的左右孩子都不存在时
if(deleted.left == null && deleted.right == null){
return null;
}
//当删除节点没有左孩子,只有右孩子的话执行下面这句
if(deleted.left == null && deleted.right != null){
return deleted.right;
}
//当删除节点只有左孩子,没有右孩子的话
if(deleted.left != null && deleted.right ==null){
return deleted.left;
}
//当删除节点有两个孩子的时候需要找到后继节点
Node s = deleted.right;
//当删除节点的左孩子不等于null的时候,后继节点就一直循环,直到找到后继节点为止
while (s.left != null){
s = s.left;
}
return s;
}
}