红黑树-算法导论

这个周看算法导论,看到红黑树,看的我云里雾里绕啊。虽然最后看懂了,据我估计,要是过一个星期不看保证忘干净,因此决定写篇博客记录下红黑树。

二叉树

红黑树是二叉树的一种,所以学习红黑树必须先搞懂二叉树。

二叉树是查找树的一种
查找树是一种数据结构,支持多种动态集合操作,包含查询,最大值,最小值,前赴后继,插入,删除操作。
查找树可以用来作为字典或者优先队列使用。

二叉树

二叉树

如图,一颗二叉树是按照结点(二叉树结构)组成的。每个结点可以用链表结构标示。每个结点都应该有left right p ,他们分别指向左儿子,右儿子和父结点。每个结点还应该有个关键字key。如果某个儿子结点不存在,则相应位置就是nil。跟结点是树中唯一的父结点值是nil的节点。

@interface TTree : NSObject
@property (nonatomic,strong) TTree *left;
@property (nonatomic,strong) TTree *right;
@property (nonatomic,strong) TTree *parent;
@property (nonatomic,assign) int key;
@end

二叉树性质

设x为二叉树中的一个结点,如果y是x的左子树中的一个结点,则key[y]<=key[x]。如果y是x的右子树中的一个结点,则key[x]<=key[y]

二叉树的从无到有

我们有很多结点,如何将他组合成符合二叉树性质的二叉树呢?

插入数据

虚线连接的数字就是13
代码如下

-(void)ttreeInsert:(int)key{
    TTree * y = nil;
    TTree * x = self.ttroot.root;
    while (x!=nil) {
        y = x;
        if (key<x.key) {
            x = x.left;
        }else{
            x = x.right;
        }
    }
    TTree * z = [TTree new];
    z.key=key;
    z.parent = y;
    if (y==nil) {
        self.ttroot.root = z;
    }else if (z.key<y.key){
        y.left = z;
    }else{
        y.right = z;
    }
}

中序遍历算法 和 前序遍历算法

我们一般通过下面两种方式遍历二叉树中的key

树根的关键字(key)在左子树和右子树的关键字之间输出就是中序遍历算法
树根的关键字(key)在左子树和右子树的关键字之前输出就是前序遍历算法

///中序排列算法
-(void)middleOrderTreeWalk:(TTree*) node{
    if (node !=nil) {
        [self middleOrderTreeWalk:node.left];
        NSLog(@"%d",node.key);
        [self middleOrderTreeWalk:node.right];
    }
}
///前序排列算法
-(void)frontrderTreeWalk:(TTree*) node{
    if (node !=nil) {
        NSLog(@"%d",node.key);
        [self frontrderTreeWalk:node.left];
        [self frontrderTreeWalk:node.right];
    }
}

二叉树的查找search

查找二叉树
查找普通key
-(TTree *)searchKey:(int)key rootTree:(TTree*)tree{
    if (tree==nil||tree.key == key) {
        return tree;
    }
    if (key<tree.key) {
       return  [self searchKey:key rootTree:tree.left];
    }else{
        return [self searchKey:key rootTree:tree.right];
    }
    return nil;
}
查找最大值和最小值

从图中我们能看出只要沿着各个结点的left指针查找下去,知道遇见nil 时候为止,就是最小值,最大值正好相反。

-(TTree *)ttreeMinimum:(TTree *)tree{
    while (tree.left!=nil) {
        tree = tree.left;
    }
    return tree;
}
-(TTree *)ttreeMaximum:(TTree *)tree;
{
    while (tree.right!=nil) {
        tree = tree.right;
    }
    return tree;
}
前趋和后继

前趋:假设所有的关键字key都不相同,则某一个结点x的后继是二叉树中小于key[x]值的最大的那个结点。(如何把二叉树的key值按照从小到大排列,前趋就是key[x]的前面的key所对应的tree)
后继:假设所有的关键字key都不相同,则某一个结点x的后继是二叉树中大于key[x]值的最小的那个结点。(如何把二叉树的key值按照从小到大排列,后继就是紧接着key[x]的那key对应的tree)

前趋后继

如图,3的前趋是2, 后继是6。

