红黑树
- 我会给大家从理论,图析,代码实现来看看红黑树,不难的,大家认真看
一、首先什么是红黑树?
我们可以看作特殊的AVL树,再通俗说:它的本质还是BST树,万变不离其宗。不过给AVL树加上特有的性质而已,红黑树只有左右旋转两种方式,和AVL树四种旋转相比简单,不过多了节点着色,我们首先来看看红黑树的五大性质:
1、节点都有颜色,不是黑色,就是红色
2、null表示黑色(叶子节点left和right的地址域)
3、根节点是黑色
4、不能出现连续的红色(连续:手拉手那种=-=)
5、从根节点到任意叶子节点路径上的黑色节点数量相同
我么可以从4,5条性质得到一个结论:红黑树节点子树高的不能超过低的二倍(验证:可以画左右两条路径,一条路径全黑色,另一条路径红黑相间,自己可以看看)
二、红黑树旋转图析
- 图是我自己画的,直观易懂,我分析了AVL和红黑树的旋转,大家可以比较学习(红黑树旋转情况就只有我画的这几种情况,没别的了),加油!
三、代码实现(红黑树的增删,代码上有我的注释)
大家看代码实现前,一定要认真看我上面的:图析(重要,直观易懂)
1.左右旋转代码
/**
* 以node为根节点进行左旋转
* @param node
*/
public void leftRotate(Entry<T> node) {
Entry<T> child = node.right;
node.right = child.left;
if (child.left!=null){
child.left.parent = node;
}
child.parent = node.parent;
if (node.parent==null){
root = child;
}else {
if (node.parent.left == node){
node.parent.left = child;
}else {
node.parent.right = child;
}
}
child.left = node;
node.parent = child;
}
/**
* 以node为根节点进行右旋转
* 原理同左旋转
* @param node
*/
public void rightRotate(Entry<T> node) {
Entry<T> child = node.left;
node.left = child.right;
if (child.right!=null){
child.right.parent = node;
}
child.parent = node.parent;
if (node.parent==null){
root = child;
}else {
if (node.parent.left == node){
node.parent.left = child;
}else {
node.parent.right = child;
}
}
child.right = node;
node.parent = child;
}
2.创建红黑树类和节点类
public class RBTree <T extends Comparable<T>>{
private Entry<T> root = null; //头节点
static final boolean Red = false;
static final boolean Black = true;
static class Entry<E> {
private E value;//数据域
private Entry<E> left;//左孩子
private Entry<E> right;//右孩子
private Entry<E> parent;//父节点
boolean color;//着色
public Entry(E value, Entry<E> left,Entry<E> parent, Entry<E> right, boolean color) {
this.value = value;
this.left = left;
this.right = right;
this.parent = parent;
this.color = color;
}
public Entry(E value) {//增加的节点都为红色
this(value,null,null,null,Red);
}
}
3.插入调整代码
/**
* 插入后调整
* @param node
*/
public void insertAfter(Entry<T> node){
//父亲,祖父都在,父亲节点是红
while (node.parent!=null&&node.parent.parent!=null&&node.parent.color==Red){
//左边判断(父亲是孩子的左孩子)
if (node.parent ==node.parent.parent.left){//父节点是祖父节点的左孩子
if (node.parent.parent.left!=null&&node.parent.parent.right.color==Red){//叔叔节点为红色
//那就把父亲和叔叔都变黑,祖父变红;更新node节点到祖父(向上判断)
node.parent.color = Black;
node.parent.parent.color = Red;
node.parent.parent.right.color = Black;
node = node.parent.parent;
continue;
}
if (node.parent.right == node){//叔叔节点为黑色,node是右孩子
leftRotate(node.parent);
}
//叔叔黑色且node是左孩子
node.parent.color = Black;
node.parent.parent.color = Red;
rightRotate(node.parent.parent);
}else {//父亲是孩子的右孩子
if (node.parent.parent.left!=null&&node.parent.parent.left.color==Red){//叔叔节点为红色
//那就把父亲和叔叔都变黑,祖父变红;更新node节点到祖父(向上判断)
node.parent.color = Black;
node.parent.parent.color = Red;
node.parent.parent.left.color = Black;
node = node.parent.parent;
continue;
}
if (node.parent.left==node){//叔叔节点为黑,且node是左孩子
rightRotate(node.parent);
}
//叔叔黑色,且node右孩子
node.parent.color = Black;
node.parent.parent.color = Red;
leftRotate(node.parent.parent);
}
}
root.color = Black;
}
4.插入代码
/**
* RBT树的插入
* @param node
*/
private void insert(Entry<T> node) {//就是非递归的BST插入操作,末尾加上调整操作就好了
if (root == null){
root = node;
}
Entry<T> cur = root;
Entry<T> par = null;//保存cur的父节点
while (cur!=null){
if (cur.value.compareTo(node.value)>0){
par = cur;
cur = cur.left;
}else if (cur.value.compareTo(node.value)<0){
par = cur;
cur = cur.right;
}else {
return;
}
}
/**
* 退出循环后,cur==null
* 把新插入的叶子节点写入父节点相应的地址域中
*/
if (par.value.compareTo(node.value)>0){
par.left = node;
}else {
par.right = node;
}
node.parent = par;
// 插入完后调整
insertAfter(node);
}
public void insert(T data) {
Entry<T> node = new Entry<>(data);
if (node != null)
insert(node);
}
5.删除调整代码
//删除调整代码
public void removeAfter(Entry<T> child,Entry<T> parent){
while ((child==null|| child.color==Black)&& child!=root){
if (parent.left==child){//child是左孩子
if (parent.right!=null&&parent.right.color==Red){//兄弟节点为红色
parent.right.color = Black;
parent.color = Red;
leftRotate(parent);
}
//兄弟和兄弟的两个孩子都为黑色
if ((parent.right.left!=null || parent.right.left.color==Red)&&
(parent.right.right==null ||parent.right.right.color==Black)){
parent.right.color=Red;
child = parent;
}else {//兄弟黑色,兄弟左孩子红色,右孩子黑色
if (parent.right.right==null||parent.right.left.color==Red){
parent.right.left.color = Black;
parent.right.color = Red;
rightRotate(parent.right);
}
//兄弟黑色,兄弟右孩子红
parent.right.color = parent.color;
parent.color = Black;
if (parent.right.right!=null){
parent.right.right.color = Black;
}
leftRotate(parent);
break;
}
}else {//child 是右孩子
if (parent.left!=null && parent.left.color==Red){//兄弟节点为红色
parent.left.color = Black;
parent.color = Red;
rightRotate(parent);
}
//兄弟和兄弟的两个孩子都为黑色
if ((parent.left.right!=null || parent.left.right.color==Red)&&
(parent.left.left==null ||parent.left.left.color==Black)){
parent.left.color=Red;
child = parent;
}else {//兄弟黑色,兄弟左孩子红色,右孩子黑色
if (parent.left.left==null||parent.left.right.color==Red){
parent.left.right.color = Black;
parent.left.color = Red;
rightRotate(parent.left);
}
//兄弟黑色,兄弟左孩子红
parent.left.color = parent.color;
parent.color = Black;
if (parent.left.left!=null){
parent.left.left.color = Black;
}
leftRotate(parent);
break;
}
}
}
}
6.删除代码
/**
* RBT树的删除
* @param value
*/
public void remove(T value){
if (this.root == null){
return;
}
Entry<T> parent=null;
Entry<T> cur = this.root;
while (cur!=null){
if (cur.value.compareTo(value)>0){
parent = cur;
cur = cur.left;
}else if(cur.value.compareTo(value)<0){
parent = cur;
cur = cur.right;
}else{
break;//结束while循环,但是否找到,要看cur是否为空
}
if (cur==null){//没找到
return;
}
}
/**
* 找到了待删value节点,cur指向待删节点
* 先处理 待删节点有两个孩子:及就是要删除的节点有左右两个孩子
*/
if(cur.left != null && cur.right != null){
//找前驱节点代替待删除节点的值,然后删除前驱节点
Entry<T> old = cur; //记录待删节点
parent = cur;
cur = cur.left; //cur指向待删节点的左孩子域
while (cur.right != null){ //找前驱结点:待删节点左子树中值最大的节点
parent = cur;
cur = cur.right;
}
//此时cur指向前驱结点
old.value = cur.value;
}
/**
* 统一删除cur指向的节点 即:有一个节点或者没有节点的情况
* child指向待删节点的不为空的孩子,可能为null
*/
Entry<T> child = cur.left;
if(child == null){
child = cur.right;
}
//两种情况 child为空和不为空
if (child!=null){
if (parent== null){//说明是根节点
this.root = child;
}else if (parent.left ==cur){
parent.left = child;
}else {
parent.right = child;
}
if (cur.color==Black && child.color == Black){//孩子和待删除节点都为黑
removeAfter(child,parent);
}else if (cur.color==Black && child.color == Red){//若孩子为红色,那变成黑色就好了
child.color= Black;
}
}else {//孩子为空
if (parent == null){//说明cur就是根节点,删除了就没了
root = null;
}else {//cur没有孩子,cur是叶子节点
if (cur.color == Black){
removeAfter(cur,parent);
}
if (parent.left == cur){
parent = null;
}else {
parent = null;
}
}
}
}