数据结构 -- 红黑树精解

Posted wangbingc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 -- 红黑树精解相关的知识,希望对你有一定的参考价值。

普通二叉查找树

 

 

红黑树

 创建节点

 1 /**
 2  * 红黑树节点
 3  */
 4 class Node {
 5 
 6     /** 节点颜色 */
 7     public Color color;
 8     /** 存储值 */
 9     public int val;
10     /** 父节点 */
11     public Node parent;
12     /** 左节点 */
13     public Node left;
14     /** 右节点 */
15     public Node right;
16 
17 }

为了方便后续操作,对节点类进行一些改进

  • 红黑树的叶子节点是null节点, 为了方便判断叶子节点的颜色(黑色), 创建一个特殊节点代替null节点
  • 为节点类添加相应构造方法
  • 为节点类创建两个辅助性方法 为当前节点插入左节点: appendLeft(Node)      为当前节点插入右节点: appendRight(Node)
 1 /**
 2  * 红黑树节点
 3  */
 4 class Node {
 5 
 6     /** 创建一个特殊节点代替null节点 方便给他附上颜色 */
 7     public static Node NIL = new Node(Color.BlACK, null, null, null, null);
 8 
 9     /** 节点颜色 */
10     public Color color;
11     /** 存储值 */
12     public Integer val;
13     /** 父节点 */
14     public Node parent;
15     /** 左节点 */
16     public Node left;
17     /** 右节点 */
18     public Node right;
19 
20     public Node() {
21     }
22 
23     public Node(Integer val) {
24         this(Color.RED, val, NIL, NIL, NIL);
25         this.val = val;
26     }
27 
28     public Node(Color color, Integer val, Node parent, Node left, Node right) {
29         super();
30         this.color = color;
31         this.val = val;
32         this.parent = parent;
33         this.left = left;
34         this.right = right;
35     }
36 
37     /** 工具:插入左节点 */
38     public boolean appendLeft(Node node) {
39 
40         if (node == NIL) {
41             System.err.println("添加节点不能为null");
42             return false;
43         }
44         if (this.left != NIL) {
45             System.err.print(this.toString() + " 左子节点已经存在元素 " + this.left.toString());
46             System.err.print(" 不能再插入 " + node.toString() + "
");
47             return false;
48         }
49         this.left = node;
50         node.parent = this;
51         return true;
52     }
53 
54     /** 工具:插入右节点 */
55     public boolean appendRight(Node node) {
56 
57         if (node == NIL) {
58             System.err.println("添加节点不能为null");
59             return false;
60         }
61         if (this.right != NIL) {
62             System.err.print(this.toString() + " 右子节点已经存在元素 " + this.right.toString());
63             System.err.print(" 不能再插入 " + node.toString() + "
");
64             return false;
65         }
66         this.right = node;
67         node.parent = this;
68         return true;
69     }
70 
71     @Override
72     public String toString() {
73         return "Node [color=" + color + ", val=" + val + "]";
74     }
75 
76 }

创建一个颜色枚举

1 enum Color {
2     RED, BlACK
3 }

 

左旋转

 1     /** 
 2      * 左旋操作
 3      *         pp                              |         pp
 4      *        /                                |         /
 5      *       p                                 |       x(旋转后节点)
 6      *     /                                  |     /    7      *    L     x(待旋转节点)        == >         |    p     CR
 8      *         /                              |   /   9      *        CL  CR                           |  L    CL
10      *                                         |
11      *  */
12     private void rotateLeft(Node node) {
13 
14         /* 如果不是根节点 父节点就不是NIL */
15         if (node != root) {
16 
17             Node parent = node.parent;
18             parent.right = node.left;
19             node.left = parent;
20             /* 原节点的父节点指向原父节点的父节点 */
21             node.parent = parent.parent;
22             /* 原节点的父节点指向原父节点 */
23             parent.parent = node;
24             /* 原父节点的父节点的子节点指向自己 */
25             if(node.parent != Node.NIL) {
26                 
27                 /* 原父节点在原祖父节点的左边 */
28                 if(node.parent.left == parent) {
29                     node.parent.left = node;
30                 }
31                 
32                 else {
33                     node.parent.right = node;
34                 }
35             }
36             /* 将原左子节点的父节点指向 原父节点 */
37             if(parent.right != Node.NIL) {
38                 parent.right.parent=parent;
39             }
40         }
41     }

 

 右旋转

 1     /** 
 2      * 右操作
 3      *         pp                              |         pp
 4      *        /                                |         /
 5      *       p                                 |       x(旋转后节点)
 6      *     /                                  |      /   7      *    x     R                == >          |     CL    p
 8      * (待旋转节点)                              |         /       
 9      *   /                                    |        CR   R  
10      * CL   CR                                 |
11      *                                         |
12      *  */
13     private void rotateRight(Node node) {
14 
15         /* 如果不是根节点 父节点就不熟NIL */
16         if (node != root) {
17 
18             Node parent = node.parent;
19             parent.left = node.right;
20             node.right = parent;
21             /* 原节点的父节点指向原祖父节点 */
22             node.parent = parent.parent;
23             /*  */
24             parent.parent = node;
25             /* 原父节点的父节点的子节点指向自己 */
26             if(node.parent != Node.NIL) {
27                 
28                 /* 原父节点在原祖父节点的左边 */
29                 if(node.parent.left == parent) {
30                     node.parent.left = node;
31                 }
32                 
33                 else {
34                     node.parent.right = node;
35                 }
36             }
37             /* 将原右子节点的父节点指向 原父节点 */
38             if(parent.left != Node.NIL) {
39                 parent.left.parent=parent;
40             }
41         }
42     }

 

 

插入节点代码

 1     public boolean insert(Node node) {
 2 
 3         if (node == null || node == Node.NIL || node.val == null) {
 4             System.err.println("插入 节点/值 不能为空");
 5             return false;
 6         }
 7 
 8         /*
 9          * 如果根节点是null 则将节点插入根节点 性质2: 根节点的颜色为黑色
10          */
11         if (this.root == Node.NIL) {
12             node.color = Color.BlACK;
13             this.root = node;
14             return true;
15         }
16 
17         /*
18          * 根据二叉查找树的特性 寻找新节点合适位置 插入节点颜色初始值为红色    */
19         node = new Node(node.val);
20         Node tmp = root;
21         while (true) {
22 
23             /* 如果node.val < tmp.val */
24             if (node.val.compareTo(tmp.val) < 0) {
25                 if (tmp.left == Node.NIL) {
26                     tmp.appendLeft(node);/* 插入节点 */
27                     break;
28                 }
29 
30                 tmp = tmp.left;
31             }
32 
33             /* 如果node.val > tmp.val */
34             else if (node.val.compareTo(tmp.val) > 0) {
35                 if (tmp.right == Node.NIL) {
36                     tmp.appendRight(node);/* 插入节点 */
37                     break;
38                 }
39 
40                 tmp = tmp.right;
41             }
42 
43             /* 否侧 node.val == tmp.val 插入失败 */
44             else {
45                 System.err.println("[ " + node.val + " ] 该值已存在");
46                 return false;
47             }
48         }
49 
50         /* 对插入的元素进行调整 */
51         this.insertFixup(node);
52 
53         return true;
54     }
1     /* 节点调整 */
2     private void insertFixup(Node node) {}

 

 

 

节点调整的代码

 1     /** 对插入元素进行调整 */
 2     private void insertFixup(Node node) {
 3 
 4         /*
 5          * if 屏蔽的条件 1. 插入节点不为NIL 2. 插入节点为根节点 只需要将颜色转为黑色即可(性质2) 3. 插入节点的父节点为黑色节点 直接插入
 6          * 不需要调整    */
 7         while (node != Node.NIL && node != root && node.parent.color != Color.BlACK) {
 8             Node parent = node.parent;
 9 
10             /* [一]、 调整节点在 父节点的左边 */
11             if (parent.left == node) {
12                 /* 获取祖父节点 */
13                 Node pp = parent.parent;
14                 /* 父节点在祖父节点的左边 */
15                 if (pp != Node.NIL && pp.left == parent) {
16 
17                     /*
18                      * 1) 父节点颜色 == 叔叔节点颜色 ==> 红色 此时只需要变换颜色
19                      */
20                     if (pp.left.color == pp.right.color) {
21                         pp.left.color = Color.BlACK;
22                         pp.right.color = Color.BlACK;
23                         pp.color = Color.RED;
24                     }
25 
26                     /*
27                      * 2) 父节点颜色 (红) != 如果叔叔节点(黑) 需要对其进行右旋转
28                      */
29                     else {
30                         this.rotateRight(parent);
31                         /* 改变一下颜色 */
32                         pp.color = Color.RED;
33                         parent.color = Color.BlACK;
34                     }
35 
36                 }
37 
38                 /* 3) 如果父节点在祖父节点的右边 先右旋再左旋 */
39                 else if(pp != Node.NIL && pp.right == parent){
40                     this.rotateRight(node);
41                     // 右旋之后节点变成了上面的对称结构 操作与其相似 在下面[二]解决
42                 }
43             }
44             
45             /* [二]、调整节点在 父节点的右边 
46              *  与上面的处理一样(对称) */
47             else {
48                 /* 获取祖父节点 */
49                 Node pp = parent.parent;
50                 /* 父节点在祖父节点的右边
51                  *     同时解决了 3) 的下半部分问题 */
52                 if (pp != Node.NIL && pp.right == parent) {
53 
54                     /*
55                      * 父节点颜色 == 叔叔节点颜色 ==> 红色 此时任然只需要变换颜色
56                      *  与 1) 一摸一样 代码没变
57                      */
58                     if (pp.left.color == pp.right.color) {
59                         pp.left.color = Color.BlACK;
60                         pp.right.color = Color.BlACK;
61                         pp.color = Color.RED;
62                     }
63 
64                     /*
65                      * 父节点颜色 (红) != 如果叔叔节点(黑) 需要对其进行左旋转
66                      *  与 2) 一摸一样 == 改变旋转方向
67                      */
68                     else {
69                         this.rotateLeft(parent);
70                         /* 改变一下颜色 */
71                         pp.color = Color.RED;
72                         parent.color = Color.BlACK;
73                     }
74                 }
75                 
76                 /*
77                  * 父节点在祖父节点的左边
78                  */
79                 else if(pp != Node.NIL && pp.left == parent){
80                     this.rotateLeft(node);
81                     // 此时变成了 [一]、的情况
82                 }
83             }
84             
85             /* 处理完当前节点 父节点可能会破化条件 所以处理父节点 */
86             node = parent;
87         }
88         
89         /* 重新赋值根节点 */
90         if(node.parent == Node.NIL) {
91             this.root = node;
92         }
93         /* 将根节点置为黑色 */
94         this.root.color = Color.BlACK;
95     }

 

添加一个打印的辅助方法

 1     public void print(Node node) {
 2 
 3         if (node == Node.NIL) {
 4             return;
 5         }
 6         System.out.println(
 7                 "[ 我是:" + node.val + ", 我的父元素是:" + node.parent + ", 左子节点:" + node.left + ", 右子节点:" + node.right + " ]");
 8         print(node.left);
 9         print(node.right);
10     }

 

测试一下

 1     public static void main(String[] args) {
 2 
 3         RBTree tree = new RBTree();
 4         for (int i = 0; i < 10; i++) {
 5             /* 产生0-19的 10个 随机数 */
 6             int n=(int) (Math.random() * 20);
 7             System.out.print(n+", ");
 8             tree.insert(new Node(n));
 9         }
10         System.out.println();
11         tree.print(tree.root);
12     }

技术分享图片

 

 本章源码

技术分享图片
  1 public class RBTree {
  2 
  3     /** 根节点 */
  4     private Node root = Node.NIL;
  5 
  6     /** 
  7      * 左旋操作
  8      *         pp                              |         pp
  9      *           /                                |         /
 10      *       p                                 |       x(旋转后节点)
 11      *     /                                  |     /    12      *    L     x(待旋转节点)        == >         |    p     CR
 13      *         /                              |   /   14      *        CL  CR                           |  L    CL
 15      *                                         |
 16      *  */
 17     private void rotateLeft(Node node) {
 18 
 19         /* 如果不是根节点 父节点就不是NIL */
 20         if (node != root) {
 21 
 22             Node parent = node.parent;
 23             parent.right = node.left;
 24             node.left = parent;
 25             /* 原节点的父节点指向原父节点的父节点 */
 26             node.parent = parent.parent;
 27             /* 原节点的父节点指向原父节点 */
 28             parent.parent = node;
 29             /* 原父节点的父节点的子节点指向自己 */
 30             if(node.parent != Node.NIL) {
 31                 
 32                 /* 原父节点在原祖父节点的左边 */
 33                 if(node.parent.left == parent) {
 34                     node.parent.left = node;
 35                 }
 36                 
 37                 else {
 38                     node.parent.right = node;
 39                 }
 40             }
 41             /* 将原左子节点的父节点指向 原父节点 */
 42             if(parent.right != Node.NIL) {
 43                 parent.right.parent=parent;
 44             }
 45         }
 46     }
 47 
 48     /** 
 49      * 右操作
 50      *         pp                              |         pp
 51      *           /                                |         /
 52      *       p                                 |       x(旋转后节点)
 53      *     /                                  |      /   54      *    x     R                == >          |     CL    p
 55      * (待旋转节点)                              |         /       
 56      *   /                                    |        CR   R  
 57      * CL   CR                                 |
 58      *                                         |
 59      *  */
 60     private void rotateRight(Node node) {
 61 
 62         /* 如果不是根节点 父节点就不熟NIL */
 63         if (node != root) {
 64 
 65             Node parent = node.parent;
 66             parent.left = node.right;
 67             node.right = parent;
 68             /* 原节点的父节点指向原祖父节点 */
 69             node.parent = parent.parent;
 70             /*  */
 71             parent.parent = node;
 72             /* 原父节点的父节点的子节点指向自己 */
 73             if(node.parent != Node.NIL) {
 74                 
 75                 /* 原父节点在原祖父节点的左边 */
 76                 if(node.parent.left == parent) {
 77                     node.parent.left = node;
 78                 }
 79                 
 80                 else {
 81                     node.parent.right = node;
 82                 }
 83             }
 84             /* 将原右子节点的父节点指向 原父节点 */
 85             if(parent.left != Node.NIL) {
 86                 parent.left.parent=parent;
 87             }
 88         }
 89     }
 90 
 91     public boolean insert(Node node) {
 92 
 93         if (node == null || node == Node.NIL || node.val == null) {
 94             System.err.println("插入 节点/值 不能为空");
 95             return false;
 96         }
 97 
 98         /*
 99          * 如果根节点是null 则将节点插入根节点 性质2: 根节点的颜色为黑色
100          */
101         if (this.root == Node.NIL) {
102             node.color = Color.BlACK;
103             this.root = node;
104             return true;
105         }
106 
107         /*
108          * 根据二叉查找树的特性 寻找新节点合适位置 插入节点颜色初始值为红色    */
109         node = new Node(node.val);
110         Node tmp = root;
111         while (true) {
112 
113             /* 如果node.val < tmp.val */
114             if (node.val.compareTo(tmp.val) < 0) {
115                 if (tmp.left == Node.NIL) {
116                     tmp.appendLeft(node);/* 插入节点 */
117                     break;
118                 }
119 
120                 tmp = tmp.left;
121             }
122 
123             /* 如果node.val > tmp.val */
124             else if (node.val.compareTo(tmp.val) > 0) {
125                 if (tmp.right == Node.NIL) {
126                     tmp.appendRight(node);/* 插入节点 */
127                     break;
128                 }
129 
130                 tmp = tmp.right;
131             }
132 
133             /* 否侧 node.val == tmp.val 插入失败 */
134             else {
135                 System.err.println("[ " + node.val + " ] 该值已存在");
136                 return false;
137             }
138         }
139 
140         /* 对插入的元素进行调整 */
141         this.insertFixup(node);
142 
143         return true;
144     }
145 
146     /** 对插入元素进行调整 */
147     private void insertFixup(Node node) {
148 
149         /*
150          * if 屏蔽的条件 1. 插入节点不为NIL 2. 插入节点为根节点 只需要将颜色转为黑色即可(性质2) 3. 插入节点的父节点为黑色节点 直接插入
151          * 不需要调整    */
152         while (node != Node.NIL && node != root && node.parent.color != Color.BlACK) {
153             Node parent = node.parent;
154 
155             /* [一]、 调整节点在 父节点的左边 */
156             if (parent.left == node) {
157                 /* 获取祖父节点 */
158                 Node pp = parent.parent;
159                 /* 父节点在祖父节点的左边 */
160                 if (pp != Node.NIL && pp.left == parent) {
161 
162                     /*
163                      * 1) 父节点颜色 == 叔叔节点颜色 ==> 红色 此时只需要变换颜色
164                      */
165                     if (pp.left.color == pp.right.color) {
166                         pp.left.color = Color.BlACK;
167                         pp.right.color = Color.BlACK;
168                         pp.color = Color.RED;
169                     }
170 
171                     /*
172                      * 2) 父节点颜色 (红) != 如果叔叔节点(黑) 需要对其进行右旋转
173                      */
174                     else {
175                         this.rotateRight(parent);
176                         /* 改变一下颜色 */
177                         pp.color = Color.RED;
178                         parent.color = Color.BlACK;
179                     }
180 
181                 }
182 
183                 /* 3) 如果父节点在祖父节点的右边 先右旋再左旋 */
184                 else if(pp != Node.NIL && pp.right == parent){
185                     this.rotateRight(node);
186                     // 右旋之后节点变成了上面的对称结构 操作与其相似 在下面[二]解决
187                 }
188             }
189             
190             /* [二]、调整节点在 父节点的右边 
191              *  与上面的处理一样(对称) */
192             else {
193                 /* 获取祖父节点 */
194                 Node pp = parent.parent;
195                 /* 父节点在祖父节点的右边
196                  *     同时解决了 3) 的下半部分问题 */
197                 if (pp != Node.NIL && pp.right == parent) {
198 
199                     /*
200                      * 父节点颜色 == 叔叔节点颜色 ==> 红色 此时任然只需要变换颜色
201                      *  与 1) 一摸一样 代码没变
202                      */
203                     if (pp.left.color == pp.right.color) {
204                         pp.left.color = Color.BlACK;
205                         pp.right.color = Color.BlACK;
206                         pp.color = Color.RED;
207                     }
208 
209                     /*
210                      * 父节点颜色 (红) != 如果叔叔节点(黑) 需要对其进行左旋转
211                      *  与 2) 一摸一样 == 改变旋转方向
212                      */
213                     else {
214                         this.rotateLeft(parent);
215                         /* 改变一下颜色 */
216                         pp.color = Color.RED;
217                         parent.color = Color.BlACK;
218                     }
219                 }
220                 
221                 /*
222                  * 父节点在祖父节点的左边
223                  */
224                 else if(pp != Node.NIL && pp.left == parent){
225                     this.rotateLeft(node);
226                     // 此时变成了 [一]、的情况
227                 }
228             }
229             
230             /* 处理完当前节点 父节点可能会破化条件 所以处理父节点 */
231             node = parent;
232         }
233         
234         /* 重新赋值根节点 */
235         if(node.parent == Node.NIL) {
236             this.root = node;
237         }
238         /* 将根节点置为黑色 */
239         this.root.color = Color.BlACK;
240     }
241 
242     public void print(Node node) {
243 
244         if (node == Node.NIL) {
245             return;
246         }
247         System.out.println(
248                 "[ 我是:" + node.val + ", 我的父元素是:" + node.parent + ", 左子节点:" + node.left + ", 右子节点:" + node.right + " ]");
249         print(node.left);
250         print(node.right);
251     }
252 
253     public static void main(String[] args) {
254 
255         RBTree tree = new RBTree();
256         /* 产生0-19的 10个 随机数 */
257         for (int i = 0; i < 10; i++) {
258             int n=(int) (Math.random() * 20);
259             System.out.print(n+", ");
260             tree.insert(new Node(n));
261         }
262         System.out.println();
263         tree.print(tree.root);
264     }
265 
266 }
267 
268 /**
269  * 红黑树节点
270  */
271 class Node {
272 
273     /** 创建一个特殊节点代替null节点 方便给他附上颜色 */
274     public static Node NIL = new Node(Color.BlACK, null, null, null, null);
275 
276     /** 节点颜色 */
277     public Color color;
278     /** 存储值 */
279     public Integer val;
280     /** 父节点 */
281     public Node parent;
282     /** 左节点 */
283     public Node left;
284     /** 右节点 */
285     public Node right;
286 
287     public Node() {
288     }
289 
290     public Node(Integer val) {
291         this(Color.RED, val, NIL, NIL, NIL);
292         this.val = val;
293     }
294 
295     public Node(Color color, Integer val, Node parent, Node left, Node right) {
296         super();
297         this.color = color;
298         this.val = val;
299         this.parent = parent;
300         this.left = left;
301         this.right = right;
302     }
303 
304     /** 工具:插入左节点 */
305     public boolean appendLeft(Node node) {
306 
307         if (node == NIL) {
308             System.err.println("添加节点不能为null");
309             return false;
310         }
311         if (this.left != NIL) {
312             System.err.print(this.toString() + " 左子节点已经存在元素 " + this.left.toString());
313             System.err.print(" 不能再插入 " + node.toString() + "
");
314             return false;
315         }
316         this.left = node;
317         node.parent = this;
318         return true;
319     }
320 
321     /** 工具:插入右节点 */
322     public boolean appendRight(Node node) {
323 
324         if (node == NIL) {
325             System.err.println("添加节点不能为null");
326             return false;
327         }
328         if (this.right != NIL) {
329             System.err.print(this.toString() + " 右子节点已经存在元素 " + this.right.toString());
330             System.err.print(" 不能再插入 " + node.toString() + "
");
331             return false;
332         }
333         this.right = node;
334         node.parent = this;
335         return true;
336     }
337 
338     @Override
339     public String toString() {
340         return "Node [color=" + color + ", val=" + val + "]";
341     }
342 
343 }
344 
345 enum Color {
346     RED, BlACK
347 }
View Code

 

 


未完待续

 

以上是关于数据结构 -- 红黑树精解的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之红黑树

红黑树探索笔记

码图并茂红黑树

数据结构-红黑树

数据结构——红黑树

红黑树探索