红黑树
- 红黑树是一颗二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是
RED
或BLACK
。一颗红黑树是满足下面红黑性质的二叉搜索树: - 性质1:红黑树中的每个结点是红色或是黑色的。
- 性质2:根节点是黑色的。
- 性质3:每个叶结点(NIL,即空结点)是黑色的。
- 性质4:如果一个结点是红色,则它的两个子结点都是黑色的(个人理解,红色结点若有子结点必是黑色的,黑色结点的子节点可以是黑色也可以是红色)。
- 性质5:对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
- 一颗有n个内部结点(除空结点之外)的红黑树的高度至多为
2lg(n+1)
。
下图一颗红黑树
在性质3中所说的”叶结点”或”NIL结点”,他不包含数据而只充当树在此结束的指示,因此经常会省略。
红黑树的旋转
- 当我们插入结点到红黑树或者从红黑树删除结点的时,由于对树做来修改,所以红黑树的性质遭到来破坏。为来保持红黑树的性质,我们可以对树的结点重新着色,或者对红黑树进行相关的旋转操作,即修改树中某些结点的颜色及指针的指向,以继续保持红黑树的性质。树的旋转分为左旋 和 右旋。
左旋
假设旋转结点为x
,此时他的右孩子结点为y
。左旋以x
到y
的链为”支轴”进行。左旋过后y
称为该子树新的根结点,x
成为y
的左孩子,y
的左孩子结点成为x
的右孩子结点。
左旋伪代码如下,
leftRotation(RBTNode x){
y=x.right;//保存旋转结点x的右孩子结点信息
//设置x和y.left之间的父子关系
x.right=y.left;
if(y.left){
y.left.parent=x;
}
//设置y和x.parent之间的父子关系
y.parent=x.parent;
if(x.parent==NIL)//如果x为根结点的情况
this.root=y;//左旋之后y将成为新的根结点
else if(x.parent.left==x)
x.parent.left=y;
else
x.parent.right=y;
//设置x和y之间的父子关系
y.left=x;
x.parent=y;
}
右旋
右旋操作的代码是对称的,其伪代码如下,
rightRotation(BRTNode x){
y=x.left;
x.left=y.right;
if(y.right)
y.right.parent=x;
y.parent=x.parent;
if(x.parent==NIL)
this.root=y;
else if(x.parent.left==x)
x.parent.left=y;
else
x.parent.right=y;
y.right=x;
x.parent=y;
}
左旋和右旋都在O(1)
时间内运行完成。在旋转操作中只有指针的改变,其他所有属性都保持不变。
红黑树的插入
在向一颗含n
个结点的红黑树中插入一个新结点可以在O(lgn)
时间内完成当插入结点后,将该结点着为红色(如果着为黑色,那么就不用调整了)。
当插入新结点时,性质1和性质3继续成立,因为新插入的红色结点的两个子结点(NIL)都是黑色的。性质5,即从一个指定结点开始的每条简单路径上黑色结点的个数都是相等的,也会成立,新插入结点代替了NIL
,并且新插入结点本身是有NIL
的红结点。插入结点只能破坏性质2和性质4。这两个性质可能被破坏是因为新插入结点被着为红色。如果新插入结点是根结点则破坏性质2;如果新插入结点的父结点是红结点,则破坏了性质4。
修复红黑树分为6中情况,其中3种和另外3种情况是对称的。这取决于插入结点的父结点是祖父结点的左孩子还是右孩子。以插入结点的父结点为祖父结点的左孩子说明。在插入修复情况2、3中,通过新插入结点是其的父结点的左孩子还是右孩子来区分。
插入修复情况1:插入结点的父节点为红色且叔结点也为红色。
对策:将当前结点的父结点和叔叔结点着为黑色,祖父结点着为红色,并把当前结点指向祖父结点,从新的当前结点重新开始。
变化前,
变化后,
插入修复情况2:插入结点的父节点为红色且叔结点为黑色,插入结点是父节点的左孩子。
对策:当前结点的父结点作为新的当前结点,以新的当前结点为支点进行左旋。
变化前,
变化后,
插入修复情况3:插入结点的父节点为红色且叔节点为红色,插入结点是父节点的右孩子。
对策:将父结点着为黑色,将祖父结点着为红色,以祖父结点为支点右旋。
最后,将根结点着为黑色,整棵树便恢复了平衡。
变化前(当前结点为2结点),
变化后,
代码实现,
Color
定义
package cs.DataStructure;
public enum Color {
RED,BLACK;
}
红黑树结点定义,
package cs.DataStructure;
/**
* 定义红黑树的节点
*/
public class RBTNode {
//设数据域访问的修饰符为default,以便在同一包下直接访问
Color color; //存储本节点的颜色
int value;//本节点的值
RBTNode leftChild;//左孩子节点
RBTNode rightChild;//右孩子节点
RBTNode parent;//父节点
public RBTNode(){}
public RBTNode(int value){
this.value=value;
this.leftChild=null;
this.rightChild=null;
this.parent=null;
this.color=Color.RED;//新建节点默认为红色节点
}
}
红黑树API
,
package cs.DataStructure;
/**
* RBTree API
*/
public class RBTree {
RBTNode root; //存储根节点,default修饰符
public void insertRBTNode(int value){
RBTNode p=null;//p记录上一次访问的节点
RBTNode z=new RBTNode(value);//建立待插入的节点
RBTNode x=root;
while(x!=null){
p=x;
if(x.value>value) {//根节点的值较大,应到左子树上寻找插入位置
x=x.leftChild;
}else { //x.value<=value,到右子树上找插入位置
x=x.rightChild;
}
}
z.parent=p;
if(p==null){ //如果没有进入循环,则代表该红黑树中还没有节点
this.root=z;//则设置根节点
}
else if(p.value>value)
p.leftChild=z;
else //p.value<=value,value的值较大
p.rightChild=z;
insertFixUp(z); //调整红黑树
}
private void insertFixUp( RBTNode node ){
while(node.parent!=null && node.parent.color==Color.RED){ //插入节点一定是红色节点,检查它的父节点是否为红色
if(node.parent==node.parent.parent.leftChild){ //插入节点的父节点是祖父节点的左孩子
RBTNode uncle=node.parent.parent.rightChild;//取得叔节点
if( uncle!=null && uncle.color==Color.RED){ //叔节点为红色
uncle.color=Color.BLACK; //叔节点设置为黑色
node.parent.color=Color.BLACK;//父节点设置为黑色
node.parent.parent.color=Color.RED;
node=node.parent.parent;//改变node指向
continue;
}
if(node==node.parent.rightChild){ //叔节点为黑色的情况且node为右子节点
node=node.parent;
leftRotation(node);
}
node.parent.color=Color.BLACK;//将红色父节点设置为黑色
node.parent.parent.color=Color.RED;//将黑色祖父节点设置为红色
rightRotation(node.parent.parent);//将祖父节点右旋
}else{//插入节点的父节点是祖父节点的右孩子
RBTNode uncle=node.parent.parent.leftChild;//取得叔节点
if( uncle!=null && uncle.color==Color.RED){ //叔节点为红色
uncle.color=Color.BLACK;
node.parent.color=Color.BLACK;
node.parent.parent.color=Color.RED;
node=node.parent.parent;
continue;
}
if(node==node.parent.leftChild) {
node=node.parent;
rightRotation(node);
}
node.parent.color=Color.BLACK;//将红色的父节点设置为黑色
node.parent.parent.color=Color.RED;
leftRotation(node.parent.parent);
}
}
this.root.color=Color.BLACK;
}
//左旋
private void leftRotation(RBTNode node){
//保存node的右子节点,保存为nodeRightChild
RBTNode nodeRightChild=node.rightChild;
//设置node和nodeRightChild.leftChild之间的父子关系
node.rightChild=nodeRightChild.leftChild;
if(nodeRightChild.leftChild!=null)
nodeRightChild.leftChild.parent=node;
//设置nodeRightChild和node.parent的父子关系
nodeRightChild.parent=node.parent;
if(node.parent==null) //node为根节点的情况
this.root=nodeRightChild;
else if(node==node.parent.leftChild)
node.parent.leftChild=nodeRightChild;
else //node==node.parent.right
node.parent.rightChild=nodeRightChild;
//设置node和nodeRightChild之间的父子关系,node为nodeRightChild的左子节点
nodeRightChild.leftChild=node;
node.parent=nodeRightChild;
}
//右旋
private void rightRotation(RBTNode node){
//保存node的左子节点
RBTNode nodeLeftChild=node.leftChild;
//设置node和nodeLeftChild.rightChild之间的父子关系
node.leftChild=nodeLeftChild.rightChild;
if( nodeLeftChild.rightChild!=null )
nodeLeftChild.rightChild.parent=node;
//设置node.parent和nodeLeftChild之间的父子关系
nodeLeftChild.parent=node.parent;
if( node.parent==null ) //node为根节点的情况
this.root=nodeLeftChild;
else if(node==node.parent.leftChild)
node.parent.leftChild=nodeLeftChild;
else //node==node.parent.rightChild
node.parent.rightChild=nodeLeftChild;
//设置nodeLeftChild和node之间的关系
nodeLeftChild.rightChild=node;
node.parent=nodeLeftChild;
}
//设置桥方法
public void preOrder(){
preOrder(root);
}
public void inOrder(){
inOrder(root);
}
public void postOrder(){
postOrder(root);
}
//前序遍历
private void preOrder(RBTNode node){
if (node!=null){
System.out.println(node.value+" "+node.color);
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
//后序遍历
private void inOrder(RBTNode node){
if (node!=null){
inOrder(node.leftChild);
System.out.println(node.value+" "+node.color);
inOrder(node.rightChild);
}
}
//后序遍历
private void postOrder(RBTNode node){
if (node!=null){
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println(node.value+" "+node.color);
}
}
public static void main(String[] args){
RBTree tree=new RBTree(); //
int[] nums={41,38,31,12,19,8};//待插入序列
for(int e:nums){ //依次插入各数值
tree.insertRBTNode(e);
}
tree.preOrder();//前序遍历
}
}
如有错误,欢迎留言指正!