前趋后继的规律是什么呢?
前趋:如果结点x的左子树为非空,则x的前趋就是做子树中的最右结点。要是x的左子树为nil ,并且有前趋y(key是最最小值对应的x是没有前趋的),那么y是x的最低祖先,并且y的右儿子也是x的祖先。(x必须出现在y的右儿子的分支中,并且y是最低祖先)

后继:如果结点x的右子树为非空,则x的后继就是右子树中的最左结点。要是x的右子树为nil ,并且有后继y(key是最大值对应的x是没有后继的),那么y是x的最低祖先,并且y的左儿子也是x的祖先。(x必须出现在y的左儿子的分支中,并且y是最低祖先)

///后继
-(TTree *)treeSuccessOr:(TTree *)tree{
    if (tree.right!=nil) {
        return [self ttreeMinimum:tree.right];
    }
    TTree * y = tree.parent;
    while (y!=nil && tree == y.right) {
        tree = y;
        y = y.parent;
    }
    return y;
}
///前趋
-(TTree*)treePredecessOr:(TTree*)tree{
    if (tree.left!=nil) {
        return [self ttreeMaximum:tree.left];
    }
    TTree * y = tree.parent;
    while (y!=nil && tree == y.left) {
        tree = y;
        y = y.parent;
    }
    return y;
}

二叉树的删除

这里一定要搞明白,因为这里是红黑树删除的基础部分。

摘自算法导论

二叉树结点的删除分三种情况。

  • 1.如果删除结点z没有子女,则修改z->parent,使z->parent 的子女变为nil 对应上图的a
  • 2.如果结点z只有一个子女,则让子女和父节点直接连接,即子女的parent=z->parent, z->parent 的子女 是z的子女。
  • 3.如果结点z有两个子女,第一步,删除z的后继(没有左子女),再用y的内容来替代z的内容。(这里也可以用z的前趋代替,没有右子女)

这里我们需要注意第三种情况,删除z之后,因为子女比较多,因此我们需要从众多的子女中找一个子女代替z,这个子女可以是前趋,也可以是后继。

-(TTree * )delete:(int)key{
     TTree *z=  [self searchKey:key rootTree:self.ttroot.root];
    //需要删除的y
    TTree * y=nil;
    TTree * x= nil;

    if (z.left==nil && z.right==nil) {    ///情况1 一个结点没有
        y= z;
        if (y.parent.left==y) {
            y.parent.left = nil;
        }else{
            y.parent.right = nil;
        }
        ///可能是根节点 ///ios 这里可以不关心,因为parent是nil ,给nil赋值还是nil
        if (y.parent==nil) {
            self.ttroot.root = nil;
        }
        
    }else if (z.left==nil ||z.right==nil){ ///情况2  只有一个结点
        y = z;
        if (y.left==nil) {
            x = y.right;
        }else{
            x = y.left;
        }
//        
        if (y.parent==nil) {
            y.parent = x;
        }else if (y.parent.left==y) {
            y.parent.left = x;
        }else{
            y.parent.right = x;
        }
    }else{ ///情况3  有两个结点
        ///找到 z的后继结点,将key 赋值给z
         y =[self treeSuccessOr:z];
        z.key = y.key;
        ///再删除 y 就可以了。因为y是后继,没有左结点。y 一定是父类的左结点
        x = y.right;
        ///这里y 可能是根节点。
     if (y.parent.left==y) {
            y.parent.left = x;
        }else{
            y.parent.right = x;
        }
        
    }
    
    return y;
    
# if 0

    if (z.left==nil||z.right==nil) {
        y = z;
    }else{
        y =[self treeSuccessOr:z];
    }
    
    ///我们从三种情况中知道, y只有一个孩子
    if (y.left!=nil) {
        x = y.left;
    }else{
        x = y.right;
    }
    
    if (x!=nil) {
      x.parent = y.parent;
    }
    if (y.parent==nil) {
        self.ttroot.root = x;
    }else if (y.parent.left==y){
        y.parent.left = x;
    }else{
        y.parent.right = x;
    }
    
    if (y!=z) {
        z.key = y.key;
    }
    return y;
#endif
}

这里肯定是算法导论的书写方式简明,自己的写法只是方便理解。

这里针对第三种情况的删除,我们不是删除z,而是删除的是z的后继y,把y的值赋值给z,这样不破坏树中的结构。变成了只是删除了一个叶子,这个叶子的特点是左儿子是nil

