红黑树Java基本操作-201805
(只讨论怎么操作)[参考 java.util.TreeMap]
一、定义
普通的二叉搜索树在插入或删除的时候,可能会出现树结构向一侧倾倒的情况。这时,这棵二叉树上将近似于链表。
红黑树(RBTree)是一棵二叉搜索树,在每个节点位增加了一个表示颜色的存储位(RED/BLACK)。通过对从根到叶子的简单路径上节点颜色的约束,确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。红黑树可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。
树的节点包含5个属性:key,left,right,parent,color。
红黑树满足下列5条性质:
1.每个节点非红即黑。
2.根节点是黑色的。
3.每个叶节点(null)是黑色的。
4.若一个节点是红色的,则其两个子节点为黑色。
5.对每个节点,从该节点到其后代各叶节点的简单路径上的黑色节点的个数(黑高)相同。
引理:一棵有n个内部节点的红黑树高度至多为2lg(n+1)
class Nord{ //定义节点构造
Nord parent;
Nord left;
Nord right;
int element;
boolean red;
Nord(Nord parent, Nord left, Nord right, int element,boolean red) {
super();
this.parent = parent;
this.left = left;
this.right = right;
this.element = element;
this.red=red;
}
@Override
public String toString() {
return "Nord [element=" + element +",red="+red+ "]";
}
}
二、旋转
当对红黑树进行插入或删除操作时,可能导致该树违反其基本性质。为了维护这些性质,必须改变某些结构的颜色和指针。
指针的修改是通过旋转来完成的。
左旋
//左旋-x.left和y.right不变,x.right=y.left,再用y替代x的位置,y.left=x
public Nord leftRotate(Nord x) {
Nord y;
if(x!=null&&((y=x.right)!=null)) {
x.right=y.left;
if(y.left!=null) {
y.left.parent=x;
}
y.parent=x.parent;
if(x.parent==null) {
root=y;
}else if(x.parent.left==x) {
x.parent.left=y;
}else {
x.parent.right=y;
}
y.left=x;
x.parent=y;
return y;
}
return null;
}
右旋-与左旋正相反
//右旋
public Nord rightRotate(Nord x) {
Nord y;
if(x!=null&&(y=x.left)!=null) {
x.left=y.right;
if(y.right!=null) {
y.right.parent=x;
}
y.parent=x.parent;
if(x.parent==null) {
root=y;
}else if(x.parent.left==x) {
x.parent.left=y;
}else {
x.parent.right=y;
}
y.right=x;
x.parent=y;
return y;
}
return null;
}
三、插入
和二叉搜索树的insert操作基本一致,最后把插入的节点颜色设Red。为保持红黑树的性质,调用insertFixup重新着色并旋转。
//插入
public boolean insert(int z) {
return insert(new Nord(null,null,null,z,RED),root);
}
//插入-和二叉搜索树的insert操作基本一致,最后把插入的节点颜色设Red,调用insertFixup重新着色并旋转
public boolean insert(Nord x,Nord root) {
if(x==null) {
return false;
}
Nord y = null;
while(root!=null) {
y=root;
if(x.element>root.element) {
root=root.right;
}else if(x.element<root.element) {
root=root.left;
}else return false;
}
x.parent=y;
if(y==null) {
this.root=x;
}else if(x.element>y.element) {
y.right=x;
}else y.left=x;
x.color=RED;
insertFixup(x);
return true;
}
辅助函数-(过滤null值,防止空指针)
//过滤null,避免空指针
private Nord parentOf(Nord x) {
return x==null?null:x.parent;
}
private Nord leftOf(Nord x) {
return x==null?null:x.left;
}
private Nord rightOf(Nord x) {
return x==null?null:x.right;
}
private void setColor(Nord x,boolean color) {
if(x!=null) x.color=color;
}
insertFixup函数
case 1:叔节点为红色
case 2.a:叔节点为黑色,当前节点为右孩子
case 2.b:叔节点为黑色,当前节点为左孩子
/**
* insert之后的重新着色和旋转
*
* y:当前x节点的叔节点
* 当x的父节点为red时 循环:
* 若x.parent是左孩子,
* case 1:当x的叔节点y为red时,重新染色,把x.parent和y都染成black,y.parent染成red,x指针上移,重赋为x.parent.parent;
* case 2:当x的叔节点y为black时,a.若x为右孩子,则令x=x.parent,在对当前的x左旋;再将当前x.parent染红,x.parent.parent染黑,对x.p.p右旋
* b.若x为左孩子,则将当前x.parent染红,x.parent.parent染黑,对x.p.p右旋.
* 若x.parent是右孩子,与左孩子正相反
*
* @param x 插入的节点x
* */
private void insertFixup(Nord x) {
x.color=RED;
while(x!=null && x!=root && x.parent.color==RED) {
Nord y;
if(x.parent==leftOf(parentOf(x.parent))) { //x.parent为左孩子的情况
y=rightOf(parentOf(x.parent));
if(y!=null && y.color==RED) { //case 1
x.parent.color=BLACK;
y.color=BLACK;
setColor(parentOf(x.parent),RED);
x=parentOf(x.parent); //case 1 结束
}else {
if(x.parent.right==x) { //case 2.a
x=x.parent;
leftRotate(x);
}
x.parent.color=RED; //case 2.b
setColor(parentOf(x.parent),BLACK);
rightRotate(parentOf(x.parent)); //case 2.a 结束 //case 2.b 结束
}
}else {
y=leftOf(parentOf(x.parent)); //x.parent为右孩子的情况,与上面相反
if(y!=null && y.color==RED) {
x.parent.color=BLACK;
y.color=BLACK;
setColor(parentOf(x.parent),RED);
x=parentOf(x.parent);
}else {
if(x.parent.left==x) {
x=x.parent;
rightRotate(x);
}
x.parent.color=RED;
setColor(parentOf(x.parent),BLACK);
leftRotate(parentOf(x.parent));
}
}
}
root.color=BLACK; //root节点染黑
}
四、删除
先写几个辅助函数:
transplant(Nord x,Nord y)-用节点y移植到x的位置
//transplant:与二叉搜索树的transplant一致
public void transplant(Nord x,Nord y){
if(x!=null){
if(x.parent==null){
this.root=y;
}else if(x.parent.left==x){
x.parent.left=y;
}else x.parent.right=y;
if(y!=null){
y.parent=x.parent;
}
}
}
public Nord min(Nord root)-返回该root下的最小节点
//最小节点
public Nord min(Nord root){
Nord y = null;
while(root!=null){
y=root;
root=root.left;
}
return y;
}
public Nord get(Nord root,int x)-取得元素为x的节点
//取得节点x
public Nord get(Nord root,int x) {
if (root == null || x == root.element) {
return root;
}
if (root.element > x) {
return get(root.left, x);
} else {
return get(root.right, x);
}
}
删除-remove(Nord x)
//删除
public boolean remove(int x) {
return remove(get(root,x))==null?false:true;
}
/**
* 这里的删除算法与二叉搜索树的remove方法基本思路一致,最后当originColor=BLACK时,调用deleteFixup(z)重新旋转着色。
* 不同点: 1.节点y:a.当Nord x不存在两个子节点时,y即是x;
* b.当x存在两个子节点时,y是x的后继(successor),这里由于x的右子树非空,即min(x.right).
* 2.增加一个记录y节点初始颜色的变量originColor,
* 3.增加Nord z,用于记录y的初始位置状态。
*
* 这样设置y的原因与最后要进行判断的条件有关:originColor=BLACK
* a. 当Nord x不存在两个子节点时,若x.color=red,则x.left/right=black,这时将x.left/right移植到x的位置时,
* i. 黑高不改变; ii.不存在相邻的红色节点; 对照红黑树的5条性质,都满足。此时y为x.
* b.当Nord x存在两个子节点时,这时取y=min(x),即x的后继。若y.color=red,则y的子节点为黑色。
* i.此时若x为red,即y非x的右子树,x的子节点为黑,首先y和x同色,对红黑树性质无影响,再有此时y.child将成为x.child
* 的子节点,而两者都是黑色,无影响。
* ii.若x为black,则需要将y移动后染成black(代码中 y.color=x.color;),此时可保证黑高不变.由于y的子节点为
* 黑,它可以接受任何颜色的父节点。
* c.若y.color=red,则y非根节点,root仍保持黑色。
*
* @param x
* @return
*/
public Nord remove(Nord x){
if(x!=null){
Nord y=x,z;
boolean originColor=y.color;
if(x.left==null){
z=x.right;
transplant(x,x.right);
}else if(x.right==null){
z=x.left;
transplant(x,x.left);
}else{
y=min(x.right);
originColor=y.color;
z=y.right;
if(y==x.right){
if(z!=null) z.parent=y;
}else{
transplant(y,y.right);
y.right=x.right;
y.right.parent=y;
}
transplant(x,y);
y.left=x.left;
y.left.parent=y;
y.color=x.color;
}
if(originColor==BLACK){
if(z!=null) {
removeFixup(z);
}else {
removeFixup(x); //当x无子节点时,以x为起点向上做fixup
}
}
return x;
}
return null;
}
removeFixup函数讨论的情况:
case 1:
case 2:
case 3:
case 4:
public void removeFixup(Nord z)-删除修正函数
/**
* w:z的兄弟节点
* 当z是左孩子时,
* case 1: 若w为red,则把w染黑,再把父节点z.parent染红,在对z.parent作左旋,最后把w赋为z新的兄弟节点;
* case 2: 若w为黑,且w的两个子节点都为黑,则将w染红,z上移,赋为z.parent;
* case 3: 若w为黑,w的右孩子为黑,左孩子为红,则先把w.left染黑,w染红,再对w做右旋,最后把w赋为z新的兄弟节点;
* case 4: 若w为黑,且w的两个子节点都为红,把z.parent的颜色赋给w,z.parent染黑,再对z.parent作左旋,z=root,退出循环
* 若z是右孩子,与左孩子正相反。
* 最后将z颜色染黑
*
* @param z
* @return
*/
public void removeFixup(Nord z) {
while(z!=null && z!=root && colorOf(z)==BLACK) {
Nord w;
if(z==leftOf(parentOf(z))) { //若z是左孩子
w=rightOf(parentOf(z));
if(colorOf(w)==RED) { //case 1
setColor(w,BLACK);
setColor(parentOf(z),RED);
leftRotate(parentOf(z));
w=rightOf(parentOf(z)); //case 1 结束
}
if(colorOf(leftOf(w))==BLACK && colorOf(rightOf(w))==BLACK) { //case 2
setColor(w,RED);
z=parentOf(z); //case 2 结束
}else {
if(colorOf(rightOf(w))==BLACK) { //case 3
setColor(leftOf(w),BLACK);
setColor(w,RED);
rightRotate(w);
w=rightOf(parentOf(z)); //case 3 结束
}
setColor(w,colorOf(parentOf(z))); //case 4
setColor(parentOf(z),BLACK);
setColor(rightOf(w),BLACK);
leftRotate(parentOf(z));
root=z; //case 4 结束
}
}else { //若z是右孩子,与左孩子正相反
w=leftOf(parentOf(z));
if(colorOf(w)==RED) {
setColor(w,BLACK);
setColor(parentOf(z),RED);
rightRotate(parentOf(z));
w=leftOf(parentOf(z));
}
if(colorOf(leftOf(w))==BLACK && colorOf(rightOf(w))==BLACK) {
setColor(w,RED);
z=parentOf(z);
}else {
if(colorOf(leftOf(w))==BLACK) {
setColor(rightOf(w),BLACK);
setColor(w,RED);
leftRotate(w);
w=leftOf(parentOf(z));
}
setColor(w,colorOf(parentOf(z)));
setColor(parentOf(z),BLACK);
setColor(leftOf(w),BLACK);
rightRotate(parentOf(z));
root=z;
}
}
}
setColor(z,BLACK);
}
五、测试程序
public void printRBTree(Nord root,int n)-凹入法打印rb树
//打印红黑树
public void printRBTree(Nord root,int n) {
if(root!=null) {
printRBTree(root.right,n+1);
for(int i=0;i<n;i++) System.out.print(" ");
System.out.print("-- ");
System.out.print(root.element+":"+showColor(root)+"\n");
printRBTree(root.left,n+1);
}
}
private String showColor(Nord x)-颜色标记
private String showColor(Nord x) {
if(x!=null) {
return x.color?"红":"黑";
}else return "空";
}
测试程序:
public static void main(String[] args) {
RBTree r=new RBTree();
r.insert(-5);
r.remove(-5);
System.out.println(r.getRoot());
r.insert(-5);
r.insert(-8);
r.insert(-10);
r.insert(-6);
r.insert(5);
r.insert(10);
r.insert(1);
r.insert(2);
r.insert(0);
r.printRBTree(r.getRoot(),0);
r.remove(-5);
System.out.println("===============");
r.printRBTree(r.getRoot(),0);
r.remove(1);
System.out.println("===============");
r.printRBTree(r.getRoot(),0);
r.remove(10);
System.out.println("===============");
r.printRBTree(r.getRoot(),0);
r.remove(2);
System.out.println("===============");
r.printRBTree(r.getRoot(),0);
r.remove(5);
System.out.println("===============");
r.printRBTree(r.getRoot(),0);
}
结果:
null
-- 10:黑
-- 5:红
-- 2:红
-- 1:黑
-- 0:红
-- -5:黑
-- -6:黑
-- -8:红
-- -10:黑
===============
-- 10:黑
-- 5:红
-- 2:红
-- 1:黑
-- 0:黑
-- -6:黑
-- -8:红
-- -10:黑
===============
-- 10:黑
-- 5:红
-- 2:黑
-- 0:黑
-- -6:黑
-- -8:红
-- -10:黑
===============
-- 5:黑
-- 2:红
-- 0:黑
-- -6:黑
-- -8:红
-- -10:黑
===============
-- 5:黑
-- 0:黑
-- -6:黑
-- -8:红
-- -10:黑
===============
-- 0:黑
-- -6:红
-- -8:黑
-- -10:黑
参考:https://blog.csdn.net/u010853261/article/details/54312932
https://blog.csdn.net/carechere/article/details/51749885