红黑树是每个节点都带有颜色属性的二叉查找树,颜色为 红色 或 黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 节点是红色黑色
  2. 黑色
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色子节点。(从每个叶子的所有路径上不能有两个连续的红色节点。)
  5. 任一节点其每个叶子的所有简单路径都包含相同数目的黑色节点。

下面是一个具体的红黑树的图例:

常见的查找算法(五):树表查找之二 ---- 红黑树

旋转 

 旋转是一种能保持二叉搜索树性质的搜索树局部操作。其中两种旋转分别为左旋右旋

 在某个结点 x 上进行左旋时,假设它的右孩子为y而不是树的 T.nil 结点;x为其右孩子而不是 T.nil 结点的树内任意节点。

 左旋以 x 到 y 的链为“支轴”进行,使得 y 成为该子树的新的根节点,x 成为 y 的左孩子,y 的左孩子变成 x 的右孩子;右旋与此相反。

常见的查找算法(五):树表查找之二 ---- 红黑树

左旋代码:

 1     /**
 2      * 左旋
 3      * 左旋示意图(对节点x进行左旋):
 4      *     px                               px
 5      *     /                                /
 6      *     x                                y
 7      *    / \          --(左旋)-->         /  \
 8      *   lx   y                           x   ry
 9      *    / \                           /  \
10      *  ly  ry                        lx   ly
11      *
12      * @param x
13      */
14     private void leftRotate(RBTNode<T> x) {
15         RBTNode<T> y = x.right; // y是x的右节点
16 
17         x.right = y.left;       // 把x的左节点变为y的右节点
18         // 若y有左子节点,把y的左节点的父节点换成x
19         if (y.left != null) {
20             y.left.parent = x;
21         }
22 
23         y.parent = x.parent;    // y的父节点(原来是x)设为x的父节点
24 
25         // 若x是根节点,y直接变根节点
26         if (x.parent == null) {
27             this.mRoot = y;
28         } else {
29             if (x.parent.left == x) {
30                 x.parent.left = y;  // 如果x是x父节点的左孩子,把x的父节点的左孩子指向y
31             } else {
32                 x.parent.right = y; // 如果x是x父节点的右孩子,把x的父节点的右孩子指向y
33             }
34         }
35 
36         y.left = x;     // 将y的左节点指向x
37         x.parent = y;   // 将x的父节点设为y
38     }

右旋代码:

 1     /**
 2      * 右旋,操作和左旋相反
 3      * 右旋示意图(对节点y进行左旋):
 4      *      py                              py
 5      *     /                                /
 6      *     y                                x
 7      *    / \          --(右旋)-->         /  \
 8      *   x  ry                           lx   y
 9      *   / \                                  / \
10      *  lx  rx                               rx  ry
11      *
12      * @param y
13      */
14     private void rightRotate(RBTNode<T> y) {
15         RBTNode<T> x = y.left;  // y的左孩子
16 
17         y.left = x.right;
18         if (x.right != null) {
19             x.right.parent = y;
20         }
21 
22         x.parent = y.parent;
23 
24         if (y.parent == null) {
25             this.mRoot = x;
26         } else {
27             if (y.parent.left == y) {
28                 y.parent.left = x;
29             } else {
30                 y.parent.right = x;
31             }
32         }
33 
34         x.right = y;
35         y.parent = x;
36     }

红黑树新结点插入代码:

就像一个普通的二叉搜索树一样,将新结点插入树中,并将其着为红色。之后为了能保证红黑的性质,还需要一个辅助代码对结点重新着色并且旋转。

 1     /**
 2      * 插入操作
 3      *
 4      * @param node
 5      */
 6     private void insert(RBTNode<T> node) {
 7         int result;
 8         RBTNode<T> y = null;
 9         RBTNode<T> x = this.mRoot;
10 
11         // 查找树中插入点的父结点y的位置
12         while (x != null) {
13             y = x;  // 注意这里,y不是空的
14             result = node.key.compareTo(x.key);
15             if (result < 0) {
16                 x = x.left;
17             } else {
18                 x = x.right;
19             }
20         }
21 
22         node.parent = y;
23         if (y != null) {
24             result = node.key.compareTo(y.key);
25             if (result < 0) {
26                 y.left = node;
27             } else {
28                 y.right = node;
29             }
30         } else {
31             this.mRoot = node;
32         }
33 
34         node.color = RED;
35 
36         // 插入后修正树
37         insertFixUp(node);
38     }

辅助修正函数:

 1     /**
 2      * 红黑树插入修正函数
 3      *
 4      * @param node
 5      */
 6     private void insertFixUp(RBTNode<T> node) {
 7         RBTNode<T> parent, gparent; // 父节点,祖父节点
 8 
 9         while (((parent = parentOf(node)) != null) && isRed(parent)) {
10             gparent = parentOf(parent);
11 
12             // 父节点是祖父节点的左孩子
13             if (parent == gparent.left) {
14                 RBTNode<T> uncle = gparent.right; // 叔叔节点,祖父的右节点
15 
16                 // ① 叔叔节点是红色的
17                 if ((uncle != null) && isRed(uncle)) {
18                     node.setColor(BLACK);
19                     parent.setColor(BLACK);
20                     gparent.setColor(RED);
21                     node = gparent;
22                     continue;
23                 }
24 
25                 // ② 叔叔是黑色,且当前节点是右孩子
26                 if (parent.right == node) {
27                     RBTNode<T> tmp;
28                     leftRotate(parent);
29                     tmp = parent;
30                     parent = node;
31                     node = tmp;
32                 }
33 
34                 // ③ 叔叔是黑色,且当前节点是左孩子
35                 parent.setColor(BLACK);
36                 gparent.setColor(RED);
37                 rightRotate(gparent);
38 
39             } else {    // 父节点是祖父节点的右孩子
40 
41                 RBTNode<T> uncle = gparent.left; // 叔叔节点,祖父的左节点
42 
43                 // ① 叔叔节点是红色的
44                 if ((uncle != null) && isRed(uncle)) {
45                     uncle.setColor(BLACK);
46                     parent.setColor(BLACK);
47                     gparent.setColor(RED);
48                     node = gparent;
49                     continue;
50                 }
51 
52                 // ② 叔叔是黑色,且当前节点是左孩子
53                 if (parent.left == node) {
54                     RBTNode<T> tmp;
55                     rightRotate(parent);
56                     tmp = parent;
57                     parent = node;
58                     node = tmp;
59                 }
60 
61                 // ③ 叔叔是黑色,且当前节点是右孩子
62                 parent.setColor(BLACK);
63                 gparent.setColor(RED);
64                 leftRotate(gparent);
65             }
66         }
67 
68         this.mRoot.setColor(BLACK);
69     }

