红黑树(Red-black tree)

红黑树(Red-black tree)的基本知识

一.定义

红黑树是一种自平衡二叉查找树,高效的O(log n)时间内做查找,插入和删除。

NIL节点表示数据的结束,画图时应该也体现出来。
二.特点
  1. 任意节点的左子树不空,则左子树上所有节点的值均小于它的根结点的值;
  2. 任意节点的右子树不空,则右子树上所有节点的值均大于它的根结点的值;
  3. 任意节点的左、右子树也分别为二叉查找树;
  4. 没有键值相等的节点;
  5. 节点是红色或黑色;
  6. 根是黑色, 所有叶子都是黑色;
  7. 每个红色节点必须有两个黑色的子节点;(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  8. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点;

    通过节点颜色,限制了二叉树的高度。
    操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

三.抛出问题

O(log n)的操作效率是如何得出的?为什么与高度成比例,而不是与节点数成比例?
一个由n个节点随机构成的二叉查找树的高度为(log n ).证明如下:

而时间复杂度是以某个基础数据操作的重复次数作为量度。红黑树的是二叉搜索树,左子树上所有节点的值均小于他的根节点的值,右子树上所有节点均大于根节点的值,左右子节树相对根节点按大小分布。如果把每次节点值的比较看成基础数据操作,那么最差的查找情况是一直查找到高度最大的根节点,那么查找的时间复杂度即与高度成正比,可表示成O(log n)

如何生成红黑树

简单了解了红黑树的字面定义,下面动手感受下红黑树的相关操作。当你插入或者删除一个节点时,可能会破坏红黑树的性质,所以需要对树节点进行重新着色或者旋转,来保持红黑树的结构。首先看下二叉树的旋转。

一.旋转
  • 左旋

假设pivot节点不为空,其右子树不为空,那么左旋即是:使pivot的右孩子Y为子树的根,pivot节点为子树根节点的左孩子,pivot左孩子、Y节点的右孩子不改变,Y节点左孩子变为pivot节点右孩子。

  • 右旋

假设pivot节点不为空,其左子树不为空,那么右旋:使pivot的左孩子Y为子树的根,pivot节点为子树根节点的右孩子,pivot的右孩子、Y节点的左孩子不变,Y节点的右孩子变为pivot节点的左孩子。

任意节点的左子树不空,则左子树上所有节点的值均小于它的根结点的值
任意节点的右子树不空,则右子树上所有节点的值均大于它的根结点的值

实战演练之增加、删除节点时,如何保证红黑树的性质不被破坏。

二.增加节点

往一个空的红黑树中,依次插入数据:12 1 9 2 0 11 7 19 4

  • 插入12

节点为根节点,所以为黑色,两个null节点为黑色节点。

  • 插入 1

1小于12,所以是根节点的左孩子,如果为黑色,那么违反性质:从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。所以新增节点为红色。

  • 插入 9

按照二叉搜索树的逻辑,9小于12、大于1,应该是1节点的右孩子。但,新增的两个NIL节点已经使得12,1,9,NI这条路径的黑色节点至少为两个,而12,NIL这条路径的黑色节点只有两个。所以要对1节点进行左旋,9节点变为12节点的左孩子,发现问题还是存在。继续,对12节点进行右旋,9节点为根节点,1、12分别为9节点的左右孩子。尝试着色,9节点必须为黑色,而1,12节点可以为红色,也可以为黑色。

  • 插入 2

2大于1,直接作为1的右孩子即可。2的增加必然会增加两个黑色NIL节点,所以每条路径至少有三个黑色节点。则9,12,NIL这条路径中,12节点应该变为黑色;9,1,NIL这条路径中,1应该变为黑色。9,1,2,NIL这条路径中,2为空色来使得黑色节点个数为三个。验证一下,满足从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。则颜色调整完毕。

  • 插入 0

0节点直接作为1节点的左孩子,保持跟2节点相同的颜色即可。左右子树依旧保持平衡。

  • 插入 11

11节点作为12节点的左子树,颜色跟同一层的0,2节点保持一致即可。

  • 插入 7

从二叉查找树的性质看,7节点作为2节点的右孩子即可。这时来分析着色问题,我们先看最短路径的黑色分布,9,12,NIL这条路径,有三个黑色节点,以此为参考,尝试改变9节点左子树的着色。目前最长的路径是9,1,2,7,NIL这条路径。保持三个黑色节点的话,9跟NIL已经为黑色节点,而红色节点又不能挨着,所以只能是1为红色节点,2为黑色节点,7为红色节点。那么9,1,0,NIIL这条路径,0就要为黑色节点。调整完毕。

  • 插入 19

19节点作为12节点的右孩子,与左孩子保持一样的红色即可。

  • 插入 4

4节点应该作为7节点的左子树,无论着什么颜色,以1节点为根节点的子树,都要破坏红黑性质。所以应该进行旋转。先以7为根节点进行一次右旋,再以2为根节点进行一次左旋。尝试着色即可。

思维误区:之前我把左右字数高度相差不能超过1加入到红黑树的性质中,这导致我的推理逻辑发生了错误。因为红黑树是用红黑着色来保证高度,我直接用使用结果来推导红黑着色,这样会忽略红黑树本身的优点,而只关注了平衡二叉树的优点。错误思维路线:我先优先保持二叉搜索树的性质,然后通过旋转使其保持平衡,然后再进行颜色调整。感觉只是在探讨平衡二叉搜索树的性质,红黑性质被弱化了,仅仅是为了构造一个红黑树而调整着色。思维调整:先从二叉搜索树的角度对节点进行插入,然后从着色的角度对树进行旋转。
插入节点的五种情况:
  1. 新节点N位于树的根上,没有父节点。
  2. 新节点的父节点P是黑色情形。
  3. 父节点P、叔叔节点U,都为红色。
  4. 父节点P是红色,叔叔节点U是黑色或NIL; 插入节点N是其父节点P的右孩子,而父节点P又是其父节点的左孩子。
  5. 父节点P是红色,而叔父节点U 是黑色或NIL,要插入的节点N 是其父节点的左孩子,而父节点P又是其父G的左孩子。

    这里,先不补充每种情况的操作方法,你是不是能自己动手写下呢?欢迎留言~

三.删除节点

类似插入节点的分析、总结,删除节点也可以针对每种场景找到固定的着色方法,就像玩一个游戏,有自己的推理跟玩法。我先做个PPT,这块稍后补充。

使用场景

所有的插入、删除都是有限个情况,基于插入、删除的情况分析,即可编写算法生成红黑树,使其在固定的业务场景中发挥红黑树稳定操作效率的特色了。

  • 实现关联数组
    关联数组又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。C++的STL中。map和set都是用红黑树实现的。
  • 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
  • epoll在内核中的实现,用红黑树管理事件块
  • nginx中,用红黑树管理timer
  • Java的TreeMap实现

二叉搜索树之间的对比

一.AVL树

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

红黑树相对于AVL树来说,牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。
posted @ 2019-07-31 21:22  qxwang  阅读(88)  评论(0)    收藏  举报