总结
第三种情况,删除z,我们将其转换成了删除z的后继,z的后继的叶子的特点是左儿子是nil
综合第一种情况和第二中情况,删除的节点,要么是没有儿子,要么是只有一个儿子。

红黑树

红黑树是一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是RED或者Black。通过对任何一条从跟到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

树中每个结点包含五个域:color,key ,left,right 和p。如果某结点没有一个子结点或者父结点,则该结点相应的指针为nil。

这里,我们把这些nil 视为指向二叉树的外结点(叶子)的指针,而把带关键字的结点视为树的内结点。

红黑树的性质

  • 1.每个结点或是红的,或是黑的。
  • 2.根结点是黑的。
  • 3.每个页结点(nil)是黑的。
  • 4.如果一个结点是红的,则它的两个儿子都是黑的。
  • 5.对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
红黑树结构

从图中我们能看出每个叶子节点都是nil。nil对象都是一样的。干嘛不用一个nil 代替呢。因此如下结构。


红黑树结构

红黑树之旋转

二叉树旋转

旋转应该是二叉树的性质,只是普通二叉树没必要旋转。红黑树需要旋转来平衡树结构。

左旋

从图中我们对二叉树排序看 a<x<b<y<c。这个时候x是y的parent。现在我们想让y变成x的parent。怎么办呢?
我们知道y> x 。那么x肯定是x的左儿子。因为y<c 因此,c肯定是y的右儿子。这样y的结点就明确了。还剩下 a 和b ,我们不可能把数据a和b不要了把,因此只剩下x有两个位置。只能放在x的左右儿子中了。

结果如下。


image.png

右旋的过程只是相反而已。

左旋的作用是将右儿子变成父结点。
右旋的作用是将左儿子变成父结点。

这样变化有什么用处呢?

左旋导致左边的树高度增加了,右边的树高度减少了,但是二叉树的性质没有任何变化。
右旋导致右边的树高度增加了,左边的树高度减少了,但是二叉树的性质没有任何变化。
这里我们需要了解,树的高度越高,搜索的时间就越长,左旋和右旋可以改变树的高度,这样可以减少数的搜索时间。

-(void)leftRoTate:(TTree *)x{
    TTree * y = x.right;
    y.parent = x.parent;
    if (x.parent==nil) {
        self.ttroot.root = y;
    } else if (x.parent.left==x) {
        x.parent.left = y;
    }else{
        x.parent.right = y;
    }
    
    x.right=y.left;
    y.left.parent = x;
    
    x.parent = y;
    y.left = x;
}
-(void)rightRotate:(TTree *)x{
    TTree * y = x.left;
    y.parent = x.parent;
    if (x.parent == nil) {
        self.ttroot.root = y;
    }else if (x.parent.left==x){
        x.parent.left = y;
    }else{
        x.parent.right = y;
    }
    
    x.left = y.right;
    y.right.parent = x;
    
    x.parent = y;
    y.right = x;
    
}
image.png

生成红黑树

我们知道,红黑树就是二叉树,因此向二叉树中插入数据没有啥区别。只是不过,当我们插入结点的某些时候就破坏了红黑树性质。因此,我们需要对二叉树进行调整让其适合红黑树的特性。

下面我们应该分析什么情况下插入结点能破坏红黑树。

假如我们插入的结点是黑色的,肯定是破坏了二叉树的性质5。(除非是根节点不破坏。)
但是我们插入的结点是红色的,不一定破坏二叉树的五条性质,为了方便,我们还是将二叉树插入的结点染成红色的比较好。(情况变少)

当我们插入一个红色结点可能破坏那些性质呢?第一条和第三条肯定是不会被破坏的,恒成立的。第五条因为插入的结点是红色的肯定也是成立的。这里可能不成立的是第二条(跟结点是黑的,要是插入的结点就是根结点)和第四条(如果一个结点是红的,它的两个儿子是黑的,因为要是插入的结点的父结点是红的话,那么父父结点就不符合要求了)。

因此,红黑树的结构被破坏就两种情况,不是性质2就是性质4.
性质2好解决,将结点染成黑色的就行了。不做分析。
我们重点分析性质4破坏,我们如何修正。

我们知道性质4被破坏的条件是父节点是红色的。
如图


破坏的二叉树基本结构

