热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

从无到有构建红黑树

红黑树是60年代中期计算机科学界找寻一种算法复杂度稳定,容易实现的数据存储算法的产物。在优先级队列、字典等实用领域都有广泛地应用,更是70年代提出的关系数据库模型--B树的鼻祖。在Lin
红黑树是60年代中期计算机科学界找寻一种算法复杂度稳定,容易实现的数据存储算法的产物。在优先级队列、字典等实用领域都有广泛地应用,更是70年代提出的关系数据库模型--B树的鼻祖。 Linux kernel中,高精度定时器也工作在红黑树之上 为便于初学者掌握其基本算法,本文一步一步地演示了红黑树的创建过程。

首先回顾一下红黑树的基本性质:

    1. 红黑树本质上是一个二叉查找树(BST),但是它从根到最远叶子的长度不会超过到最近叶子长度的两倍,因此是近似平衡的。

    2. 红黑树的节点不是黑的就是红的,不会有第三种颜色。

    3. 树根必须是黑色。

    4. 叶子所指的空节点必须是黑色。

    5. 如果某个节点是红色,那么它的两个儿子必须都是黑色。

    6. 从任意节点出发的所有向下的路径上包含相同个数的黑节点。这个个数我们称为黑高度Bh。

    有了上面的认识,我们要从无到有构造一颗红黑树的话,心里就有底了。这个构造的过程可以分两步:

    第一步:执行BST的插入算法;

    第二步:对节点着色;

    第一步不会有什么问题,关键是第二步怎么对节点着色才不会违反红黑树的基本性质;

    这是一个难点,我在写这篇文章的时候脑子也卡了一下,节点不多的时候好办,但是如果节点很多了,就必须找到一种普遍适用的算法来处理。通过这两天的观察,我发现这六条性质中最关键的其实是最后一条---从任意结点出发的任意路径黑高度相等!这才是红黑树算法保持近似平衡的精华所在!其它性质要么是这条性质的产物,要么就是为这条性质服务的。

    现在我演示一下怎么从无到有生成一棵红黑树。

例如我们有一组数:{9,7,15,6,11,19},现在按照从左到右的顺序存放在一颗红黑树中。


第一个数是9,很自然地成为了树根,如图:

图-1

    每个新节点都默认地被渲染成了红色,为什么要这么做呢?后面我们将会看到它的好处!现在先忽略不谈。


    根节点9是红色,这违背了性质3,所以必须改成黑色,如图:

图-2

下一个数字是7,显然要被插入到9的左边,并且这时满足红黑树的所有性质,如图:

图-3

下一个数字是15,要插入到9的右边,并且也满足红黑树的所有性质,如图:

                                                 图-4

下一个数字是6,要插入到7的左边,这回似乎有麻烦了,性质5被违背了,如图:

图-5

可能有人会说,那很简单,既然违背了性质5,那我改一下6的颜色不就OK了?

图-6

    现在,恭喜你---也走进了我曾经走过的误区:)你的无意之举改变了新增节点路径上面的黑高度,这棵树已经不是红黑树了

    写代码的人都知道,对树的遍历,最简单的方法就是递归,那么树的数学模型就是一个可穷举的递归式。递归式中每一步的正确性都建立在前一步正确的基础之上。现在我们回过头来想想为什么每次插入的新节点都被渲染成红色呢?你肯定猜到了,因为我们要保证红黑树的核心性质--黑高度不发生变化,虽然牺牲了性质5,但这是可以补救的,并且代价很小。再来看看图-5

    既然我们不能通过改变新插入节点的颜色来维持红黑树的性质,那么就只好从原来的树结构入手了。


    我们注意到新插入的节点的父亲是一个红节点,其叔叔也是一个红节点。那么假如我把它的父亲改成黑色,则恢复了性质5,可是从树根出发的黑高度还是不相等,干脆把它的叔叔也改成黑色吧!结果如下:

图-7

现在它满足红黑树所有的性质。好了,我们继续。

插入数字11和19的过程平淡无奇,不深入探讨,最终的树如图:

图-8

如果现在要插入数字10怎么办?它肯定会是11的左节点:

                                              图-9

明显违背了性质5!难道依葫芦画瓢,把11和19都改成黑色?这样从树根出发的左边路径黑高度还是2,而右边两条路径的黑高度将变成了3,老办法失效了!

图-10

    其实不然,改了新增节点的父亲和叔叔的颜色以后,右边路径黑高度全加一,但我们只要把它的爷爷改成红色不就又减去了多出来的黑高度吗?

图-11

如果你认真看到这里,我想你的潜意识一定告诉你---这里存在某种规律等着我们去发现。

让我们再仔细审视一下图-5

    在生成树的过程中,我们已经两次遇到类似情况了,归纳一下,这个场景就是:新节点的父亲是红色,叔叔也是红色。


    至于别的节点,我们大可不必关心,因为很显然,这个场景是原子的。


    我们的处理办法是把7和15渲染成黑色,就像图-7那样,可是因为这是一个普遍适用的场景,所以要做一个扩展:假设在这个原子树的每个节点上还有别的分支。那么图-7就不能保证所有路径的黑高度相等了,即使把9改成红色也无济于事,因为9也许还有父节点,所以也许它的父节点又是红色,这就再次违反了性质5。我最喜欢的作家之一--柯南.道尔,曾说过:“历史就像车轮的辐条,每一根都终将再次转回来。”眼前的场景正是如此---我们设计的原子场景再次出现。

一个清晰的递归算法呈现在我们面前:

1. 新增节点渲染成红色;

2. 如果它的父亲是红色,则违反了性质5;

3. 如果它的叔叔也是红色,则通过同时修改其父亲和叔叔的颜色为黑色来恢复性质5;

4. 如果它的爷爷不是根节点,则有可能在另外的路径上再次违反性质5,于是我们把它的爷爷改成红色;

5. 可是如果他的太爷爷也是红色呢?很自然地,我们重新回到步骤2;

6. 不断循环,直到第二步满足就可以结束。

最后留给感兴趣的同学两个问题:

1. 如果新增节点的父亲是红色,但它的叔叔是黑色,该怎么办?(提示:使用旋转)

2. 有人说,下面这种场景,我设计的算法就失效了,真的吗?(粉色的6是新插入的节点)

注:转载源地址


推荐阅读
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • Netflix利用Druid实现高效实时数据分析
    本文探讨了全球领先的在线娱乐公司Netflix如何通过采用Apache Druid,实现了高效的数据采集、处理和实时分析,从而显著提升了用户体验和业务决策的准确性。文章详细介绍了Netflix在系统架构、数据摄取、管理和查询方面的实践,并展示了Druid在大规模数据处理中的卓越性能。 ... [详细]
  • 本文探讨了哪些数据库支持队列式的写入操作(即一个键对应一个队列,数据可以连续入队),并且具备良好的持久化特性。这类需求通常出现在需要高效处理和存储大量有序数据的场景中。 ... [详细]
  • 深入解析RDMA中的队列对(Queue Pair)
    本文将详细探讨RDMA架构中的关键组件——队列对(Queue Pair,简称QP),包括其基本概念、硬件与软件实现、QPC的作用、QPN的分配机制以及用户接口和状态机。通过这些内容,读者可以更全面地理解QP在RDMA通信中的重要性和工作原理。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
author-avatar
碎碎念囧雷
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有