1.红黑树的定义:
AVL树在数据量大的情况下,如果进行插入和删除操作,为了维护 AVL树节点的平衡,在插入和删除数据时做了大量的旋转操作,拖慢了进行数据插入删除操作的时间,而红黑树相对二叉树来说,并非绝对的平衡树,它要求左右子树的高度差不超过2倍,故进行旋转操作的次数减少。
/**
* 红黑树的节点颜色定义
*/
enum Color{
BLACK,RED;
}
/**
* 红黑树的节点类型定义
* @param <T>
*/
class RBNode<T extends Comparable<T>> {
private T data;
private RBNode<T> left;
private RBNode<T> right;
private RBNode<T> parent;
private Color color;
public RBNode(T data, Color color) {
this.data = data;
this.color = color;
}
public RBNode(T data, RBNode<T> left, RBNode<T> right, RBNode<T> parent, Color color) {
this.data = data;
this.left = left;
this.right = right;
this.parent = parent;
this.color = color;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public RBNode<T> getLeft() {
return left;
}
public void setLeft(RBNode<T> left) {
this.left = left;
}
public RBNode<T> getRight() {
return right;
}
public void setRight(RBNode<T> right) {
this.right = right;
}
public RBNode<T> getParent() {
return parent;
}
public void setParent(RBNode<T> parent) {
this.parent = parent;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
红黑树的性质如下:
1.每个节点都是由颜色的,不是红色就是黑色
2.root必须是黑色
3.所有叶子节点都是黑色,叶子节点都是null,不存储实际的数据
4.每个红色节点都包含两个黑色子节点,或者说每个叶子节点到根节点的所有路径中,不允许出现两个连续的红色节点
5.从任一节点到其叶子节点的简单路径都包含相同数目的黑色节点
/**
* 红黑树的定义
*/
class RBTree<T extends Comparable<T>>{
private RBNode<T> root;
/**
* 获取红黑树节点颜色
*/
private Color color(RBNode<T> node){
//节点为空 默认颜色为黑色 若不为空,获取到当前节点的颜色
return node== null?Color.BLACK:node.getColor();
}
/**
* 设置节点的颜色
*/
private void setColor (RBNode<T> node,Color color ){
node.setColor(color);
}
/**
* 获取当前节点的父亲节点
*/
private RBNode<T> parent(RBNode<T> node){
return node.getParent();
}
/**
* 获取当前节点的左孩子节点
*/
private RBNode<T> left(RBNode<T> node){
return node.getLeft();
}
/**
* 获取node节点的右孩子节点
*/
private RBNode<T> right(RBNode<T> node){
return node.getRight();
}
2.红黑树的左旋:
左旋原理图解如下:
我们对下图红黑树中的节点40进行左旋操作:
得到的左旋结果图如下:
代码实现如下:
/**
* 红黑树的左旋
*/
private void leftRotate(RBNode<T> node){
RBNode<T> child = node.getRight(); //当前节点的孩子节点
child.setParent(node.getParent());
if (node.getParent() == null){
this.root = child;
}
else if(node.getParent().getLeft()==node){
node.getParent().setLeft(child);
}
else {
node.getParent().setRight(child);
}
node.setParent(child);
node.setRight(child.getLeft());
if (child.getLeft()!=null){
child.getLeft().setParent(node);
}
child.setLeft(node);
node.setParent(child);
}
3.红黑树的右旋:
右旋原理图解如下:
我们对下图红黑树中的节点120进行右旋操作:
得到的右旋结果图如下:
代码实现如下:
/**
* 红黑树的右旋操作
*/
private void rightRotate(RBNode<T> node){
RBNode<T> child = node.getLeft();
child.setParent(node.getParent());
if (node.getParent() == null){
this.root = child;
}else if(node.getParent()==null){
this.root = child;
}else {
node.getParent().setRight(child);
}
node.setParent(child);
node.setLeft(child.getRight());
if (child.getRight()!=null){
child.getRight().setParent(node);
}
child.setRight(node);
}
4.红黑树的插入操作:
红黑树的根节点是黑色,其他新插入的节点为红色,因为红色节点不影响红黑树的性质,不会对性质5造成影响,如果父节点为黑色,则不需要再进行后续的修复操作,如果父节点为红色,则需要进行后序的修复操作,
需要进行修复操作的情况有以下三种(新插入节点为红色):
1.叔叔节点为红色
2.叔叔节点为黑色,且祖父节点,父节点,和新节点处于一条斜线上
3.叔叔节点为黑色,且祖父节点,父节点,和新节点不处于一条斜线上
如上图,新插入节点和其父亲节点都为红色,违反了红黑树的规则4,且X的叔叔节点 y 为红色,符合情况1。
**调整方式:**改变某些节点的颜色(把其父节点和叔叔节点设为黑色,祖父节点设为红色)改变结果如下图:
如上图,x和其父节点都为红色,违反了规则3,叔叔节点y为黑色,且当前节点和其父节点,祖父节点都在同一条斜线上,符合情况3
**调整方式:**对部分节点进行旋转操作(需要进行两次旋转),第一次旋转操作结果如下:
如上图,当前节点x和其父节点都为红色,(此处指的是更新后的节点)违反了红黑树规则3,叔叔节点为黑色,且当前接节点、父节点、祖父节点位于同一斜线上,符合情况2
**调整方式:**改变某些节点的颜色,对某些节点进行旋转,第二次旋转操作结果如下:
调整完成
/**
* 红黑树的插入操作
*/
private void insert(T data){
if (root == null){
//根节点为空时,新插入的节点即为红黑树的根节点,根节点颜色为黑
root=new RBNode<T>(data,Color.BLACK);
return;
}
RBNode<T> parent = null;//指向父节点的索引
RBNode<T> cur = root; //指向根节点的索引
while(cur!=null){ //根节点不为空时
if (cur.getData().compareTo(data)>0){
parent = cur;
cur = cur.getLeft();
}else if (cur.getData().compareTo(data)<0){
parent = cur;
cur = cur.getRight();
} else {
return;
}
}
RBNode<T> node = new RBNode<T>(data,null,null,parent,Color.RED);
if (parent.getData().compareTo(node.getData())>0){
parent.setLeft(node);
}else {
parent.setRight(node);
}
//如果新插入的节点父节点是红色,则需要进行红黑树的调整
if (color(parent) == Color.RED){
fixAfterInsert(node);
}
}
/**
* 红黑树的插入调整
* @param node
*/
private void fixAfterInsert(RBNode<T> node) {
//node的父节点是红色 出现连续的红色节点 需要进行颜色调整
while(color(parent(node)) == Color.RED){
//当插入的节点在祖父节点的左子树上 (先左旋-再右旋)
if (left(parent(parent(node))) == parent(node)){
RBNode<T> uncle = right(parent(parent(node)));
//1.叔叔节点是红色
if (color(uncle)==Color.RED){
setColor(parent(node),Color.BLACK);//将父节点设置为黑色
setColor(uncle,Color.BLACK);//将叔叔节点设置为黑色
setColor(parent(parent(node)),Color.RED);//祖父节点设置为红色
node = parent(parent(node));//node/指向祖父节点 继续向上回溯调整
}
else {
//2.叔叔节点是黑色节点,当前节点、父亲节点、祖父节点不在同一直线上
if (right(parent(node)) == node){
//做一个左旋操作
node = parent(node);
leftRotate(node);
}
//3.叔叔节点是黑色,当前节点、父亲节点、祖父节点在一条斜线上
//直接进行右旋操作
setColor(parent(node),Color.BLACK);//将父节点设置为黑色,旋转以后就成为根节点
setColor(parent(parent(node)),Color.RED);//将祖父节点设置为红色
//以祖父节点为根节点做一个右旋操作
rightRotate(parent(parent(node)));
break;//调整完成 跳出调整操作
}
}else {
//插入节点在祖父节点的右子树上,此时和在左子树上的情况刚好相反(先右旋-再左旋)
RBNode<T> uncle = left(parent(parent(node)));
//1.叔叔节点是红色
if (color(uncle)==Color.RED){
setColor(parent(node),Color.BLACK);//将父节点设置为黑色
setColor(uncle,Color.BLACK);//将叔叔节点设置为黑色
setColor(parent(parent(node)),Color.RED);//祖父节点设置为红色
node = parent(parent(node));//node/指向祖父节点 继续向上回溯调整
}
else {
//2.叔叔节点是黑色节点,当前节点、父亲节点、祖父节点不在同一直线上
if (left(parent(node)) == node){
//做一个左旋操作
node = parent(node);
rightRotate(node);
}
//3.叔叔节点是黑色,当前节点、父亲节点、祖父节点在一条斜线上
//直接进行右旋操作
setColor(parent(node),Color.BLACK);//将父节点设置为黑色,旋转以后就成为根节点
setColor(parent(parent(node)),Color.RED);//将祖父节点设置为红色
//以祖父节点为根节点做一个右旋操作
leftRotate(parent(parent(node)));
break;//调整完成 跳出调整操作
}
}
}
//有可能回溯时把根节点设置为红色 直接把各个节点设置为黑色
setColor(this.root,Color.BLACK);
}
4.红黑树的删除操作:
删除时,如果删除的是红色节点,直接删除,如果删除的是黑色节点,删除节点后,违反了规则5,需要对红黑树进行调整。
**操作的总体思想是:**从兄弟节点借调节点,保持局部平衡,如果局部平衡达到了,就看整体的树是否平衡,如果不平衡,就接着向上回溯。
删除修操作分为四种情况(删除黑节点后):
1.待删除节点的兄弟节点是红色,无法借调一个黑色节点过来,但是兄弟节点的孩子节点一定都为黑色,我们可以通过旋转,使兄弟节点的孩子节点成为新的兄弟节点,此时兄弟节点为黑色,可以借调
图解如下:
2.兄弟节点是黑色节点,且兄弟节点的孩子节点都是黑色的,直接将兄弟节点设置为红色,然后从父节点开始进行回溯调整
图解如下;
3.兄弟节点是黑色节点,且兄弟节点的左子节点是红色,右子节点是黑色(兄弟节点在右边),如果兄弟节点在左边,则左子节点为黑色,右子节点为红色
图解如下:
4.待调整节点的兄弟节点是黑色的节点,且右子节点是红色的(兄弟节点在右边),如果兄弟节点在左边,则对应的就是左节点是红色的。
图解如下:
/**
* 红黑树的删除操作
*/
public void remove(T data){
if (this.root == null){
return;
}
RBNode<T> cur = this.root;
while (cur != null){
if (cur.getData().compareTo(data)>0){
cur= cur.getLeft();
} else if (cur.getData().compareTo(data)<0){
cur = cur.getRight();
}else {
break;
}
}
if (cur==null){
return;
}
//#待删除节点有两个孩子,转化为有一个孩子或者叶子节点的删除
if (cur.getLeft() != null&&cur.getRight()!=null){
//用后继节点代替 后继节点:待删除节点右子树中,值最小的节点
RBNode<T> old = cur;
cur = cur.getRight();
while(cur.getRight() != null){
cur=cur.getLeft();
}
old.setData(cur.getData());
}
//# 待删除节点有一个孩子或者没有孩子
RBNode<T> child = (cur.getLeft()!=null?cur.getLeft():cur.getRight());
if (child != null){
child.setParent(cur.getParent());
if (cur.getParent()==null){
this.root = child;
} else if (cur.getParent().getLeft() == cur){
cur.getParent().setLeft(child);
} else {
cur.getParent().setRight(child);
}
//如果删除的是黑色节点 需要进行调整
if (cur.getColor()==Color.BLACK){
fixAfterInsert(child);
}
}
else {
if (cur.getParent() == null){
this.root=null;
} else {
//删除的是叶子节点
if (cur.getColor() == Color.BLACK){
fixAfterInsert(cur);
}
//当前删除节点cur 没有孩子节点 上面把cur当作虚拟节点,进行红黑树删除调整
//完成后 把cue节点删掉
}
if (cur.getParent().getLeft() == cur){
cur.getParent().setLeft(null);
}else {
cur.getParent().setRight(null);
}
}
}
/***
* 红黑树的删除调整
*/
private void fixAfterDelete(RBNode<T> node){
//回溯过程中遇到根节点或者红色节点结束
while (node!=this.root && color(node)==Color.BLACK){
if (node == left(parent(node))){
RBNode<T> b = right(parent(node));
//1.兄弟节点是红色 无法借调一个节点,进行旋转操作,
// 将兄弟节点的黑色孩子节点向上提,使其成为可以借调的兄弟节点
if (color(b) == Color.RED){
b.setColor(Color.BLACK); //将兄弟节点颜色设为黑色
parent(node).setColor(Color.RED);//将父节点颜色设为红色
leftRotate(parent(node));//对其进行左旋
b=right(parent(node));
}
//2.兄弟节点是黑色,而且兄弟节点的两个孩子节点也是黑色节点,把兄弟黑带你改为红色,继续回溯父节点
if (color(left(b)) == Color.BLACK){
if (color(right(b)) == Color.BLACK){
left(b).setColor(Color.BLACK);
b.setColor(Color.RED);
rightRotate(b);
b = right(parent(node));
} else {
//3.兄弟节点是黑色,但是右孩子没有红色
if (color(right(b)) == Color.BLACK){
left(b).setColor(Color.BLACK);
b.setColor(Color.RED);
rightRotate(b);
b = right(parent(node));
}
//4.兄弟节点是黑色,且其右孩子是红色 直接左旋,并将右孩子直接调整成黑色
b.setColor(parent(node).getColor());
parent(node).setColor(Color.BLACK);
right(b).setColor(Color.BLACK);
leftRotate(parent(node));
node = this.root;
}
}
}
/**
* #删除黑色节点后,其孩子节点是红色,直接调成 黑色,结束上面的循环,保持黑色节点的数量保持不变
* #删除节点后,其孩子节点是黑色,向上回溯的过程中,遇到红色节点直接改为黑色节点,
* 结束上面的循环,保持黑色节点的数量不变
*/
}
}