x 代表插入的结点RED。p代表 x的父节点RED。 p的父结点是跟,跟是Black。
因为根结点一定不是nil,所以,肯定有两个结点(性质5),其中一个是p(red)。还有一个儿子s待q确认颜色。可能是红色的结点,也可能是黑色的结点。
s 我们命名为叔父结点。

这里我们分情况讨论 s 的颜色。
第一种情况 : s的结点是红色。

image.png

这个情况,我们从根到左边的黑色结点数是2 ,右边的黑色结点数是2。
那么我们如何将其变成从跟到叶子的所有路线的结点数是2还不破坏红黑树呢?
只有下面一种变化才能符合红黑树,这样从根向下就符合红黑树了。
将祖父结点(跟结点)变成红色,p 和s 结点都变成 黑节点。

叔父是红色变更后的结点样子

从上面的结果图我们能看出来,根父的颜色不定,可能是红色的,也可能是黑色的,那么我们就应该让 当做被插入的新节点,遍历根父 的兄弟的结点的颜色情况情况。这其实就是递归了。
要是每个根父的兄弟结点都是红色的话,那么遍历到根就结束了。将根染成黑色即可。要是根父的兄弟不是红色,那么就应该进入到第二种情况了。

第二种情况:s结点是黑色的。


叔父结点是黑色的

这种情况不管如何变化,让总结点数不变的情况下,单纯染色根本没有办法让所有结点符合红黑树特性。让从根到叶子的路径含有相同的黑色结点。 那么怎么办呢?
我们知道,二叉树可以旋转,旋转可以改变树的结构。

这里我们用根做旋转(左旋或者右旋),结果如下


旋转之后的结果图1
旋转之后的结果图2

我们看看旋转结果,结果1,根变成p儿子的一边黑色结点树没有变化。而x的一边黑色结点数量减少了1。那我们如何让x一边增加1呢?只有如下一种情况,将p染成黑色,根染成红色。

结果2是无法达到我们的要求的,因此我们需要抛弃掉这种情况,让s是黑的时候只能变成结果1。

结果1旋转最终结果

结果1所有的情况下都满足红黑树,不需要进行递归了。

这里我们需要看看什么情况下,x结点是p结点的儿子而不是根结点的儿子。这里我们需要看左旋右旋逻辑图了。

左旋转

左旋转
当x 是p的左儿子那么就变成了s是黑色 结果2的变化图。
当x 是p的右儿子,那么就变成了s是黑色 结果1 的变化图。

右旋转

右旋转
当x是p的右节点,那么就变成了s是黑色的结果2的变化图。
当x是p的左节点,那么就变成了s是黑色的结果1的变化图。

因此这里我们总结下结果1 的条件

x 是p的左儿子,p是跟的左儿子。(左旋)
x 是p的右儿子,p是跟的右儿子。(右旋)

结果2 就是剩下的。那么要是结果2的情况,我们应该如何处理呢。

x是p的左儿子,p是跟的右儿子。(左旋)
x是p的右儿子,p是跟的左儿子。(右旋)

那么结果2 在右旋的结构图就很清晰了。如下。p的右儿子是插入的结点x。

结果2的结构图

这种情况如何解决呢?
因为x是p的右儿子,所以旋转只能是左旋转。我们以p为根节点旋转。
结果如下


p的左旋转

这样就变成了结果1 准备右旋转的的情况了。

同理,情况2的左旋图就变成了结果1准备左旋图的情况了。

总结

1.当s结点是红色的时候,那么将根染成红色,p和s都染成黑色就行了。并且将x指向跟进行循环,直到s是黑的时候或者到根。
2.当s结点是黑色的时候,那么x和p都是左儿子,那么就以根为旋转点进行右旋 将p染成黑色,根染成红色。x和p都是右儿子,那么就以根为旋转点进行左旋,将p染成黑色,根染成红色。
3.当s结点是黑色的时候,x和p的分别是左儿子和右儿子,那么就以p进行左旋或者右旋,那么就变成2的情况,接着执行2的情况就行了。

#define RED 1
#define BLACK 0

