資料結構 -- 紅黑樹精解
阿新 • • 發佈:2019-01-14
普通二叉查詢樹
紅黑樹
建立節點
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 staticNode 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
未完待續