您的位置:首页 > 其它

红黑树学习总结

2015-09-17 16:46 316 查看
摘要: 红黑树的概念,性质,插入删除操作

红黑树学习总结

概念

一种自平衡的二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。时间复杂度为O(log n),高效的插入、删除和查找。



二、性质
1. 只有两种节点,黑色和红色;
2. 根节点是黑色;
3. 每个叶节点(NIL节点,空节点)是黑色;
4. 每个红节点的两个子节点必为黑色;
5. 从任意节点到其每个叶子节点所经过的路径,都包含相同数量的黑色节点。
6. 由于红黑树也是二叉查找树,它们当中每一个节点的比较值都必须大于或等于在它的左子树中的所有节点,并且小于或等于在它的右子树中的所有节点。这确保红黑树运作时能够快速的在树中查找给定的值。
三、与二叉树的区别和性能方面的优点
二叉查找树性质:

1.若任意节点的左子树不为空,则左子树中任意节点的值均小于它的根节点的值;
2.任意节点的右子树不为空,则右子树中任意节点的值均大于它的根节点的值;
3.任意节点的左右子树也为二叉查找树;
4.没有键值相等的节点。
区别:

一)性质不同,红黑树除了具有二叉查找树所有性质外,还具备自己的性质;
二)结构,二叉树结构不稳定,可能形成只有左节点或有节点的线性链形式,失去了树的特性;红黑树结构稳定,通过其性质保证不会出现线性链形式;
三)性能,二叉查找树若退化为只有左/右子树线性链,其查找的时间复杂度为O(n),红黑树由于其结构稳定,其最坏的时间复杂度为O(logN)。
四、代码实现
左右旋概念:由于对红黑树的插入和删除节点,会造成树违反红黑树的5个特性,所以会引出左右旋的概念,以调整树结构,使其符合红黑树所有特性。
1,左旋:



若树T以x为支点进行左旋(上图pivot为支点),所做的操作为,x的右节点Y移动到x节点的位置,x节点移动到x的左节点a的位置,同时x的右节点指向x的右节点的左节点b,Y节点的左节点指向x。
Java代码实现:
Public void leftRoate(T,x){
Y = x.left; // 局部变量y为x的左子节点
x.right = y.left; // y的左子节点赋给x的右子节点
If(y.left != NIL){ // y的左子节点不为空时
y.left.p = x; // 将y的左子节点的父节点指向x
}
y.p = x.p; // y的父节点指向x的父节点
if(x.p = T.NIL){ // 当x不存在父节点时(x为树的根节点)
T.root = y; // 将T的根节点指向y
}else if(x = x.p.left){ // 当x为父节点的左子节点时
x.p.left = y; // x的父节点的左子节点指向y
}else { //当x为父节点的右子节点时
x.p.right = y; // x的父节点的右子节点指向y
}
x.p = y; // 将x的父节点指向y
y.left = x; // 将y的左子节点指向x
}
右旋操作与此类似。
2.插入操作
红黑树插入操作与二叉查找树前面步骤一致,需要先找到插入位置,插入元素后,红黑树的特性肯能会被破坏,这时需要做一些修正操作(通过左右旋、重新着色等)。
Java代码实现:
Public void add(T,z){
x = T.root; // 获取根节点
y=NIL; // 变量用于存储父节点
While(x != NIL){ // 若x不为叶子节点时执行循环体(找插入位置)
y = x; // 将x作为父节点存储
If(x.key > z.key){ // 比较x和z的key值大小
x = x.left; // 若x的key大,则向x的左节点继续寻找
}else{
x=x.right; // 若z的key大,则向x的右节点继续寻找
} // 直到找到合适位置(x=NIL时)
}
z.p = y; // 设置z的父节点
if(y == T.NIL){ // 若父节点为NIL,说明插入位置是根节点
T.root = z; // 设置z为根节点
}else if(y.key > z.key){ // 当y的key大于z的key时
y.left = z; // 将y的left指向z
}else{ // 当y的key小于z的key时
y.right =z; // 将y的right指向z
}
z.left = NIL; // 将z的左右子节点置为NIL
z.right = NIL;
z.color = red; // 将z着为红色
fixup(T,z); // 修正红黑树
}

修正红黑树有以下几种情况:

插入节点是根节点;

插入节点的父节点是黑色;

如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色;

当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子;

当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子。

对应修正办法:

直接将节点涂为黑色;

不违反红黑树性质,无需修正;

将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法;

当前节点的父节点做为新的当前节点,以新当前节点为支点左旋;

父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋。