-(void)treeInsertFixup:(TTree *)z{
    ///这里是算法导论的算法
#if 1
    while  (z.parent.color==RED) {
        TTree * y = nil;//叔父
        if (z.parent == z.parent.parent.left) {
            y = z.parent.parent.right;
            if (y.color==RED) {
                z.parent.color = BLACK;
                y.color = BLACK;
                z.parent.parent.color = RED;
                z = z.parent.parent;
                
            }else{
                if (z==z.parent.right){///黑色 右节点 结果1
                    z=z.parent;
                    [self leftRoTate:z];
                }
                z.parent.color = BLACK;
                z.parent.parent.color = RED;
                z=z.parent.parent;
                [self rightRotate:z];
                break;
            }
        }else{
            y= z.parent.parent.left;
            if (y.color == RED) {
                z.parent.color = BLACK;
                y.color = BLACK;
                z.parent.parent.color = RED;
                z = z.parent.parent;
            }else{
                if (z ==z.parent.left) {
                    z=z.parent;
                    [self rightRotate:z];
                }
                z.parent.color = BLACK;
                z.parent.parent.color = RED;
                z=z.parent.parent;
                [self leftRoTate:z];
                break;
            }
        }
    }
    self.ttroot.root.color = 0;

#endif
    
/// 分情况比较好理解
#if 0
    while (z.parent.color==1) {
        TTree * y = nil;//叔父
        if (z.parent==z.parent.parent.left) {
            y = z.parent.parent.right;
        }else{
            y = z.parent.parent.left;
        }
        
        ///第一种情况
        if (y.color == 1) {
            z.parent.parent.color = 1;
            z.parent.color = 0;
            y.color =0;
            ///变更结点继续递归循环
            z = z.parent.parent;
        }else if (y.color==0){/// 当儿子和父亲不是一个方向的时候一个左一个右,用p为结点进行旋转
      
            if (z==z.parent.left && z.parent == z.parent.parent.right) {
                z = z.parent;
                [self rightRotate:z];
            }
    
            if (z.parent.right && z.parent==z.parent.parent.left) {
                z=z.parent;
                [self leftRoTate:z];
            }
            
            z.parent.color = 0;
            z.parent.parent.color = 1;
            if (z==z.parent.left && z.parent==z.parent.parent.left) {
                z = z.parent.parent;
                [self rightRotate:z];
                break;
            }
            if (z==z.parent.right && z.parent==z.parent.parent.right) {
                z = z.parent.parent;
                [self leftRoTate:z];
             
                break;
            }
            
        }
    }
    

    self.ttroot.root.color = 0;
    
#endif
}

这里上面的方法是算法导论给出的,下面的是自己实现的。

红黑树的删除

删除操作和二叉树的操作一样,同样的只不过是删除的时候,可能引起对红黑树性质的破坏。

这里把删除二叉树的总结搬过来

总结
第三种情况,删除z,我们将其转换成了删除z的后继,z的后继的叶子的特点是左儿子是nil。
综合第一种情况和第二中情况,删除的节点,要么是没有儿子,要么是只有一个儿子。

这里我们罗列下这三种情况各个删除结点的结构。

二叉树第一种情况:没有儿子的情况。


没有儿子

二叉树第二种情况:只有一个儿子。左儿子或者右儿子。有四种情况


只有一个儿子的情况

二叉树第三种情况:最多有一个右儿子


至多有一个儿子

我们看出来,第三种情况不是第一种情况,就是第二种情况。因此这里我们只需要研究第一种情况和第二种情况就行了。

第一种情况x是红色分析------没有儿子结点

当x是红色的时候


第一种情况x是红色删除前

第一种情况x是红色删除后

将x删除,我们发现红黑树的黑色结点的数量不会发生任何变化。红黑树结构正常

第二种情况x是红色分析------只有一个儿子
第二种情况x是红色删除前
第二种情况x是红色删除后

我们发现不管什么条件下,删除红色结点红黑树不会发生任何变化。

红黑树情况一分析-没有儿子-删除结点是黑色的

第一种情况x是黑色分析------没有儿子结点
第一种情况x是黑色删除前
第一种情况x是黑色删除前

这里我们需要讨论下 p 和 s的颜色,我们知道p 和s 分别只有红色和黑色,并且p 和s 不能同时为红色。那么p 和s的组合情况只有三种了。

组合1: p 红色,s黑色
组合2:p黑色,s红色
组合3:p 黑色,s黑色


组合分类

我们发现不管哪一种情况都删除x的那一支都缺少一个黑色结点。

组合1

组合1 完整图

