赤黒木についての簡単な話(赤黒木)

â红æ»âçå¾çæç」¢ç»æ

 

序文

我々はすべて知っているように、古典的な赤黒木は、40年後、理由はその安定的かつ効率的な特性を発明された1972年以来、赤黒木は、多くのシステムコンポーネントと基本クラスライブラリで使用されてきた、非常に、非常に重要なデータ構造であり、あいまいで、私たちはしばしば尋ねる多くの学生を中心に、サービスを提供するためにそう簡単な記事を書くためにここに、赤黒木を達成する方法であり、我々は次の赤黒木についての話

 

小さなシリーズは赤黒木、フル赤黒木は非常に複雑である主な理由は、理解することは非常に簡単ではないの話を多くの記事を見てきました、それは明らかに、この記事では、私が試してみたいので、記事は、非常に困難であるしたいです私たちは、おそらく実際には、赤黒木の原則のが理解されていることを確認することができるように平易口語では、(理解しやすいだけでなく、達成することは容易2-3ツリーバージョン、)で「アルゴリズム」でロバート・セッジウィックの改良版と組み合わせて、ほとんどの学生のために、赤黒木のアイデアは、あなたがほとんどここ読んだ後、赤黒木のリアライズを所有する必要はありません通常、誰もあなたが手書き赤黒木を手放すしませんので、十分な基本的に取り組んでいます実現のあなたが言うにもっと何かを感じ、興味のある学生は、「はじめにアルゴリズムの」赤黒木の実装に行くと見ることができれば、それは完全な4階B-木である(2-3-4木)バージョン

 

だから私たちは魂の3赤黒木の主題についての質問しました:

  1. なぜ赤黒木?
  2. アプリケーションのシナリオと赤黒木の定義は?
  3. それは、効率的かつ安定的な赤黒木を達成する方法ですか?

 

なぜ赤黒木があります

バイナリツリー、平衡二分木(私たちは、あなたがすべてのこれらの事前知識を持っていると仮定ので、我々は唯一の、おそらく説明):赤黒木、前任者の最初のを理解するために

事前知識:

二分木:大型リニアテーブルデータを扱う従来のアレイおよび他の線状鎖構造の非効率的なテーブル、時間がラインレベルの複雑さはO(n)であるので、この非効率的なデータ構造は、千を処理することはほとんど不可能ですその思考のバイナリ二分法に基づいてデータの量よりも百万レベル以上は、最良の場合には、O(logN個)の数、どのような概念上の恐怖のレベルに達することができるバイナリツリーの時間の複雑さを見つけるために、生まれましたの?それはそれはその後、唯一のターゲットを見つけるために30〜15回にアクセスする必要があるバイナリ10億レベル内のデータの量は、当然のことながら、私たちの前提が最良のケースで、最悪のケースでありますか?あなたは、図を参照することができます。

バイナリツリーの最高の場合:

バイナリツリーの最悪の場合:

我们从上图可以看到,二叉树的性能的好坏,依赖数据的插入顺序,最坏情况下二叉树会退化为链表,所有操作的时间复杂的度回到的线性级别 O(n),那么怎么解决这个问题呢?

首先平衡二叉树出现了,平衡二叉树的思想是在操作的时候对树进行平衡调整,来防止二叉树退化为链表,从而保证二叉树的最优查找性能,完美的平衡二叉树对高度的定义是相差不会大于1,这就相当于每次都插入/删除操作,都会对树进行平衡操作,这是代价非常高的操作,你可以理解为,类似数组为了保证有序性,数组中间插入数据,所有元素都要向后移动的代价,虽然名字叫 平衡二叉树,其实它的性能非常不平衡,因为它是最大化 插入/删除 操作的时间来换取 查找 操作的时间最小化

看到这里,就有好奇的同学问,那么有没有既可以保证树的完美平衡,又可以保证所有操作性能的数据结构呢?可以很负责任的告诉你,有的!它就是我们今天的主角,红黑树,我们先看看红黑树能为我们带来什么?

  • 红黑树可以保证 最好 最坏 情况的所有操作(插入/删除/查找等)时间复杂度都是对数级别 O(logN)
  • 和二叉树不同,无论插入顺序如何,红黑树都是接近完美平衡的
  • 无数实验的应用证明,红黑树的操作成本(包括旋转和变色)比二叉树降低40%左右

常见树形结构的操作复杂度对比:

Cº¢が「æ¶é'å¤æåºあるâäºåæ|âçå¾çæç」¢æç»æ

红黑树的应用场景和定义

定义这类枯燥的问题先放后面讨论,简单罗列下我们用的哪些工具是通过红黑树实现的

  • Java 的 HashMap (8 以后)的链表树化是通过 红黑树实现
  • Java 的 TreeMap 是通过红黑树实现
  • Nginx 是用红黑树管理 timer 等
  • Linux 进程调度用红黑树管理进程控制块
  • 等等……

红黑树的定义

红黑树本身是二叉树,其背后的思想是使用二叉树的结构再加载额外的颜色信息,来表示2-3树,所以红黑树是包含了二叉树的高效查找和2-3树的高效插入平衡优点的算法

在我们讨论的版本中对红黑树的定义如下:

  • 红链接必须为左链接
  • 不能出现两条相连的红链接
  • 该树是完美黑色平衡的

只看这些定义你可能会觉得描述非常的学院派,非常不好理解,我们先看看标准的红黑树,后面再用画图的方式来逐渐讲解

