《算法》中的红黑树实现

有别于上一篇文章介绍的红黑树,在《算法<第四版>》一书中用另一套规则实现了红黑树,主要手段是递归,实现思路来自于2-3树,这本书中有详细的解读,在这里我谈谈自己对它的理解。
首先,在之前文章中介绍的红黑树,我们把节点看成红,黑两色,而这里红节点指的是它指向父亲的链接是红色的,有什么不同?当我们介绍左旋,右旋你就会看到。

来看看这套定义红黑树的规则:

  1. 红链接均为左链接
  2. 没有任何一个节点同时与两个红链接相连
  3. 该树是完美的黑平衡,即任何空节点到根节点的路径上的黑链接数目相同

与之前红黑树的不同:之前的红节点在左在右都行。
先来看看关于Node的定义:

    private class Node {
        private int size;
        private Key key;
        private Value value;
        private Node left;
        private Node right;
        private boolean color;
        
        public Node(Key key, Value value, boolean color, int size) {
            this.color = color;
            this.key = key;
            this.value = value;
            this.size = size;
        }
    }

只有左右两个指针没了之前的parent指针,加了个size用于记录节点个数,规则是x.size = size(x.left) + size(x.right) + 1;
再来介绍三板斧:rotateLeft, rotateRight, flipColors 这三板斧在变换过程中不会破坏黑平衡

rotateLeft
看到区别没有,原来的黑节点变成了红,红链接被转过来了;还有一个要注意的地方就是它的返回,接下来的操作我们会经常看到这样 x = rotateLeft(x); 这个x实际上在变换后变成了他原来的右子节点,是因为没有指向父节点的指针所以用这种方法来替代。 最重要的是这里的旋转不会破坏树的黑平衡
rotateRight

flipColors
flipColor函数不是能够随便调用的,一定要满足以下条件: x及其左右子树都不为null && 左右子树颜色相同,x颜色与他们相反。原因是保证黑平衡不被破坏。

插入操作就用到了上面这三板斧,我们将在插入操作中见识到这套规则的魅力

    public void put(Key key, Value value) {
        if (key == null) throw new IllegalArgumentException("first argument to put() is null");
        if (value == null) {
            delete(key);
        }
        root = put(root, key, value);
        root.color = BLACK;  //根节点为黑
    }

    private Node put(Node x, Key key, Value value) {
        if (x == null) return new Node(key, value, RED, 1);
        
        int cmp = key.compareTo(x.key);
        if (cmp < 0)        x.left = put(x.left, key, value);
        else if (cmp > 0)   x.right = put(x.right, key, value);
        else                x.value = value;
        
        if (isRed(x.right) && !isRed(x.left))     x = rotateLeft(x);
        if (isRed(x.left) && isRed(x.left.left))  x = rotateRight(x);
        if (isRed(x.left) && isRed(x.right))      flipColors(x);
        x.size = size(x.left) + size(x.right) + 1;
        
        return x;
    }

就是二叉查找树的插入操作+重平衡递归式的二产查找树实现
跟之前比简介很多,为什么?原因就在与两套红黑树规则的不同之处,即该套规则红链接均为左链接,不允许有右红节点的出现。
红黑树新插入的节点设置为红色,这样不会破环黑平衡,但可能会破坏规则1和2,可能出现的情况有三种:1.x左黑右红,左旋; 2.x左红,左左红,右旋 ;3. x左右都红,flipColor。这样能够在递归过程中保证一层层往上直到根节点都符合规则,最后要保证根节点为黑。

deleteMin

    public void deleteMin() {
        if (isEmpty()) throw new NoSuchElementException("the tree is empty");
//为嘛要有这步操作?我的理解:只有一种情况需要将根节点变红否则会出错,那就是头三个左链接皆为黑,
这时候不能让其调用moveRedLeft,前面说过flipColor保持平衡不变是有前提条件的,那么就带来一个问题:
上面的这种情况人为将根节点变红,而且会将这个红链接往下传,想想,这样左子树不就高度减一了吗?
不会,最后从下往上递归修复过程中会将root重新变为红,最后在将其设置为黑。
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMin(root);
        if (!isEmpty()) root.color = BLACK;
    }
    private Node deleteMin(Node x) {
//为什么返回null?因为我们定义的红黑树,最小节点一定没有子节点
        if (x.left == null)
            return null;
//为什么判断条件是左儿左孙都为黑调用,因为如下面介绍的那样,moveRedLeft利用三板斧
就管自己的这一亩三分地,然后从上往下递归传递红链接,若存在左儿或左孙为红,那就跳到那去传递它的红链接
        if (!isRed(x.left) && !isRed(x.left.left))
            x = moveRedLeft(x);
        x.left = deleteMin(x.left);
//在我们从上往下改变红黑树的过程中,会出现右链接为红还有两左红链接连一起的情况,
这就需要在递归从下往上过程中进行修复,因为三板斧所以不用担心会破坏平衡
        return balance(x);
    }
//你会发现凡是调用该方法的节点一定是红
    private Node moveRedLeft(Node x) {
        // assert (h != null);
        // assert isRed(h) && !isRed(h.left) && !isRed(h.left.left);
        flipColors(x);
        if(isRed(x.right.left)) {
            x.right = rotateRight(x.right);
            x = rotateLeft(x);
            flipColors(x);
        }
        return x;
    }

    private Node balance(Node x) {
        if (isRed(x.right) &&!isRed(x.left)) x = rotateLeft(x);
        if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);
        if (isRed(x.left) && isRed(x.right)) flipColors(x);
        
        x.size = size(x.left) + size(x.right) + 1;
        return x;
    }