组合一通过染色是不可能达到树的平衡的。(有人好说了,把p染成黑色,s染成红色不就行了?我刚开始也犯了这个错误,要是s的两个儿子是红色怎么办呢?红色是不参与节点数计算的。)
因此我们这里直能通过旋转来改变树的结构来看看能不能达到我们的要求。我们以p旋转看看结果如下。

组合1 完整图结果

这貌似也不行,因为SL 可能是红色的,违反性质4。因此我们可以看出来,p是红色的情况下,旋转一次想让红黑树平衡依赖S的两个结点的颜色。
要是p左旋,那么s的左孩子是黑色的就可以。要是p右旋,那么s的右孩子是黑色的就可以了。

因此这里讨论下s的两个孩子的颜色情况。

如果SL 和SR 都是红色怎么处理呢?


image.png

单纯的旋转根本不可能让红黑树成立。只能先改变颜色在旋转了。
颜色改变如下


image.png

要是SL 和SR都是黑色的情况下
结构图如下


image.png

这种结构图 SL SR 都是nil。这种情况下旋转一下 p就行了。


image.png

SR SL只有一个颜色是红色的。如果p的删除分支是左儿子。那么SL 是红色,只能以s先右旋转。这样就变成了 SL SR都是黑色,但是s是红色的情况。是SL ,SR变化成平衡树的中间过程


image.png

最后这种情况,和SL SR 都是黑色一样的处理。


image.png

组合2


组合2完整图

这里我们单纯靠染色是不可能实现树平衡的。因为删除x的结点的一支的结点都是黑色的。没有可以改变的红色结点存在。怎么办呢?我们只能通过旋转将删除的节点的一支上面增加红色结点才行。这里有个s是红色的,我们需要让其到删除结点分支上。
旋转结果


组合2旋转结果

这样组合2删除结点的分支上有红色结点了。到这里我们发现这个地方的树可能违反红黑树了。
违反1:根是红色的话,s是红色违反性质4.
违反2:p结点黑节点数量还是对不上。违反性质5
不过没关系,要是我们能通过染色让树平衡的话,不用再调整树的结构的话,那么组合2也就算是解决了。

貌似通过染色也解决不了这个树平衡的问题,不过违反的红黑树的地方可以减少,我们将s染成黑色,p染成红色。

组合2染色减少破坏平衡树的条数

这里我们发现只有 p删除结点的一支,黑色结点数量少一。正好是组合一的情况。按照组合一的情况做一次旋转就可以解决问题

组合三

组合三完整图

这种情况是最复杂的了。我们知道S要是红色,这就是组合2的情况,要是p是红色,那么就是组合1了。那我们想办法怎么让s或者p变成红色的呢?我们先从叶子节点找红色结点,看能不能让s或者p变成红色的。肯定是可以的。不过有一定条件。这里需要依赖SL,SR的颜色了。

SL SR 都是红色,
SL SR只有一个红色
SL SR 都是黑色


组合三s的子树具体颜色

当SL,SR都是红色,这种变化只通过改变颜色就可以让s变成红色,变成组合2的情况。


SL ,SR都是红色

当SL,SR 只有一个红色的时候,通过改变颜色是不能达到要求的。因此我们就需要做旋转来达到要求。

SL,SR 只有一个红色

当SL SR 都是黑色。没有红色,p结点一下是不能解决这个问题的。那怎么办呢?这样只能依赖p的父类了。不过这里需要注意的是p本身不平衡,我们这里依赖p的父类的时候,p需要是平衡的。如何平衡p呢?
我们只需要将s变成红色就可以了。p就平衡了。

当SL SR 都是黑色

不过这里注意,p的这一整支的所有分支都是少黑色结点1 的。
这里我们注意到p只有一个儿子。这里我们需要把p当成删除结点x的一个儿子。这样就转换成了x带一个儿子的情况了。这种情况如何平衡树。下面讨论一个孩子的时候包含,暂时不在这里讲解。

红黑树情况一分析-只有一个儿子-删除结点是黑色的

先拿一种情况分析 x是p的左儿子,x有一个左儿子


x是p的左儿子,x有一个左儿子

分析L 如果L是黑色的话,肯定是nil。因此可以将这种只有一个儿子的情况转换成没有儿子的情况。也可以这么说,要是x有儿子,儿子的颜色一定不是黑色。而是红色。如图

L是p的儿子是红色

删除X后的结点变化

删除x后的结点变化

我们发现违反了红黑树的性质

