算法与数据结构--简析红黑树

1.为什么要使用红黑树:

可以保证在 O(logN)的时间复杂度下做查找 删除 添加

2.性质:

(来自于维基百科Red–black tree条目 )

  1. 节点是红色或者黑色的(Each node is either red or black)
  2. 根是黑色的,有时会被省略,由于根是黑色和红色对规范并没有其他影响(The root is black. This rule is sometimes omitted. Since the root can always be changed from red to black, but not necessarily vice versa, this rule has little effect on analysis)【这个特性与很多其他地方不同,但是从英文维基的解释,根节点是红是黑确实没有什么影响】
  3. 所有叶子是黑色的(叶子都是 nil 节点)
  4. 如果节点是红色,那他的子节点一定是黑色(If a node is red, then both its children are black)
  5. 一个节点的所有到 nil 叶子节点的路径都包含同样的黑色节点(Every path from a given node to any of its descendant NIL nodes contains the same number of black nodes)

2.1 其他定义:

  1. 从根节点到某个节点的路径中,包含黑色节点的数量,叫做黑色深度
  2. 从根到叶子的所有路径中的黑色节点的统一数量被称为红黑树的黑色高度

3.红黑树的关键属性:

  1. 从根到最远叶子的路径不超过从根到最近叶子的路径的两倍,可以使树大致高度平衡。由于,对树进行增 删 查的操作的最坏时间是和树的高度紧密相关的,基于这个关键属性,可以保证红黑树在最坏情况下仍然能保证效率
  2. 可以从性质4 5得知,红黑树是怎么保证这个关键属性。由于插入的所有节点都是红色的,由于性质4,插入节点的父节点是不允许为红色的。又因为性质5,黑色高度是相同的,所以从任意一点出发,最长的可能路径是2*B 即红黑交替的情况,最短则是完全不包含红色点的路径。

4.操作

4.1加入新节点

  • 根据规则4 新加的节点都是红色的
  • 加入处理顺序如下:(基于维基百科的算法)
    • 步骤1:
      • 条件:树是空树
      • 操作:直接将节点放入根节点。
    • 步骤2:
      • 条件:父节点是黑色
      • 操作:直接将节点加入父节点的空叶子节点
      • 说明:因为父节点是黑色没有违反性质4【从这里往下,说明父节点是红色的】
    • 步骤3:
      • 条件:如果叔父节点存在,并且也是红色,
      • 操作:将父节点和叔父节点都改为黑色,然后将祖父节点改为红色。为了保证祖父节点往上也不违反特性5,就会将祖父节点传入,重复步骤1到3,直到是根节点【步骤1】或者是根节点的子节点【步骤3】
      • 目的:因为直接加入节点后,违反了特性4 。这时候如果叔父节点为 nil 或者黑色,直接修改父节点颜色是会违反性质5
    • 步骤4:
      • 条件:叔父节点为 nil 或者黑色
      • 操作:
        • ①如果接入节点和其父节点是左右或右左形态,则直接对其进行左旋和右旋,以达到下一步操作的要求。
        • ②如果加入节点和其父节点是以左左或者右右形态。则直接对应右旋,左旋来操作。旋转完成后,由于加入节点的父亲是红色,祖父是黑色;所以违反了性质4,此时,将父亲节点改为黑色,又会导致叔父子树多一个黑色节点,所以将被旋转下去的祖父变为红色。
      • 目的:通过这种方法进行操作,是基于 AVL 的平衡旋转方案,减少了树的高度。

