红黑树并没有我们想象的那么难(上)

红黑树并没有想象的那么难, 初学者觉得晦涩难读可能是因为情况太多. 红黑树的情况可以通过归结, 通过合并来得到更少的情况, 如此可以加深对红黑树的理解. 网络上的大部分红黑树的讲解因为没有「合并」. 红黑树的五个性质:

性质1. 节点是红色或黑色。

性质2. 根是黑色。

性质3. 所有叶子都是黑色(叶子是NIL节点)。

性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。

红黑树的数据结构

摘自 sgi stl 红黑树数据结构定义:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

typedef bool _Rb_tree_Color_type;

const _Rb_tree_Color_type _S_rb_tree_red = false;

const _Rb_tree_Color_type _S_rb_tree_black = true;

 

struct _Rb_tree_node_base

{

  typedef _Rb_tree_Color_type _Color_type;

  typedef _Rb_tree_node_base* _Base_ptr;

 

  _Color_type _M_color;

  _Base_ptr _M_parent;

  _Base_ptr _M_left;

  _Base_ptr _M_right;

 

  static _Base_ptr _S_minimum(_Base_ptr __x)

  {

    while (__x->_M_left != 0) __x = __x->_M_left;

    return __x;

  }

 

  static _Base_ptr _S_maximum(_Base_ptr __x)

  {

    while (__x->_M_right != 0) __x = __x->_M_right;

    return __x;

  }

};

 

template <class _Value>

struct _Rb_tree_node : public _Rb_tree_node_base

{

  typedef _Rb_tree_node<_Value>* _Link_type;

  _Value _M_value_field;

};

二叉搜索树的插入删除操作

在展开红黑树之前, 首先来看看普通二叉搜索树的插入和删除. 插入很容易理解, 比当前值大就往右走, 比当前值小就往左走. 详细展开的是删除操作.

二叉树的删除操作有一个技巧, 即在查找到需要删除的节点 X; 接着我们找到要么在它的左子树中的最大元素节点 M、要么在它的右子树中的最小元素节点 M, 并交换(M,X). 此时, M 节点必然至多只有一个孩子; 最后一个步骤就是用 M 的子节点代替 M 节点就完成了. 所以, 所有的删除操作最后都会归结为删除一个至多只有一个孩子的节点, 而我们删除这个节点后, 用它的孩子替换就好了. 将会看到 sgi stl map 就是这样的策略.

在红黑树删除操作讲解中, 我们假设代替 M 的节点是 N(下面的讲述不再出现 M).

红黑树的插入

插入新节点总是红色节点, 因为不会破坏性质 5, 尽可能维持所有性质.

假设, 新插入的节点为 N, N 节点的父节点为 P, P 的兄弟(N 的叔父)节点为 U, P 的父亲(N 的爷爷)节点为 G. 所以有如下的印象图:

插入节点的关键是:

    • 插入新节点总是红色节点
    • 如果插入节点的父节点是黑色, 能维持性质
    • 如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质

插入算法详解如下, 走一遍红黑树维持其性质的过程:

第 0.0 种情况, N 为根节点, 直接 N->黑. over

第 0.1 种情况, N 的父节点为黑色, 这不违反红黑树的五种性质. over

第 1 种情况, N,P,U 都红(G 肯定黑). 策略: G->红, N,P->黑. 此时, G 红, 如果 G 的父亲也是红, 性质又被破坏了, HACK: 可以将 GPUN 看成一个新的红色 N 节点, 如此递归调整下去; 特俗的, 如果碰巧将根节点染成了红色, 可以在算法的最后强制 root->红.

第 2 种情况, P 为红, N 为 P 右孩子, N 为红, U 为黑或缺少. 策略: 旋转变换, 从而进入下一种情况:

第 3 种情况, 可能由第二种变化而来, 但不是一定: P 为红, N 为 P 左孩子, N 为红. 策略: 旋转, 交换 P,G 颜色, 调整后, 因为 P 为黑色, 所以不怕 P 的父节点是红色的情况. over

红黑树的插入就为上面的三种情况. 你可以做镜像变换从而得到其他的情况.

红黑树的删除

假设 N 节点见上面普通二叉树删除中的定义, P 为 N 父节点, S 为 N 的兄弟节点, SL,SR 分别是 S 的左右子节点. 有如下印象图:

 N 没有任何的孩子!

删除节点的关键是:

    • 如果删除的是红色节点, 不破坏性质
    • 如果删除的是黑色节点, 那么这个路径上就会少一个黑色节点, 破坏了性质. 故删除算法就是通过重新着色或旋转, 来维持性质