红黑树定义规则的代码

    private Node put(Node h, Key key, Value val) {
        // 二分插入
        if(h == null) return new Node(key, val, RED, 1);
        int cmp = key.compareTo(h.key);
        if(cmp < 0) h.left = put(h.left, key, val);
        else if(cmp > 0) h.right = put(h.right, key, val);
        else h.val = val;

        if(isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);         // 违反规则 不允许出现右红连接
        if(isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);     // 违反规则 不允许出现连续的左红连接
        if(isRed(h.left) && isRed(h.right)) flipColors(h);              // 当左右子节点为红色, 则变色
        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }

 

红黑树的高效和稳定是怎么实现?

在插入数据的过程中红黑树会出现很多违反上面定义的情况,如果出现违反红黑树定义的情况,那么就依靠红黑树的三个核心操作来保证树的平衡,这三个操作也对应了红黑树定义的三条规则,分别如下:

  • 左旋转(当出现右红子节点时,进行左旋转)
  • 右旋转(当出现两条相连的左子红链接时,进行右旋转)
  • 变色(当左右节点都是红链接时,进行变色)

 

左旋转

将红色的右节点,调整到树的左边,假如我要在树的底部插入元素S,但是元素被分配到的元素E的右边,具体如下:

这是一条明显的红右链接,违反了红黑树定义的第一条规则,所以我们需要将它进行左旋转操作,被操作了左旋转后,元素E的位置会被元素S取代,E元素成为了S的左子节点,符合了二叉树的定义,左旋转的具体代码:

    private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = x.left.color;
        x.left.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

 

 

右旋转

当左边出现连续的左红链接时,把左链接放到右边(右旋转的代码和左旋转几乎相同把 x.left 换成 x.right 即可)

右旋转的代码

    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = x.right.color;
        x.right.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

 

变色

当左右子节点都是红色的时候,把颜色进行转换,具体如图:

颜色转换的代码也非常简单:

    private void flipColors(Node h) {
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
    }

理解了以上三种操作的原理,基本也就理解了红黑树的原理,有了这三种操作的基本知识,最后我们开始结合案例来分析红黑树插入平衡的全过程

我们先看一张图,这张图是一组有序数据插入的过程,再逐步分析红黑树是怎么把它构造为一颗接近完美平衡的树

这是一组 A,C,E,H,L,M,P,R,S,X 的有序字母数据,以下是对红黑树的插入平衡性进行解析

  1. A首先成为根节点
  2. C首先插入在A的右边,A违反了不能出现红右子节点的规则,进行左旋转,A成了C的左红子节点
  3. E首先插入在C的右边,C违反左右子节点均为红色的规则,进行变色,C,A,E变黑(根节点永远为黑)
  4. H首先插入在E的右边,E违反了不能出现红右子节点的规则,进行左旋转,E成了H的左红子节点
  5. L首先插入在H的右边,H违反左右子节点均为红色的规则,进行变色,E,L变黑,H变红,导致C违反了不能出现红右子节点的规则,进行左旋转,C成为H的左红子节点(这里违反2个规则)
  6. M首先插入在L的右边,L违反了不能出现红右子节点的规则,进行左旋转,L成为M的左红子节点
  7. P首先插入在M的右边,M违反左右子节点均为红色的规则,进行变色,L,P变黑,M变红,导致H违反左右子节点均为红色的规则,进行变色,H,C,M变黑(这里违反2个规则)
  8. R首先插入到P的右边,P违反了不能出现红右子节点的规则,进行左旋转,P成为R的左红子节点
  9. S首先插入到R的右边,R违反左右子节点均为红色的规则,进行变色,S,P变黑,R变红,导致M违反了不能出现红右子节点的规则,进行左旋转,M成为R的左红子节点
  10. X首先插入到S的右边,S违反了不能出现红右子节点的规则,进行左旋转,S成为X的左红子节点

通过以上证明,就可以得出结论,和二叉树不同,无论数据的插入顺序如何,红黑树都可以保证完美平衡,

理解红黑树的背后思想,就能明白只要谨慎的使用简单的,左旋,右旋,变色这三个操作,就可以保证红黑树的两种重要的特性 有序性和完美平衡性,因为旋转和变色都是局部操作,所以无需为整棵树的平衡性担心,另外红黑树的查找完全和二叉树相同,不需要额外的平衡,这里并不打算讲红黑树的删除操作,因为红黑树的删除实现复杂,比插入平衡还要复杂的多,要在文章里讲清楚很困难,推荐大家去看看我开篇推荐的经典书籍

总结

到这里对于为什么要使用红黑树的结论已经非常简单了,红黑树最吸引人的是它的所有操作在 最好 最坏 情况下都可以保证对数级别的时间复杂度 O(logN),是什么概念呢,可以简单说明对比下:

例如要在十亿级别的数据量找到一条数据,十亿的对数是30,线性表要找到数据需要访问十亿次,而使用红黑树的书只需要访问30次元素就能找到,10亿次/30次,差不多是3千万倍的性能提升,在现代上千亿数据的信息海洋里,只要通过几十次的比较就能随意的插入和查找数据,这是多么了不起的成就呀

而且对于二叉树,无数的实验和应用都能证明,红黑树的操作成本比二叉树要低 40% 左右(包含旋转和变色),红黑树自从被发现这40年来,一直高效稳定的通过各种应用的考验,包含需要系统基础组件和类库都是用红黑树,所以非常值得我们去学习和掌握它,最后留给大家一个问题,红黑树和散列表有什么区别,散列表查找的时间复杂度是常数级别 O(1),那为什么很多场景我们不用散列表而用红黑树呢?欢迎留言拍砖

 

参考资料:

https://algs4.cs.princeton.edu/33balanced/

https://algs4.cs.princeton.edu/33balanced/RedBlackBST.java.html

https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91

https://book.douban.com/subject/10432347/ 

おすすめ

転載: www.cnblogs.com/xiao2shiqi/p/11297819.html