4.2删除节点:

  • 通常, 在二叉搜索树(红黑树属于此类型)中,删除一个节点,需要找到他左子树的最大节点或者是右子树的最小节点,将值与需要被删除的值的节点替换,并删除找出这个值的节点。所以,删除一个有两个子节点的节点的场景可以简化为删除一个节点只有一个子节点的场景。

  • 场景一:删除红色节点,那由性质4可得知。此节点的父子节点都是黑色。所以我们可以简单的使用他的黑色儿子来替换它

  • 场景二:被删除的节点他儿子是红色的,如果只是去删除这个黑色节点,用红色的儿子顶替的话,会违反性质5.但是如果我们把替换后的儿子变为黑色,则通过他的路径都会通过他的黑色儿子。

  • 场景三:当要删除的节点和他的儿子两个都是黑色的时候,这是复杂情况。下面将会分类讨论。出于方便,称呼这个儿子为N(在新的位置上),称呼它的兄弟(它父亲的另一个儿子)为S。我们使用P称呼N的父亲,SL称呼S的左儿子,SR称呼S的右儿子。【场景里实际上需要删除的节点已经被删除】。

  • 处理顺序如下:

    • 步骤1.
      • 条件:如果 N 是根节点,因为根节点为什么颜色都不违反红黑树的性质,所以直接操作完毕。
    • 步骤2.【下面的情形,S 都是黑色,只是对 S 儿子不同颜色进行不同操作】——为4 5 6步骤做准备
      • 条件:S 是红色,
      • 操作:对 N 的父亲进行旋转,旋转方向是 S 的反方向【即S 是 P 的左儿子则对 P 右旋】。旋转完成后 S 就变成了 N 的祖父。接着我们把 N 的父亲和祖父颜色进行对调【即将 S 和 P 的颜色对调】。
      • 目的:因为被删除的节点和 N 都是黑色的。所以 N 被删除以后已经违反了性质5。此时,通过旋转将原来是红色的 S 放到 N 和 P 的通过的路径上,并将 P 的颜色和 S 颜色进行对调。这样,对未被影响的一边的子树的黑色节点数没有任何影响,因为 P 和 S 是对调了颜色,另外一边也未受影响。因为旋转的影响,之前 S 子树缺少的黑色节点变成了SL 和SR两个子树各缺少一个黑色节点【因为P 和 S 的颜色对调了,所以这2个子树的上游的黑色节点并没有受影响】接下来就交给步骤4 5 6继续处理。【有一点比较疑惑,其实2和3交换顺序,并不会影响处理逻辑,而如果2放到后面,后面连续处理起来会更加的清晰。】
    • 步骤3——通过减少一个黑节点来使树符合条件
      • 条件:如果 P S 和 S 的儿子 都是黑色,
      • 操作:我们直接将 S 变为红色。然后将再从 P 开始步骤1的处理,直到到根。
      • 目的:因为之前被删除的节点是黑色,所以这个子树少了一个黑色节点。所以当 S 的 父亲和儿子都是黑色的时候,就解决了不符合性质5的情况,而且因为父亲儿子都是黑色,也是符合性质4的。但是,实际上 P 的兄弟也是少了一个黑色节点,所以需要递归的往上进行处理,直到根截止。
    • 步骤4.
      • 条件:S 和 S 的儿子都是黑色,但是 P 是红色。
      • 操作:交换 S 和 P 的颜色。
      • 目的:这不影响不通过N的路径的黑色节点的数目,但是它在通过N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点
    • 步骤5:
      • 条件:S 是黑色; N 是左儿子时,S 的左儿子是红色,右儿子是黑色;N 是右儿子时,S 的左儿子是黑色,右儿子是红色;
      • 操作:当 N 是左儿子时对 S 右旋,当 N 是右儿子时对 P 左旋。然后交换 S 和对应被提升成父节点的儿子的颜色。【即S 的儿子变成黑色,S 变成红色】
      • 目的:为了给步骤6做准备,因为如果直接旋转S,将 S 作为 P 的父节点时,S 的某一边的红色儿子会被转移到 P 的儿子位。此时,如果 P 是红色则违反了性质4,【两个连续红色】如果 P 是黑色,则违反了性质5。原来 S 的儿子少了一个黑色的路径
    • 步骤6:
      • 条件: S 是黑色的,S 的某一个儿子是红色的【可能是步骤5旋转后又对调颜色后的 S】。
      • 操作: P 做对 S 红色反方向的选择【即右红左转】。然后交换 P 和 S 的颜色,并让之前红色的孩子变为红色
      • 目的:把黑色的 S 旋转成为P 的父亲,并吧黑色交换给了 P。这时候相当于给 N 子树补齐了之前删除的黑色节点数,同时红色节点是S 子树的最高节点。将他变成黑色后,因为 S 黑色被换走后也被补充回来了。同时,P 与 S 对调了颜色。不管现在 S 是什么颜色P 和 S 儿子这2个子树的黑色节点数都是相同的。

你可能感兴趣的:(算法与数据结构--简析红黑树)