数据结构 -- 红黑树精解
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 }
未完待续
以上是关于数据结构 -- 红黑树精解的主要内容,如果未能解决你的问题,请参考以下文章