//空节点为黑
    private boolean isRed(Node x) {
        if (x == null) return false;
        return x.color == RED;
    }

如果以之前的红黑树来思考:如果x为黑,它可能有一个右红子节点,他的父亲(p)的情况,他父亲的兄弟(sib)的情况,还有sib两子节点情况,以及sib左子节点的两个子节点情况。我们需要把每种情况都考虑到,很复杂。不过一切的根源就在于x为黑,那x要是红直接删除就可以了,有没有一种办法保证x一定为红?这就是上面代码做的事,让被删除节点为红。
现在来想想怎么把它变红?直接变,这就和之前一样了,要考虑复杂的情况,从下往上的重新平衡树。
我们之前说过flipColor有它的使用前提来保证不会破坏平衡,旋转操作也不会破坏平衡,那moveRedLeft是干嘛用的?人如其名,就是要把红链接往下传的,但是又不能破坏平衡,于是用三板斧来操作,如上述代码,moveRedLeft用三板斧将当前的红链接转到了他的儿子,这样从上往下到最后最小节点一定为红无论他原来是什么(这里有个前提是空连接视为黑),从上而下改变树,这从上而下的变换结果就会让最小节点是一个红的且没有任何子节点。最后在从下往上递归中修复树,即balance函数。

deleteMax

    public void deleteMax() {
        if (isEmpty()) throw new NoSuchElementException("tree is null");
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMax(root);
        if (!isEmpty()) root.color = BLACK;
    }
    private Node deleteMax(Node x) {
//如果x左子节点为红,直接右旋,达到rang
        if (isRed(x.left))
            rotateRight(x);
        if (x.right == null)
            return null;
        if (!isRed(x.right) && !isRed(x.right.left))
            x = moveRedRight(x);
        x.right = deleteMax(x.right);
        return balance(x);
    }
//x一定为红链接,将该红链接传到原右儿子处(注意是原右儿子,因为变换后这个原来x的右儿子
可能变成了变换后x的右孙子。。。自己画图就明白了)
    private Node moveRedRight(Node x) {
        flipColors(x);
        if (isRed(x.left.left)) {
            x = rotateRight(x);
            flipColors(x);
        }
        return x;
    }

主要思想与deleteMin相同,不同在处理手段上。
这里的主要操作就是从根节点开始通过右旋或moveRedRight在从上往下递归过程中不断将红链接传下去(我们定义的红黑树它的最大节点最多有一个左红节点),最后最大节点一定是个无子节点的红节点。再通过从下往上的递归过程中balance()红黑树。

delete

    public void delete(Key key) {
        if (key == null) throw new IllegalArgumentException("argument to delete is null");
        if (!contains(key)) return;
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = delete(root, key);
        if (!isEmpty()) root.color = BLACK;
    }
    private Node delete(Node x, Key key) {
        if (key.compareTo(x.key) < 0) {
            if (!isRed(x.left) && !isRed(x.right))
                x = moveRedLeft(x);
            x.left = delete(x.left, key);
        }
        else {
            if (isRed(x.left))
                x = rotateRight(x);
            if (key.compareTo(x.key)==0 && x.right == null) 
                return null;
            if (!isRed(x.right) && !isRed(x.right.left)) 
                x = moveRedRight(x);
            if (key.compareTo(x.key) == 0) {
                Node min = min(x.right);
                x.key = min.key;
                x.value = min.value;
                x.right = deleteMin(x.right);
            }
            else x.right = delete(x.right, key);
        }
        return balance(x);
    }

    public Node min(Node x) {
        //assert x!=null
        if (x.left == null) return x;
        else return min(x.left);
    }

这里的处理方式就是deleteMin对左链接和deleteMax对右链接的处理方式的综合

删除操作最终会转化为删除右子树中最小节点操作(例外是右子树为null,那么直接返回null,因为经过从上到下的变换,右子树为null的节点一定是无子节点且颜色为红);我们从根节点开始,到这右子树的最小节点这一路径上不断变换向下传递红节点,期间并没有影响到树的原有黑平衡(除了一开始人为设置根节点为红,前面说过)。最后再从下自上修复。

总结

这种红黑树的特点:rotateLeft,rotateRight,flipColor这三个操作不会破坏树的黑平衡,于是以此为基础我们就可以来借助递归从上至下的传递红链接,即moveRedLeft,moveRedRight。最后在递归从下至上过程中修复树,即balance。它有别于之前那种红黑树,之前的是先找到目标节点然后综合考虑各种情况以达到黑平衡,是自下而上的处理。

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

推荐阅读更多精彩内容

  • Java关于数据结构的实现:树 关于作者 郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢...
    郭孝星阅读 2,884评论 2 4
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,016评论 0 12
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,698评论 13 42
  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,584评论 4 32
  • 我想我还是不喜欢雨的,尤其是秋雨,总感觉身上有种似湿非湿的黏腻触感,让整个人都沉闷了起来。 我有点畏寒,天稍稍冷点...
    茶果果麻麻阅读 237评论 0 0