违反一:要是p是红色,违反性质4
违反二:p结点违反性质5。

因此这里我们需要做矫正。
从上图看我们只要把L变成黑的就解决了所有问题。

image.png

我们这里看完只有删除的结点x 只有一个儿子所有情况了。

x结点要是有儿子,那么儿子结点必须是红色的。要是结点是黑色的,那么肯定是nil
x结点右儿子,让去符合平衡树很简单,就是将其儿子变成黑色的就行了。

这里所有的情况分析完毕。

这里我们知道要是删除结点x有分支,分支的L肯定是红色结点,要不就是nil。
这里我们要分析下L是黑结点不是nil的情况。这种情况是发生在没有儿子的组合三情况发生的。

基本组合形态


image.png

这里我们还是分类分析。
要是p是红色,那么s肯定是黑色的。
这种情况以p中旋转点做一次旋转,就可以了。


p是红色

要是p是黑色,那么s就可以是红色或者黑色。
先看p是黑色,s是红色情况


做一次旋转结构

这样的结构肯定不符合红黑树。我们把s变成黑色,p变成红色。


颜色替换

这时候我们发现p结点一下不正确,接着调整p结点一下。只要有红色结点就好弄。做一次旋转。


再做一次旋转

这样终于发现树平衡了。

我们再看p s都是黑色的情况,这个就又变成了分析的没有儿子结点的组合三情况。只要把s染成红色,在让p当成x删除的分支可是分析就可以了。

总结下:
删除x结点,要是x是红色直接删除就可以了。不需要调整。
删除x结点,要是x有孩子,并且孩子是红色的,直接将孩子染成黑色就可以了。
删除x结点,要是x是黑色的。我们需要从x的父亲或者兄弟或者兄弟的子女中查找红色结点,找到了就可以通过一系列旋转变化,改变结点颜色来达到树的平衡。
要是x的父亲或者兄弟或者兄弟的子女中没有红色结点,我们就应该将x的兄弟染成红色,把p当成x的子女进行下一次循环查找。直到找到根为止。

算法导论和自己写的算法。还是算法导论的比较清晰。自己写的复杂了 

-(void)treeDeleteFixup:(TTree *)z
{
#if 1
    TTree * x = z;
    TTree * w = nil;

    while (x!=self.ttroot.root && x.color == BLACK) {
        if (x ==x.parent.left ) {
            w = x.parent.right;
            if (w.color == RED) {
                w.color=BLACK;
                w.parent.color = RED;
                [self leftRoTate:x.parent];
                w = x.parent.right;
            }
            if (w.left.color == BLACK && w.right.color == BLACK) {
                w.color = RED;
                x = x.parent;
            }else if (w.right.color == BLACK){
                w.left.color = BLACK;
                w.color = RED;
                [self rightRotate:w];
                w = x.parent.right;
            }
            w.color = x.parent.color;
            x.parent.color = BLACK;
            w.right.color = BLACK;
            [self leftRoTate:x.parent];
            return;
        }else{
            w = x.parent.left;

            if (w.color == RED) {
                w.color=BLACK;
                w.parent.color = RED;
                [self rightRotate:x.parent];
                w = x.parent.left;
            }
            if (w.left.color == BLACK && w.right.color == BLACK) {
                w.color = RED;
                x = x.parent;
            }else if (w.left.color == BLACK){
                w.right.color = BLACK;
                w.color = RED;
                [self leftRoTate:w];
                w = x.parent.left;
            }
            w.color = x.parent.color;
            x.parent.color = BLACK;
            w.left.color = BLACK;
            [self rightRotate:x.parent];

        }
    }
    x.color = 0;
#endif
    
    
#if 0
    if (z.left!=nil||z.right!=nil) {
        z.left.color = 0;
        z.right.color = 0;
        return;
    }
    while (z.parent!=nil) {
    ///这样就说明z是没有左右结点的了
    TTree * p = z.parent;
    /// p 肯定有左右儿子
    TTree * s = nil;
    if (z==p.left ||p.left==nil) {
        s = p.right;
    }else{
        s = p.left;
    }
    ///组合一
    if (p.color == 1) {

        if (z==p.left||p.left==nil) {
            if (s.left.color==RED && s.right.color==RED) {
                s.color = RED;
                s.left.color = BLACK;
                s.right.color = BLACK;
                [self leftRoTate:p];
                [self leftRoTate:p];
            }else if(s.left.color == RED){
                [self rightRotate:s];
                s.color = RED;
                s.parent.color = BLACK;
                [self leftRoTate:p];
            }else{
                [self leftRoTate:p];
            }
            
        }else{
            if (s.left.color==RED && s.right.color==RED) {
                s.color = RED;
                s.left.color = BLACK;
                s.right.color = BLACK;
                [self rightRotate:p];
                [self rightRotate:p];
            }else if(s.right.color == RED){
                [self leftRoTate:s];
                s.color = RED;
                s.parent.color = BLACK;
                [self rightRotate:p];
            }else{
                [self rightRotate:p];
            }
        }
        return;
    }

SColorRed:
    ///p是黑色的,那么s是红色的
    if (s.color==1) {
        p.color = 1;
        s.color = 0;
        if (s==p.right) {
            [self leftRoTate:p];
        }else{
            [self rightRotate:p];
        }
        ///s 是红的,SR SL 必定不是nil
        if (p.left!=nil) {
            [self rightRotate:p];
        }else{
            [self leftRoTate:p];
        }
        return;
    }
    
    ///组合三 p 和 s都是 黑的。 那我么只能看SR SL 的颜色了。
    if (s.left.color == 1&& s.right.color == 1) {
        s.left.color = 0;
        s.right.color = 0;
        s.color = 1;
        goto SColorRed;
    }
    
    /// SR SL 只有一个红色
    if (s.left.color == 1) {
        [self rightRotate:s];
        s = s.parent ;
        goto SColorRed;
    }
    if (s.right.color == 1) {
        [self leftRoTate:s];
        s = s.parent;
        goto SColorRed;
    }
    
    s.color = 1;
    z = p;
}
    
#endif
   
}

