1. 程式人生 > >資料結構 -- 紅黑樹精解

資料結構 -- 紅黑樹精解

普通二叉查詢樹

 

 

紅黑樹

 建立節點

 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() + "\n"); 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() + "\n"); 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() + "\n");
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() + "\n");
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

 

 


未完待續