删除算法详解如下, 走一遍红黑树维持其性质的过程:

第 0.0 情况, N 为根节点. over

第 0.1 情况, 删除的节点为红. over

第 0.2 情况, 删除节点为黑, N 为红. 策略: N->黑, 重新平衡. over

第 1 情况, N,P,S,SR,SL 都黑. 策略: S->红. 通过 PN,PS 的黑色节点数量相同了, 但会比其他路径多一个, 解决的方法是在 P 上从情况 0 开始继续调整. 为什么要这样呢? HANKS: 因为既然 PN,PS 路径上的黑节点数量相同而且比其他路径会少一个黑节点, 那何不将其整体看成了一个 N 节点! 这是递归原理.

第 2 情况, S 红, 根据红黑树性质 P,SL,SR 一定黑. 策略: 旋转, 交换 P,S 颜色. 处理后关注的范围缩小, 下面的情况对应下面的框图, 算法从框图重新开始, 进入下一个情况:

第 2.1 情况, S,SL,SR 都黑. 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点, 通过 S 的黑节点个数不变, 所以维持了性质 5. over. 将看到, sgi stl map 源代码中将第 2.1 和第 1 情况合并成一种情况, 下节展开.

第 2.2.1 情况, S,SR 黑, SL 红. 策略: 旋转, 变换 SL,S 颜色. 从而又进入下一种情况:

第 2.2.2 情况, S 黑, SR 红. 策略: 旋转, 交换 S,P 颜色, SR->黑色, 重新获得平衡.

上面情况标号 X.X.X 并不是说这些关系是嵌套的, 只是这样展开容易理解. 此时, 解释三个地方:

    • 通过 N 的黑色节点数量多了一个
    • 通过 SL 的黑色节点数量不变
    • 通过 SR 的黑色节点数量不变

红黑树删除重新调整伪代码如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

// 第 0.0 情况, N 为根节点. over

if N.parent == NULL:

    return;

 

// 第 0.1 情况, 删除的节点为红. over

if color == RED:

    return;

 

// 第 0.2 情况, 删除节点为黑, N 为红, 简单变换: N->黑, 重新平衡. over

if color == BLACK && N.color == RED:

    N.color = BLACK;

 

// 第 1 种情况, N,P,S,SR,SL 都黑. 策略: S->红. 通过 N,S 的黑色节点数量相同了, 但会比其他路径多一个, 解决的方法是在 P 上从情况 0开始继续调整.

if N,P,S,SR,SL.color == BLACK:

    S.color = RED;

 

    // 调整节点关系

    = P

    N.parent = P.parent

    = P.paernt.another_child

    SL = S.left_child

    SR = S.right_child

    continue;

 

// 第 2 情况, S 红, 根据红黑树性质 P,SR,SL 一定黑. 旋转, 交换 P,S 颜色. 此时关注的范围缩小, 下面的情况对应下面的框图, 算法从框图重新开始.

if S.color == RED:

    rotate(P);

    swap(P.color,S.color);

 

    // 调整节点关系

    = P.another_child

    SL = S.left_child

    SR = S.right_child

 

// 第 2.1 情况, S,SL,SR 都黑. 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点, 通过 S 的黑节点个数不变, 所以维持了性质 5.over. 将看到, sgi stl map 源代码中将第 2.1 和第 1 情况合并成一种情况, 下节展开.

if S,SL,SR.color == BLACK:

    P.color = BLACK;

    S.color = RED;

    return

 

// 第 2.2.1 情况, S,SR 黑, SL 红. 策略: 旋转, 变换 SL,S 颜色. 从而又进入下一种情况:

if  S,SR.color == BLACK && SL.color == RED:

    rotate(P);

    swap(S.color,SL.color);

 

    // 调整节点关系

    = SL

    SL = S.left_child

    SR = S.right_child

 

// 第 2.2.2 情况, S 黑, SR 红. 策略: 旋转, 交换 S,P 颜色.

if S.color == BLACK && SR.color == RED:

    rotate(P);

    swap(P.color,S.color);

    return;

总结

所以, 插入的情况只有三种, 删除的情况只有两种. 上面的分析, 做镜像处理, 就能得到插入删除的全部算法, 脑补吧. 从上面的分析来看, 红黑树具有以下特性: 插入删除操作都是 0(lnN), 且最多旋转三次.

时间: 2024-10-29 19:10:02

红黑树并没有我们想象的那么难(上)的相关文章

