红黑树

之前介绍过二叉查找数,平均情况下查找和插入时间复杂需为1.39logn;最坏情况下为n。算法的改进就引出了红黑树,在插入,删除操作中能够保持树本身高度平衡(红黑树并不是标准平衡二叉树,它以下面的性质 5 作为一种平衡方法,使自己的性能得到了提升)的数据结构。红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。下面来看看这种数据结构的定义:

  1. 每个节点要么是红色,要么是黑色;
  2. 根节点永远是黑色的;
  3. 所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
  4. 每个红色节点的两个子节点一定都是黑色;
  5. 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;

来看看红黑树保持平衡的三板斧:rotateRight, rotateLeft, setColor

Paste_Image.png

接下来以TreeMap来介绍红黑树的实现:

    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }

    private static <K,V> void setColor(Entry<K,V> p, boolean c) {
        if (p != null)
            p.color = c;
    }

对照着上面的图来理解代码。



插入操作

新增的节点默认是红色,这样就不会增加树的高度,但是问题来了,你的父节点是红色怎么办,违背了性质4。上面提到的三板斧出场,以TreeMap为例:

    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

逻辑非常清楚,按照父节点(相对于x)是左节点还是有节点分两大块,这两大块操作对称,我们就以父节点是左节点为例:

  1. 父节点P的兄弟节点为红,那就让你两兄弟全变成黑,爷爷节点变红,现在我们来看看以爷爷节点为根的这个子树在经过变换后满足黑平衡和性质4,接下来就是确认爷爷节点与在往上的节点有没有冲突,也就是 x = parentOf(parentOf(x)),再while循环往上继续检查。
  1. P为黑,接下来分两种:
    1),N是左子节点,将其父节点改为黑,爷爷节点改为红,右旋爷爷节点。看变换后左右的黑平衡都维持原样。
    2),N是左子节点,以P为中心左旋,之后处理同上。

为什么这样?这一切操作都是为了保持红黑树的性质不被破坏。

删除

删除操作是最复杂的。
TreeMap本质上是一个二叉查找树,他的删除操作是这样的:删除节点x,若其左右子树都不为null,则找到右子树最小节点代替x,将问题转化为删除删除右子树最小节点;若节点x有一子树为null,则直接删除再余下顶上。但是它是一个红黑树,若x是个黑节点,则红黑平衡被打破,所以我们拿出三板斧来对树进行修复。(以下分析都是以x在左这一边来分析的,x在右操作方法与左对称)

    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));
代码块一
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
代码块二
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);  
                    x = parentOf(x);
                } else {
代码块三
                    if (colorOf(rightOf(sib)) == BLACK) {
                        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 {   主要对x为左孩子分析,右孩子与其处理方式相同
                Entry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        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);   性质2:红黑树的根节点一定是黑色
    }

先总结一下:再TreeMap中fixAfterDeletion(x)的调用说明被删除的节点为黑,红黑平衡被打破,两种处理办法:1,将x父节点拉下来,使得x这一便高度加一并维持另一边高度不变;2,1处理失效,那么就让以x父节点为根的这颗子树高度整体减一,往上父节点变为x再进行 1 操作。
fixAfterDeletion(x)中x代表什么?两种情况:1,代表被删除节点的左/右子孩子。2,代表将要被删除的节点,其左右子树皆为空。代码逻辑再TreeMap的remove操作里。

情况一:x的兄弟节点为红,那么x的父节点一定为黑,x颜色不用管,如何让x这一边高度加一同时不影响另一边?


经过代码块一之后的模样,此时x这一边高度仍不平衡,其它边平衡没有被打破,兄弟节点变为A,问题接着往下走,沿着代码块来到情况二。
情况二:x的兄弟节点为黑,其左右子树皆为黑。至于父节点P颜色不用管,这里画为红色是因为承接情况一变化而来。

让B变为红色,此时B为根的子树整体高度减去一,则父节点P的左右子树高度相等,P为根的子树现在整体高度比原来小一,P变为x,往上循环比较,目的让P这颗子树高度加一。
情况三:x的兄弟节点的左孩子节点为红。不用考虑P的颜色情况

执行代码块三:A改为黑,B改为红,右旋B。接着代码块往下来到情况四。
情况四:x的兄弟节点为黑,其右孩子节点为红,P,A的颜色任意。

代码块四的逻辑:将B的颜色改为与父节点颜色相同,将父节点P颜色改为黑,将兄弟节点B的右孩子节点K改为黑,左旋P。现在看X到P高度增加一,其它边高度不变,红黑重新平衡。

总结

在我看来红黑树=二叉查找树+红黑平衡
再前面的文章中贴过二叉查找树的代码,是递归版本的,接下来分析TreeMap文章里在介绍其非递归版本。
我想如果我自己实现红黑树,我会针对每种情况写一段代码,而我们看上面的源码,他很巧妙的将其合并统一,区区几十行代码。

前面的图片是来自于看过的多篇分析红黑树的文章,后面删除操作里 的图片是利用EDraw自己画的。

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

推荐阅读更多精彩内容

  • 红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途...
    卡巴拉的树阅读 15,209评论 11 113
  • 红黑树是平衡二叉查找树的一种。为了深入理解红黑树,我们需要从二叉查找树开始讲起。 BST 二叉查找树(Binary...
    kanehe阅读 1,376评论 0 8
  • 1、红黑树介绍 红黑树又称R-B Tree,全称是Red-Black Tree,它是一种特殊的二叉查找树,红黑树的...
    文哥的学习日记阅读 9,875评论 1 35
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,912评论 13 42