算法导论没有考虑p的情况,一样可以平衡红黑树。我考虑了p的颜色,因此情况比较多。

写完自己的再看算法导论的,我们就能看懂了。算法导论没有考虑删除x的p 而是直接考虑 的是x的兄弟结点 s 和SR ,SL。

这里只简单贴贴图就行了

我们假设s 是红色。那么SR SL 只能是黑色的了,P 肯定是黑色的。
这种情况如何变化呢?我们只是将 p 旋转,之后,将p 的颜色设置红色,s的颜色设置为红色就行了。这样就转换成 x的兄弟结点是黑色的了。


image.png

我们发现x以前的兄弟是s ,现在是sl, 并且是黑色的。我们把s是红色的变成了s是黑色的。对立面的情况。

接着考虑s是黑色情况。这里需要看SL SR 的颜色。
要是SL SR 都是黑色的。那么我没就将s渲染成红色,p连接的左右两边都少一个黑结点。如果p是黑色的。那么我们就把p当成x进行下一次循环,要是p是红色,改成黑色就结束了。

image.png

要是SL 和SR 只有一个是红色的呢?这里还有个顺序问题。要是x是p的左结点,那么SL 是红色或者SR 是红色情况是不一样的。
SL 是红色的情况如下。我们把sl以s进行旋转,将s染成红色,sl染成黑色。这就变成了SR是红色的情况了。结果如下。


image.png

要是SR 是红色的,x是p的左结点。我们如何处理呢?我们用p做一次旋转,之后把s染成p的颜色,把p染成黑色,SR 染成黑色就可以了。

image.png

以上是算法导论的。

其实红黑树就是用旋转和染色让树平衡,方式有很多种,只不过算法导论的方式是最简便的。我们也可以自己定义自己 的红黑树删除逻辑。不过肯定逻辑性不如算法导论的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容

  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,817评论 13 42
  • 目录 0.树0.1 一般树的定义0.2 二叉树的定义 1.查找树ADT 2.查找树的实现2.1 二叉查找树2.2 ...
    王侦阅读 7,102评论 0 3
  • 0.目录 1.算法导论的红黑树本质上是2-3-4树 2.红黑树的结构和性质 3.红黑树的插入 4.红黑树的删除 5...
    王侦阅读 2,457评论 1 2
  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,637评论 4 32
  • 1、红黑树介绍 红黑树又称R-B Tree,全称是Red-Black Tree,它是一种特殊的二叉查找树,红黑树的...
    文哥的学习日记阅读 9,849评论 1 35