修正过程实例:

以下图中的 z 为插入后的结点,y 表示叔结点uncle,图中的每个子树的低端的节点是红黑树代码中的边界,边界中每个节点有黑色的哨兵没有画出来。

 

下面是介绍的是上面代码中 父节点是祖父节点的左孩子 的代码。

 

先看图中的第一个树,插入的 z 结点和 z.parent 父节点都是 RED,这违反了性质四。

情况 1(得到的是图中的第二个树):由于图中的第一个树中叔结点是红色,z 结点和 z.parent 父节点都是 RED结点都要被重新着色,并沿着指针 z 上升;

情况 2(得到的是图中的第三个树):由于图中的第二个树中 z 及其父节点 z.parent 都为红色,其叔结点为黑色,左旋父节点 z.parent后得到;

情况 3(得到的是图中的第四个树):z 是其父节点的左孩子,重新着色后右旋的到图中的第四个树,这样之后就是合法的红黑树了。

常见的查找算法(五):树表查找之二 ---- 红黑树

分析红黑树的插入时间复杂度:

一颗具有 n 个节点的红黑树高度为O(log n),则按照一个普通的二叉查找树的方式插入结点需要花费 O(log n);修正代码中,当情况 1发生,指针 z沿着树上升2层,才会执行 while 循环,while 循环可能执行的总次数为 O(log n)。所以红黑树的插入的总的时间复杂度为 O(log n)。此外,插入算法中总的来说旋转次数不超过 2 次。

红黑树的删除:

 1     /**
 2      * 删除树中某个节点
 3      *
 4      * @param node 要删除的结点
 5      */
 6     private void remove(RBTNode<T> node) {
 7         RBTNode<T> child, parent;
 8         boolean color;
 9 
10         // 要删除的结点node有2个子结点
11         if ((node.left != null) && (node.right != null)) {
12             RBTNode<T> replace = node;
13 
14             // 寻找后继结点
15             replace = replace.right;
16             while (replace.left != null) {
17                 replace = replace.left;
18             }
19 
20             // 判断删除的结点是不是根结点
21             if (parentOf(node) != null) {
22                 if (parentOf(node).left == node) {
23                     parentOf(node).left = replace;
24                 } else {
25                     parentOf(node).right = replace;
26                 }
27             } else {
28                 this.mRoot = replace;
29             }
30 
31 
32             child = replace.right; // 后继结点的右孩子?左孩子呢?左孩子有早就是后继结点了,所以直接看后继结点还有没有右孩子
33             parent = parentOf(replace); // 后继结点的父结点
34             color = replace.color;  // 后继结点的颜色
35 
36             // 要删除的结点node是后继结点的父结点
37             if (parent == node) {
38                 parent = replace;   // 这里应该后继结点直接替换node,留下后继结点的右子树
39             } else {    // 后继结点的父结点不是要删除的结点node
40                 // 后继结点的孩子不为空
41                 if (child != null)
42                     child.setParent(parent); // 把<后继结点的右孩子>的<父结点>设为<后继结点的父结点>
43                 parent.left = child;    // <后继结点的父结点>的<左孩子>指向<后继结点的右孩子>
44 
45                 replace.right = node.right; // 后继结点的右孩子指向删除结点的右子树
46                 node.right.setParent(replace);  //删除结点的右子树的父结点设置为后继结点
47             }
48 
49             replace.parent = node.parent;
50             replace.color = node.color;
51             replace.left = node.left;
52             node.left.parent = replace;
53 
54             if (color == BLACK)
55                 removeFixup(child, parent);
56 
57             node = null;
58 
59             return;
60         }
61 
62         // 选一个要删除的结点的孩子
63         if (node.left != null) {
64             child = node.left;
65         } else {
66             child = node.right;
67         }
68 
69         parent = node.parent;
70         color = node.color;
71 
72         // 要删除的结点的孩子不为空
73         if (child != null)
74             child.parent = parent;
75 
76         // 要删除的结点的父结点是不是树根
77         if (parent != null) {
78             if (parent.left == node) {
79                 parent.left = child;
80             } else {
81                 parent.right = child;
82             }
83         } else {
84             this.mRoot = child;
85         }
86 
87         if (color == BLACK)
88             removeFixup(child, parent);
89 
90         node = null;
91     }
View Code

相关文章:

  • 2021-09-20
  • 2021-06-25
  • 2021-12-21
  • 2021-11-05
  • 2021-05-20
  • 2022-01-05
  • 2022-01-13
猜你喜欢
  • 2022-12-23
  • 2021-09-14
  • 2021-11-06
  • 2021-06-06
  • 2022-12-23
  • 2021-10-11
  • 2021-11-28
相关资源
相似解决方案