红黑树的定义
- 每个节点都是有颜色的,不是红色就是黑色
- root 必须是黑色的
- 所有叶子节点都是黑色的,叶子节点是NULL 节点,不存储实际的数据
- 每个红色的节点必须有两个黑色的子节点(从每个叶子节点到根节点的所有路径上不能有连续的红色节点)
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点
预备知识
当进行插入或者删除时,红黑树的条件有可能被破坏,这就需要我们进行调整。调整可以分为两类:颜色调整和结构调整。结构调整包括两个操作:左旋、右旋
左旋
/**
* 以指定的node 节点为根节点进行左旋操作
* @param node
*/
public void leftRotate(RBNode<T> node){
RBNode<T> child = node.getRight();
//设置孩子节点的父节点
child.setParent(node.getParent());
if(node.getParent() == null){//node 如果是根节点
this.root = child;//设置孩子节点为root
}else{
//node 是父节点的左孩子
if(node.getParent().getLeft() == node)
//把node 的孩子结点放在node 父节点的左子树上
node.getParent().setLeft(child);
else
//node 是父节点的右孩子,把node 的孩子结点放在node 父节点的右子树上
node.getParent().setRight(child);
}
//设置node 节点的父节点
node.setParent(child);
node.setRight(child.getLeft());//node 节点的右孩子为它原来右孩子的左孩子
if(child.getLeft() != null){
child.getLeft().setParent(node);//右孩子原来的左孩子的父节点为node.
}
child.setLeft(node);
}
右旋
/**
* 以指定的node 节点为根节点进行右旋操作
* @param node
*/
public 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().getLeft() == node)
node.getParent().setLeft(child);
else
node.getParent().setRight(child);
}
node.setParent(child);
node.setLeft(child.getRight());
if(child.getRight() != null){
child.getRight().setParent(node);
}
child.setRight(node);
}
插入
新插入的节点为红色,所以说当新插入节点的父节点为黑色,那么就不用调整,因为红黑树的结构没有被破坏。
当新插入的节点的父节点为红色时,破坏了红黑树的结构,我们需要对其进行调整。当结构被破坏时分为以下三种情况
1.叔叔节点也为红色
2.叔叔节点为空,且祖父节点、父节点和新插入的节点在一条斜线上
3.叔叔节点为空,祖父节点、父节点和新插入的节点不在一条斜线上
/**
* 红黑树的插入操作
* @param val
*/
public void insert(T val){
if(this.root == null){
this.root = new RBNode<>(val, null, null, null, Color.BLACK);
return;
}
RBNode<T> parent = null;
RBNode<T> cur = this.root;
//
while(cur != null){
parent = cur;
//待插入的元素小于当前元素
if(cur.getData().compareTo(val) > 0){
cur = cur.getLeft();
//待插入的元素大于当前元素
} else if(cur.getData().compareTo(val) < 0){
cur = cur.getRight();
} else {
//待插入的元素等于当前元素
return;
}
}
//新插入的元素
RBNode<T> node = new RBNode<>(val, null, null, parent, Color.RED);
//新插入的元素大于父节点的元素,放在右孩子上
if(node.getData().compareTo(parent.getData()) > 0){
parent.setRight(node);
} else {
//放在左孩子上
parent.setLeft(node);
}
// 判断新插入节点的父节点如果是红色,进行红黑树的插入调整操作
if(color(parent) == Color.RED){
fixAfterInsert(node);
}
}
/**
* 红黑树的插入调整操作
* @param node
*
* 新插入的节点都是红色的,如果遇到新插入的节点为黑,则插入修复操作结束
*
* 插入修复操作有三种情况(都是父节点为红)
* 1.叔叔节点为红色
* 2.叔叔节点为空或者为黑色,且祖父节点、父节点、新结点在一条线上
* 3.叔叔节点为空或者为黑色,且祖父节点、父节点、新结点不在一条线上
*/
private void fixAfterInsert(RBNode<T> 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 { //叔叔节点不是红色,三点不在一条线上
if(right(parent(node)) == node){
// 情况3
node = parent(node);
leftRotate(node);
}
// 情况2::三点一线
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 {
// 情况3::三点不是一线
if(left(parent(node)) == node){
node = parent(node);
rightRotate(node);
}
// 情况2::三点一线
setColor(parent(node), Color.BLACK);
setColor(parent(parent(node)), Color.RED);
leftRotate(parent(parent(node)));
break;
}
}
}
// 回溯过程中,有可能回溯到根节点
setColor(this.root, Color.BLACK);
}
删除
待删除的节点有三种情况
- 待删除的节点的左右孩子都不为空
- 待删除节点只有一个孩子
- 待删除节点是叶子节点
第一种情况我们可以通过找前驱或者后继将它转化为后两种情况。即只有一个节点或者没有节点。
如果删除的节点为红色节点,那么就不用进行调整,红黑树的结构没有被破坏。
如果删除的节点为黑色节点,那么就破坏了红黑树的第5 个条件。当待删除的节点的子节点有红色节点的时候,将其变为黑色节点。否则只有从兄弟节点借黑色的节点了。那么又分为以下几种情况
1.待删除节点的兄弟节点是红色的节点
2.待删除的节点兄弟节点是黑色的,且其子节点也都是黑色的
3.待调整的节点的兄弟节点右子树是黑色的,左子树的红色的(假设待删除节点是其父节点的左孩子)
4.待调整节点的兄弟节点的右子树是红色的,左子树是黑色的(假设待删除节点是其父节点的右孩子)
/**
* 红黑树的删除操作
* @param val
*/
public void remove(T val){
if(this.root == null){
return;
}
RBNode<T> cur = this.root;
//找到待删除的节点
while(cur != null){
if(cur.getData().compareTo(val) > 0){
cur = cur.getLeft();
} else if(cur.getData().compareTo(val) < 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.getLeft() != null){
cur = cur.getLeft();
}
old.setData(cur.getData()); // cur已经指向后继节点了,直接删除
}
RBNode<T> child = (cur.getLeft() != null ? cur.getLeft() : cur.getRight());
// 删除节点cur有孩子节点
if(child != null){
child.setParent(cur.getParent());
if(cur.getParent() == null){//如果cur 就是根节点
this.root = child;
//如果待删除节点是其父节点的左子树
} else if(cur.getParent().getLeft() == cur){
cur.getParent().setLeft(child);
} else {
//如果待删除节点是其父节点的右子树
cur.getParent().setRight(child);
}
// 删除节点是黑色节点的时候,需要做红黑树的删除调整操作
if(cur.getColor() == Color.BLACK){
fixAfterDelete(child);
}
} else {
//删除节点cur 没有孩子节点
if(cur.getParent() == null){
this.root = null;
// 删除节点是黑色节点的时候,需要做红黑树的删除调整操作
}else if(cur.getColor() == Color.BLACK){
fixAfterDelete(cur);
}
if(parent(cur).getLeft() == cur){
parent(cur).setLeft(null);
}else{
parent(cur).setRight(null);
}
}
}
/**
* 红黑树的删除调整操作
* @param node
*/
private void fixAfterDelete(RBNode<T> node) {
//被删除的节点不是根节点,而且被删除的节点是黑色的
while(node != this.root && color(node) == Color.BLACK){
// 当前路径上没办法补充黑色节点,得向兄弟节点借调黑色
if(left(parent(node)) == node){ // 删除节点在左子树上
RBNode<T> brother = right(parent(node));
// 情况一::被删除节点的兄弟节点是红色的,没法向兄弟节点借,需要进行左旋转,把兄弟的孩子节点往上提提
if(color(brother) == Color.RED){
// 颜色调整和旋转操作
brother.setColor(Color.BLACK);//兄弟节点设置为黑色
parent(brother).setColor(Color.RED);//父节点设置为红色
//以父节点为根节点旋转
leftRotate(parent(brother));
brother = right(parent(node));
}
// 情况二::兄弟节点是黑色,其两个孩子也都是黑色,把兄弟节点改为红色,继续回溯父节点
if(color(left(brother)) == Color.BLACK
&& color(right(brother)) == Color.BLACK){
// 颜色调整和旋转操作
brother.setColor(Color.RED);
node = parent(node);
}else {
// 情况三 兄弟节点的右孩子是黑色,
if(color(right(brother)) == Color.BLACK){
brother.setColor(Color.RED);
left(brother).setColor(Color.BLACK);
rightRotate(brother);
brother = right(parent(node));
}
// 情况四 兄弟节点的右孩子是红色的。颜色调整,外加一个左旋操作就完成了
brother.setColor(parent(node).getColor());
parent(node).setColor(Color.BLACK);
right(brother).setColor(Color.BLACK);
leftRotate(parent(node));
node = this.root;
}
} else { // 删除节点在右子树上
RBNode<T> brother = left(parent(node));
// 情况一,兄弟节点是红色,无法借一个黑色节点,需要右旋转,把兄弟节点的子节点黑色节点往上提
if(color(brother) == Color.RED){
brother.setColor(Color.BLACK);
parent(node).setColor(Color.RED);
rightRotate(parent(node));
brother = left(parent(node));
}
// 情况二,兄弟节点是黑色,而且兄弟节点的两个孩子也是黑色节点
if(color(left(brother)) == Color.BLACK
&& color(right(brother)) == Color.BLACK){
brother.setColor(Color.RED);
node = parent(brother);
} else {
// 情况三,兄弟节点是黑色,但是左孩子没有红色节点
if(color(left(brother)) == Color.BLACK){
right(brother).setColor(Color.BLACK);
brother.setColor(Color.RED);
leftRotate(brother);
brother = left(parent(node));
}
// 情况四, 兄弟节点是黑色,而且左孩子是红色,直接左旋,并把有孩子直接调成黑色
brother.setColor(parent(node).getColor());
parent(node).setColor(Color.BLACK);
left(brother).setColor(Color.BLACK);
rightRotate(parent(node));
node = this.root;
}
}
}
/**
* 1.删除节点的孩子补上来以后是红色,直接把孩子从红色改成黑色
* 2.向上回溯过程中,如果碰到红色节点,则直接把红色修改成黑色节点
*/
setColor(node, Color.BLACK);
}