java代码实现:
fixup(T,z){
y = T.root; //获取根节点
if(y==z){
z.color = black; // 1.插入根节点,直接涂黑
return;
}else if(z.p.color == black){ //2.父节点为黑色,无需修复
return;
}else{
While (z.p.color == red){ //3 父节点为红色,且叔叔节点为红色
If(z.p == z.p.p.left && z.p.p.right.color ==red){ //3.1且父节点为祖父左节点
z.p.color = black; // 父节点涂黑
z.p.p.right.color = black; // 叔叔节点涂黑
z.p.p.color = red; // 祖父节点涂红
z = z.p.p; // 祖父节点作为当前节点
}
if(z.p == z.p.p.right && z.p.p.left.color == red){//3.2父节点为祖父右节点
z.p.color = black; // 父节点涂黑
z.p.p.left.color = black; // 叔叔节点涂黑
z.p.p.color = red; // 祖父节点涂红
z = z.p.p; // 祖父节点作为当前节点
}
If(z.p == z.p.p.right && z.p.p.right.color ==black){//4叔叔节点为黑色,父节点为祖父右节点
z = z.p; // 父节点为当前节点
leftRoate(T,z); // 左旋
}
If(z.p == z.p.p.left && z.p.p.left.color == black){//5 叔叔节点为黑色,父节点为祖父左节点
z.p.color = black; //父节点涂黑
z.p.p.color = red; //祖父节点涂红
z=z.p.p; // 祖父节点为当前节点
rightRoate(T,z); // 右旋
}
}
}
T.root.color = black; // 涂黑根节点
}

3.删除操作

有以下几种情况

节点没有儿子,即为叶节点。直接将父节点的相应儿子设为null;

只有一个儿子,把父节点相应儿子的指针指向儿子的独生子;

有两个儿子。这是最麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较容易,我们可以选择左儿子中的最大元素或者右儿子中的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除。习惯上大家选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别,只是人们习惯从左向右。这里咱们也选择左儿子的最大元素,将它放到待删结点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右子树就可以了,直到找到一个没有右子树的结点。那就是最大的了。

Java实现:‍
Public void delete(T,z){
If(z.left==NIL&&z.right==NIL){ //删除节点为叶子节点
If(z ==z.p.left){
z.p.left = NIL; //将父节点的相应儿子指针置为NIL
}else{
z.p.right = NIL;
}
}else if(z.left != NIL&&z.right !=NIL){ // 删除节点的儿女双全
n =z.left; //寻找左子树最大值来顶替当前节点,n记录当前节点的左儿子
m = NIL; //m记录左子树最大值
While(n!=NIL){
m=n;
n=m.right; //记录右节点为n

}
m.p.right=NIL; //m的父节点的右节点置为NIL
m.left = z.left; //m的左节点指向当前节点额左子树
m.right = z.right; //m的右节点指向当前节点的右子树
If(z ==z.p.left){ //当前节点为父节点的左节点
z.p.left = m; //父节点的左节点指向m
}else{
z.p.right = m; //否则,父节点的右节点指向m
}
}else if(z.left != NIL){ //删除节点有一个左节点
If(z==z.p.left){ // 当前节点为父节点的左节点
z.p.left = z.left; // 父节点的左节点指向当前节点左儿子
}else{ // 当前节点为父节点的右节点
z.p.right = z.left; //父节点的右节点指向当前节点左儿子
}
}else{ //删除节点有一个右节点
If(z==z.p.left){ // 当前节点为父节点的左节点
z.p.left = z.right; // 父节点的左节点指向当前节点右儿子
}else{ // 当前节点为父节点的右节点
z.p.right = z.right; //父节点的右节点指向当前节点右儿子
}
}
delFixup(T,z); //删除修复
}

删除修复,有下面几种情况:

1.删除的是红色节点;
2.删除节点不是树的唯一节点;
3.被删除节点的唯一非空子节点是红色,被删节点的父节点也是红色;
4.被删节点是根节点,它的唯一非空子节点是红色。
为了便于理解,我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。
现在修复可以变为下面几种情况:

当前节点是红+黑色;

当前节点是黑+黑,且是根节点;

当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点均为黑);

当前节点是黑+黑且兄弟是黑色且兄弟节点的两个子节点全为黑色;

当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色;

当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意

相应解决方法:

直接把当前节点染成黑色;

无需操作;

父节点染红,兄弟节点染黑,以当前节点为支点左旋,从新进入算法(此步目的是将兄弟节点转换为兄弟节点是黑色的情况);

变化前:



变化后:



4.兄弟节点涂红,当前节点指向父节点,重新进入算法;

变化前:



变化后:



5.兄弟节点涂红,兄弟节点左子节点涂黑,以兄弟节点为支点右旋,重新进入算法;

变化前:



变化后:



6.把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束。

变化前:



变化后:



TreeMap实现的插入修复和删除修复
/**
* 红黑树 插入修复
* @param x
*/
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED; //涂红当前节点