红黑树并没有我们想象的那么难(下)

SGI STL map 实现概述 根据上一节的红黑树分析, 结合 sgi stl map 的实现, 看看红黑树的源码是如何实现的. 以下主要以代码的注释为主. sgi stl map 底层实现是 _Rb_tree类, 为了方便管理, _Rb_tree 内置了 _M_header, 用于记录红黑树中的根节点, 最小节点和最大节点. 在插入删除中都会对其进行维护. 找到一副美艳的图片: 我只会展开插入和删除的代码. _Rb_tree 有 insert_unique() 和 insert_equal(

红黑树学习资料总结

关于红黑树的插入:红黑树并没有我们想象的那么难(上) 关于红黑树的删除:红黑树插入删除 关于红黑树的源码剖析:红黑树C源码实现与剖析

算法---红黑树实现介绍(一)

一.概述 红黑树是一种经典的存储结构,就其本身来说是一个二叉查找树,只是在这个基础上,树的节点增加了一个属性用于表示颜色(红或黑).通过限制从根节点到叶子的各个路径的节点着色的限制,来保证不会有哪个路径会比其它的路径长度超过2倍,从而红黑树是接近平衡的. 一直以来没有把红黑树完全理解,总觉得太难,望而生畏,最近下决心要弄清楚,也是花了很长时间,不过总算是明白了.记录下来以便更好的理解. 二.红黑树的特点 作为红黑树,需要有这5个限制,如下: 1)树中的每个节点,要么是红色,要么是黑色 2)树的根

红黑树(Red Black Tree)

介绍另一种平衡二叉树:红黑树(Red Black Tree),红黑树由Rudolf Bayer于1972年发明,当时被称为平衡二叉B树(symmetric binary B-trees),1978年被Leonidas J. Guibas 和 Robert Sedgewick改成一个比较摩登的名字:红黑树. 红黑树和之前所讲的AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能.自从红黑树出来后,AVL树就被放到了博物馆里,据说是红黑树有更好的效率,更高

树-红黑树(R-B Tree)

红黑树概念 特殊的二叉查找树,每个节点上都有存储位表示节点的颜色是红(Red)或黑(Black).时间复杂度是O(lgn),效率高. 特性: (1)每个节点或者是黑色,或者是红色. (2)根节点是黑色. (3)每个叶子节点(NIL)是黑色.(只为空(NIL或null)的节点) (4)如果一个节点是红色的,则它的子节点必须是黑色的.(黑结点可连续,红结点不能连续) (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点. 定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).

红黑树详细介绍三

根据之前红黑树的原理和<算法导论>上面的伪代码,我用代码将增加和调整的代码实现了一下,如有不对请大家指正.代码可以结合前两篇文章看. 红黑树的详细介绍一 红黑树详细介绍二 /* * ===================================================================================== * * Filename: rbtree->h * * Description: red black tree * * Version:

红黑树的实现

红黑树首先是一棵二叉查找树,它每个结点都被标上了颜色(红色或黑色),红黑树满足以下5个性质: 1. 每个结点的颜色只能是红色或黑色. 2. 根结点是黑色的. 3. 每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵:如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵. 4. 如果一个结点是红的,则它的两个儿子都是黑的.也就是说在一条路径上不能出现相邻的两个红色结点. 5. 对于每个结点来说,从该结点到其子孙叶结点的所有路径上包含相同数

红黑树(附完整C代码)

版权声明:原创不易,转载请注明转自weewqrer 红黑树 红黑树简介 首先红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK.通过对一条从根节点到NIL叶节点(指空结点或者下面说的哨兵)的简单路径上各个结点在颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的. 用途 红黑树和AVL树一样都对插入时间.删除时间和查找时间提供了最好可能的最坏情况担保.对于查找.插入.删除.最大.最小等动态操作的时间复杂度为O(lgn).常见的

nginx中的红黑树

好吧,nginx是个幌子.主要介绍对红黑树的一些理论知识,nginx的主要源码附在后面.这篇文章并不会面面俱到,我的水平也没到那个高度. 红黑树的特性: 1.红黑树是一棵二叉搜索树. 2.树上的每个结点或为红,或为黑. 3.如果一个结点为红色,那么它的左右子结点一定为黑色. 4.从根结点到每个叶子结点路径中经过的黑色结点数是相同的. 5.根结点必须是黑色. 理解这些特性是很重要的.正是因为有这些特性,才保证了红黑树的高效.(至于为什么,我学的还不精,不乱讲了.). 红黑树的插入和删除结点动作本质