一、概述
1、介紹
紅黑樹是一種自平衡的排序二叉樹,常用於關聯陣列、字典,在各種語言的底層實現中被廣泛應用,Java 的 TreeMap 和 TreeSet 就是基於紅黑樹實現的,在 JDK 8 的HashMap中也有應用。
紅黑樹是在排序二叉樹的基礎上定義的,且還要滿足以下性質(重要):(請務必先學習排序二叉樹,排序二叉樹先看這篇 二叉樹)
(1)每個結點要麼是黑色,要麼是紅色。
(2)根結點是黑色。
(3)所有葉子結點都是黑色(這裡說的葉子結點指 NIL 結點,空結點,即:空結點為黑色)。
(4)從任意一個結點到其所有葉子結點,所經過的黑色結點數目必須相等。
(5)所有紅色結點的兩個孩子結點必須是黑色(即,紅色結點不能連續)。
程式碼示例:紅黑樹-樹結點結構
1 protected static class RBNode<T extends Comparable<T>> { 2 private T value; 3 // 預設為 紅色 結點 4 private boolean red = true; 5 6 private RBNode<T> left; 7 private RBNode<T> right; 8 private RBNode<T> parent; 9 }
樹結點關係
2、左旋(重要)
動圖:
程式碼示例:對 node 左旋
1 // 左旋 2 private void leftRotate(RBNode<T> node) { 3 if (node == null) { 4 return; 5 } 6 final RBNode<T> p = node.parent; 7 8 // 左旋. 應該認為 temp 不為null 9 final RBNode<T> temp = node.right; 10 node.right = temp.left; 11 if (temp.left != null) { 12 // 該結點可能不存在 13 temp.left.parent = node; 14 } 15 16 temp.left = node; 17 node.parent = temp; 18 19 // 旋轉完成.修正根結點與父結點 20 // 1.node為根結點 21 if (node == root) { 22 root = temp; 23 temp.parent = null; 24 return; 25 } 26 27 // 2.node不為根結點 28 // node 為父結點的右孩子 29 if (node == p.right) { 30 p.right = temp; 31 } else { 32 p.left = temp; 33 } 34 temp.parent = p; 35 }
3、右旋(重要)
動圖:
程式碼示例:對 node 右旋
1 // 右旋 2 private void rightRotate(RBNode<T> node) { 3 if (node == null) { 4 return; 5 } 6 7 final RBNode<T> p = node.parent; 8 9 // 右旋. 應該認為 temp 不為null 10 final RBNode<T> temp = node.left; 11 node.left = temp.right; 12 if (temp.right != null) { 13 // 該結點可能不存在 14 temp.right.parent = node; 15 } 16 17 temp.right = node; 18 node.parent = temp; 19 20 // 旋轉完成.修正根結點與父結點 21 // 1.node為根結點 22 if (node == root) { 23 root = temp; 24 temp.parent = null; 25 return; 26 } 27 28 // 2.node不為根結點 29 // node 為父結點的右孩子 30 if (node == p.right) { 31 p.right = temp; 32 } else { 33 p.left = temp; 34 } 35 temp.parent = p; 36 }
二、插入
由於樹的左子樹和右子樹是對稱的,所以只討論一邊的情況即可。插入的原則滿足排序二叉樹的規則。而紅黑樹的插入還要滿足紅黑樹的性質,所以插入完成後,還要對紅黑樹進行調整。調整的原則(重要):
(1)按排序二叉樹的插入規則插入新結點(newNode)。
(2)新插入的結點預設為紅色。
(3)若 newNode 為根結點,則變為黑色,插入完畢。
(4)若 newNode 不為根結點,若其父結點為黑色,插入完畢。
(5)若 newNode 不為根結點,若其父結點為紅色,則按下面的情況討論(下面也主要討論這種情況)。
以 {7, 3, 10, 1(5)} 為例,新增 {7, 3, 10} 的結果很容易理解。
插入1(5)時,
情況一:newNode(1或5) 的叔叔結點(10)存在且為紅色。
調整方案:父結點(3)與叔叔結點(10)變為黑色;祖父結點變為紅色;新增結點 newNode 指向祖父結點(7),做遞迴的調整(這裡newNode == root,則變為黑色即可)。
情況二:newNode(1或5) 的叔叔結點(10)不存在,或者為黑色。
調整方案:分為左左、左右、右右、右左討論。(下面的討論中,不妨將叔叔結點畫為黑色)
1、左左
左左:newNode == 祖父結點的左孩子的左孩子。
調整方案:先對祖父結點(7)右旋;父結點變為黑色,祖父結點變為紅色。
2、左右
左右:newNode == 祖父結點的左孩子的右孩子。
調整方案:先對父結點(3)左旋;後續調整同"左左"的方案。(需注意newNode的位置不同)
3、右右(與左左對稱)
右右:newNode == 祖父結點的右孩子的右孩子。
調整方案:先對祖父結點(7)左旋;父結點變為黑色,祖父結點變為紅色。
4、右左
右左:newNode == 祖父結點的右孩子的左孩子。
調整方案:先對父結點(10)右旋;後續調整同"右右"的方案。(需注意newNode的位置不同)
5、總結
程式碼示例:完整的紅黑樹插入及調整
1 public class RBTree<T extends Comparable<T>> { 2 // 根結點 3 private RBNode<T> root; 4 5 public RBTree() { 6 } 7 8 public RBTree(T[] arr) { 9 if (arr == null || arr.length == 0) { 10 return; 11 } 12 13 for (T i : arr) { 14 this.add(i); 15 } 16 } 17 18 // 新增結點 19 public void add(T value) { 20 RBNode<T> newNode = new RBNode<>(value); 21 if (root == null) { 22 root = newNode; 23 newNode.red = false; 24 return; 25 } 26 27 // 1.新增 28 this.add(root, newNode); 29 30 // 2.調整 31 this.fixUp(newNode); 32 } 33 34 private void fixUp(RBNode<T> newNode) { 35 if (newNode == root) { 36 newNode.red = false; 37 return; 38 } 39 40 // newNode 的父結點為黑色 41 if (!newNode.parent.red) { 42 return; 43 } 44 45 /* 1.newNode 的叔叔結點存在且為紅色。*/ 46 final RBNode<T> uncle = newNode.getUncle(); 47 if (uncle != null && uncle.red) { 48 newNode.parent.red = false; 49 uncle.red = false; 50 51 newNode.parent.parent.red = true; 52 this.fixUp(newNode.parent.parent); 53 } else { 54 /* 2.newNode 的叔叔結點不存在,或者為黑色。*/ 55 final RBNode<T> grandFather = newNode.getGrandFather(); 56 // 2.1左左 57 if (newNode == grandFather.left.left) { 58 this.rightRotate(grandFather); 59 newNode.parent.red = false; 60 grandFather.red = true; 61 } 62 // 2.2左右 63 else if (newNode == grandFather.left.right) { 64 this.leftRotate(newNode.parent); 65 this.rightRotate(grandFather); 66 newNode.red = false; 67 grandFather.red = true; 68 } 69 // 2.3右右 70 else if (newNode == grandFather.right.right) { 71 this.leftRotate(grandFather); 72 newNode.parent.red = false; 73 grandFather.red = true; 74 } 75 // 2.4右左 76 else if (newNode == grandFather.right.left) { 77 this.rightRotate(newNode.parent); 78 this.leftRotate(grandFather); 79 newNode.red = false; 80 grandFather.red = true; 81 } 82 } 83 } 84 85 // 按 排序二叉樹 的規則插入結點 86 private void add(RBNode<T> root, RBNode<T> newNode) { 87 if (newNode.value.compareTo(root.value) <= 0) { 88 if (root.left == null) { 89 root.left = newNode; 90 newNode.parent = root; 91 } else { 92 this.add(root.left, newNode); 93 } 94 } else { 95 if (root.right == null) { 96 root.right = newNode; 97 newNode.parent = root; 98 } else { 99 this.add(root.right, newNode); 100 } 101 } 102 } 103 104 // 左旋 105 private void leftRotate(RBNode<T> node) { 106 if (node == null) { 107 return; 108 } 109 final RBNode<T> p = node.parent; 110 111 // 左旋. 應該認為 temp 不為null 112 final RBNode<T> temp = node.right; 113 node.right = temp.left; 114 if (temp.left != null) { 115 // 該結點可能不存在 116 temp.left.parent = node; 117 } 118 119 temp.left = node; 120 node.parent = temp; 121 122 // 旋轉完成.修正根結點與父結點 123 // 1.node為根結點 124 if (node == root) { 125 root = temp; 126 temp.parent = null; 127 return; 128 } 129 130 // 2.node不為根結點 131 // node 為父結點的右孩子 132 if (node == p.right) { 133 p.right = temp; 134 } else { 135 p.left = temp; 136 } 137 temp.parent = p; 138 } 139 140 // 右旋 141 private void rightRotate(RBNode<T> node) { 142 if (node == null) { 143 return; 144 } 145 146 final RBNode<T> p = node.parent; 147 148 // 右旋. 應該認為 temp 不為null 149 final RBNode<T> temp = node.left; 150 node.left = temp.right; 151 if (temp.right != null) { 152 // 該結點可能不存在 153 temp.right.parent = node; 154 } 155 156 temp.right = node; 157 node.parent = temp; 158 159 // 旋轉完成.修正根結點與父結點 160 // 1.node為根結點 161 if (node == root) { 162 root = temp; 163 temp.parent = null; 164 return; 165 } 166 167 // 2.node不為根結點 168 // node 為父結點的右孩子 169 if (node == p.right) { 170 p.right = temp; 171 } else { 172 p.left = temp; 173 } 174 temp.parent = p; 175 } 176 177 // 中序遍歷 178 public void infixOrder() { 179 this.infixOrder(root); 180 } 181 182 private void infixOrder(RBNode<T> root) { 183 if (root != null) { 184 this.infixOrder(root.left); 185 System.out.print("-->" + root.value + ":" + (root.red ? "紅" : "黑")); 186 this.infixOrder(root.right); 187 } 188 } 189 190 /** 191 * 紅黑樹 樹結點結構 192 */ 193 protected static class RBNode<T extends Comparable<T>> { 194 private T value; 195 // 預設為 紅色 結點 196 private boolean red = true; 197 198 private RBNode<T> left; 199 private RBNode<T> right; 200 private RBNode<T> parent; 201 202 public RBNode() { 203 } 204 205 public RBNode(T value) { 206 this.value = value; 207 } 208 209 // 返回結點的度 210 public int getDegree() { 211 if (this.left == null && this.right == null) { 212 return 0; 213 } 214 215 if ((this.left != null && this.right == null) || (this.left == null && this.right != null)) { 216 return 1; 217 } 218 219 return 2; 220 } 221 222 public RBNode<T> getUncle() { 223 final RBNode<T> grandFather = this.parent.parent; 224 final RBNode<T> parent = this.parent; 225 226 if (parent == grandFather.left) { 227 return grandFather.right; 228 } 229 230 if (parent == grandFather.right) { 231 return grandFather.left; 232 } 233 234 return null; 235 } 236 237 public RBNode<T> getGrandFather() { 238 return this.parent.parent; 239 } 240 241 @Override 242 public String toString() { 243 return "RBNode{" + 244 "value=" + value + 245 ", red=" + red + 246 '}'; 247 } 248 } 249 }
程式碼示例:測試
1 public class Main { 2 public static void main(String[] args) { 3 Integer[] integers = {15, 7, 45, 3, 10, 25, 55, 1, 5, 75}; 4 RBTree<Integer> tree = new RBTree<>(integers); 5 6 tree.infixOrder(); 7 } 8 } 9 10 // 結果 11 -->1:紅-->3:黑-->5:紅-->7:紅-->10:黑-->15:黑-->25:黑-->45:紅-->55:黑-->75:紅
最後,推薦一個線上構建紅黑樹的地址:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html 用於讀者驗證上述程式碼的結果。上述測試案例構建的紅黑樹為:
三、刪除
見下一篇。