while (x != null && x != root && x.parent.color == RED) { //1当前节点不为空,不为根节点,父节点红色
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //1.1if父节点为祖父的左节点
Entry<K,V> y = rightOf(parentOf(parentOf(x))); //获取叔叔节点
if (colorOf(y) == RED) { //1.1.1if若叔叔节点为红色
setColor(parentOf(x), BLACK); //父节点涂黑
setColor(y, BLACK); //叔叔节点涂黑
setColor(parentOf(parentOf(x)), RED); //祖父节点涂红
x = parentOf(parentOf(x)); //当前节点指向祖父节点
} else { //1.1.2else若叔叔节点为黑色
if (x == rightOf(parentOf(x))) { //1.1.2.1if且当前节点为父节点的右节点
x = parentOf(x); // 将当前节点指向父节点
rotateLeft(x); // 左旋
}
setColor(parentOf(x), BLACK); //父节点涂黑
setColor(parentOf(parentOf(x)), RED); //祖父节点涂红
rotateRight(parentOf(parentOf(x))); //以祖父节点为支点右旋
}
} else { //1.2else父节点为祖父的右节点
Entry<K,V> y = leftOf(parentOf(parentOf(x))); //获取叔叔节点
if (colorOf(y) == RED) { //1.2.1if叔叔节点为红色
setColor(parentOf(x), BLACK); //父节点涂黑
setColor(y, BLACK); //叔叔节点涂黑
setColor(parentOf(parentOf(x)), RED); //祖父节点涂红
x = parentOf(parentOf(x)); //当前节点指向祖父节点
} else { //1.2.2else叔叔节点为黑色
if (x == leftOf(parentOf(x))) { //1.2.2.1if当前节点为父节点的左节点
x = parentOf(x); // 当前节点指向父节点
rotateRight(x); // 右旋
}
setColor(parentOf(x), BLACK); //父节点涂黑
setColor(parentOf(parentOf(x)), RED); //祖父节点涂红
rotateLeft(parentOf(parentOf(x))); //乙组分节点为支点左旋
}
}
}
root.color = BLACK; //根节点涂黑
}

/**
* 红黑树删除修复
* @param x
*/
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) { //1当前节点不是根节点且颜色为黑色
if (x == leftOf(parentOf(x))) { //1.1if当前节点为父节点的左节点
Entry<K,V> sib = rightOf(parentOf(x)); //获取兄弟节点

if (colorOf(sib) == RED) { //1.1.1if兄弟节点为红色
setColor(sib, BLACK); //兄弟节点涂黑
setColor(parentOf(x), RED); //父节点涂红
rotateLeft(parentOf(x)); //以父节点为支点左旋
sib = rightOf(parentOf(x)); //重新获取兄弟节点
}

if (colorOf(leftOf(sib)) == BLACK && //1.1.2if兄弟节点的左节点为黑色,右节点为黑色
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED); //兄弟节点涂红
x = parentOf(x); //当前节点指向父节点
} else { //1.1.3else兄弟节点的子节点不全为黑色
if (colorOf(rightOf(sib)) == BLACK) {//1.1.3.1if兄弟节点的右子节点为黑色
setColor(leftOf(sib), BLACK); //兄弟节点左子节点涂黑
setColor(sib, RED); //兄弟节点涂红
rotateRight(sib); //以兄弟节点为支点右旋
sib = rightOf(parentOf(x)); //重新获取兄弟节点
}
setColor(sib, colorOf(parentOf(x)));//将兄弟节点涂成父节点的颜色
setColor(parentOf(x), BLACK); //涂黑父节点
setColor(rightOf(sib), BLACK); //涂黑兄弟节点的右子节点
rotateLeft(parentOf(x)); //以父节点为支点左旋
x = root; //当前节点指向根节点(用于跳出循环)
}
} else { // symmetric 1.2else当前节点为父节点的右子节点
Entry<K,V> sib = leftOf(parentOf(x)); //获取兄弟节点

if (colorOf(sib) == RED) { //1.2.1if兄弟节点为红色
setColor(sib, BLACK); //涂黑兄弟节点
setColor(parentOf(x), RED); //涂红父节点
rotateRight(parentOf(x)); //以父节点为支点右旋
sib = leftOf(parentOf(x)); //重新获取兄弟节点
}

if (colorOf(rightOf(sib)) == BLACK && //1.2.2if兄弟节点的双子均为黑色
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED); //涂红兄弟节点
x = parentOf(x); //当前节点指向父节点
} else { //1.2.3else兄弟节点的双子不全为黑色
if (colorOf(leftOf(sib)) == BLACK) {//1.2.3.1if兄弟节点的左子节点为黑色
setColor(rightOf(sib), BLACK); //涂黑兄弟节点的右子节点
setColor(sib, RED); //涂红兄弟节点
rotateLeft(sib); //以兄弟节点为支点左旋
sib = leftOf(parentOf(x)); //重新获取兄弟节点
}
setColor(sib, colorOf(parentOf(x)));//将兄弟节点涂为父节点的颜色
setColor(parentOf(x), BLACK); //涂黑父节点
setColor(leftOf(sib), BLACK); //涂黑兄弟节点的左子节点
rotateRight(parentOf(x)); //以父节点为支点右旋
x = root; //当前节点指向根节点(用于跳出循环)
}
}
}
setColor(x, BLACK); //当当前节点为红色时,直接涂黑

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  红黑树