红黑树

  AVL自平衡二叉树在教科书上比较常见,因为是最先提出的自平衡二叉树,自然是学术价值比较的高,但是目前工业环境觉得名为红黑二叉树RBT(Red-Black Tree)的自平衡二叉树使用的更为的广泛,比如C++标准库中的有序容器(std::set、std::map),Linux内核中的很多数据结构等,都是用的RBTree来维护管理的。

  当看完RBTree后发现,其实相对来说AVL自平衡树比RBTree更加的平衡,理论上访问效果也会更好,但是为此AVL自平衡树在插入、删除修改树结构的时候,会引入更多的旋转操作来保持平衡,所以对于经常需要添加、删除的高动态数据来说,维护这种数据结构的代价显得十分高昂,而RBTree对于树的高度限制相对要宽松的多,等于是在牺牲了部分完全性(平衡性)的条件下,以换取插入、删除操作时少量的旋转操作(但是一调整起来复杂的情况麻烦的要死~~~)。

一、红黑二叉树简介

  说到红黑二叉树,不得不先请出红黑树的基本法则,虽然简单,但是维护起来还是挺复杂的:

  (1). 节点都有颜色标记,且只能是红色或黑色。

  (2). 根是黑色。

  (3). 所有叶子都是黑色(叶子是NIL/nill_节点,不保存实际的数据)。

  (4). 每个红色节点必须有两个黑色的子节点,也可以表述为从每个叶子到根的所有路径上不能有两个连续的红色节点。

  (5). 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

  上述这些条件中,(1)(3)是很容易遵守的,(2)只有确实在操作到根节点的时候需要注意调整一下就行了,而(4)(5)是维持红黑树结构中常常会用到的准则。

  之所以要有上面的条件和规则,就是为了这么一个保证:从根到任何一个叶子,最好的情况是整条路径全部都是黑色,假设为N,最坏的情况是黑色和红色交替的情况,那也不会超过2N,因此红黑二叉树对操作的复杂度作出了最差的保证。而维护这种数据结构,需要少量的颜色变更和不超过三次树旋转(对于插入操作最多是两次),虽然插入和删除很复杂,但操作时间复杂度仍可以保持为O(log n)而不会因为元素的数目增加而性能恶化。下面是一个典型的红黑二叉树的样子。

    红黑树_第1张图片

二、红黑二叉树实现

  此处还需要事先强调一下,红黑树再复杂也是一个基本的BST,所以和AVL自平衡二叉树一样,所有的插入和删除操作,也是先按照操作BST的基本方式进行插入、删除,然后再检查是否平衡而做出相应调整,RBTree的调整操作包括着色重绘和旋转操作。

2.1 插入操作

  规定,新增加的节点着色必须是红色。插入操作分以下情况:

  (1). 如果此时还是空的红黑树,则该节点为根节点,将其重绘为黑色,然后返回;否则进行步骤(2)

  (2). 根据BST递归查找,找到可以插入的位置时候,创建新节点后进行BST插入(更新parent_、left_、right_指针域),然后进行步骤(3)

  (3). 如果父亲节点P是黑色,此时红色节点作为孩子是允许的,红色节点的加入不会影响黑色路径的计数,原先父亲的叶子节点黑色由新插入节点的叶子节点继承,对于(4)(5)规则没有任何影响,操作完成直接返回;否则进行步骤(4)

  (4). 父亲节P点是红色的,如果此时叔父节点U也是红色的,而且此刻确定祖父节点G是黑色的,进行如下操作:将祖父节点G重绘为红色,将父亲P和叔父节U点重绘为黑色,此处操作虽然子树平衡了,但是修改了祖父节点G可能导致祖父节点G和其他节点不平衡,以祖父节点G为参数重新进入步骤(3)检查递归;否则进行(5)

    红黑树_第2张图片

  (5). 此时父节点P是红色、叔父节点U是黑色、祖父节点G是黑色、新增节点N是红色,根据插入节点N是父亲节点G的左孩子还是右孩子,以及父亲节点P是祖父节点G的左孩子还是右孩子分别组合,一共形成四种情况,依次处理

    a. 如果祖父节点G、父亲节点P、插入节点N在一条直线上,即插入节点N是父亲节点P的左孩子且父节点P是祖父节点G的左孩子,或者镜像情况下插入节点N是父亲节点P的右孩子且父亲节点P是祖父节点G的右孩子,此时只需将祖父节点G改为红色,父亲节点P改为黑色,然后以祖父亲节点G为中心做一个相反方向的旋转就可以了;

    红黑树_第3张图片

    b. 如果祖父节点G、父亲节点P、插入节点N不在一条直线上,此时需要以父亲节点P为中心,事先进行一次旋转使得祖父节点、父亲节点、插入节点三者在一条直线上,然后就可以按照a的情况所对应的步骤进行处理了;

    至此,红黑树的插入操作完成。

    红黑树_第4张图片

2.2 删除操作

  红黑树的删除操作算是比较复杂的数据结构操作了,分出的情况比较的多,而且操作过程中涉及到的亲戚节点也比较多。

  此处需要说明的是,所有BST的删除操作,都可以转换看作是单个子树的删除操作,因为删除的节点只可能有三种情况:叶子节点,这时候可以将任意一个NIL节点看做单个子树;含有一个子树的节点;含有两个子树的节点,此时按照BST的删除操作,还是会寻找一个左子树最大值或者右子树最小值替换他,并将替换节点重绘成删除节点的颜色,然后问题实质上就转化成替换节点的删除了,而替换节点不可能有两个子树的情况。

  (1). 如果整个RBTree为空,直接返回;否则进入(2)

  (2). 查找当前节点是否待删除节点,如果不是递归进行左树或者右树删除,如果遍历完了还未找到直接返回,否则当前就是待删除节点,进入步骤(3)

  (3). 如果当前待删除节点的右子树为空,表明无法找到一个用于替换的右子树最小值节点,直接进入步骤(4),否则查找右子数最小节点,和当前节点进行数据部分的交换(而位置关系、着色得以保留),然后将待删除节点设置为替换节点,进入步骤(4),至此我们找到了需要真正进行删除操作的节点N

  (4). 寻找当前删除节点node的非NIL子树(如果当前节点两个孩子都是NIL那就选随便选一个假设为非NIL)作为child,然后判断当前节点是否是根节点而需要做特殊处理(此处不展开这种情况,只考虑一般的删除节点),对于一般的删除情况,通过将node父节点指针引用到child而使用child顶替当前删除节点node,node的引用计数减少(后续会被自动析构释放),而且此时如果删除掉的节点node是红色的,那么表明被删除节点的父亲和孩子child都是黑色的,最主要的是删除一个红色节点不影响RBTree的规则,此处就可以直接返回了;否则进入步骤(5)

  (5). 此处删除掉的节点node是黑色的,而如果替换的child是红色的,那么将孩子重绘为黑色,这样原来通过黑色删除节点的路线现在都由孩子去作为黑色节点顶替了,红黑树的特性没有被破坏,直接返回;否则进入步骤(6)

  (6). 进入步骤(6)就是传说中最复杂的double black情况了,在所有讨论之前这里先声明,到达这里节点的删除工作已经完成,接下来的都是调整工作了。我们将主角命名为N,其本质就是(4)操作中顶替的child节点,原先的祖父节点现在是父亲节点,原先的叔父节点现在是兄弟节点,且此时节点N是黑色的。检测兄弟节点S如果是红色,那么父亲节点P肯定是黑色,此时重绘父亲节点P成红色,重绘兄弟节点S为黑,并且如果N是父亲节点P的左儿子,则以父亲节点P为中心进行左旋操作,否则进行右旋操作,经过这一步调整,N有了一个黑色的兄弟节点和一个红色的父亲节点,但是N和现在兄弟节点S原来的儿子(现在的兄弟)挂着子树上黑色节点的数目肯定不一样,子树是不平衡的,所以还需要继续进行下面的处理,进入步骤(7)。

    红黑树_第5张图片

  (7). 无论是原生的还是经过步骤(6)修改得到的,此处兄弟节点S是黑色、N是黑色,然后检查兄弟节点S的两个孩子的颜色,如果兄弟节点S的两个孩子都是黑色,那么就根据父亲节点P的颜色进行讨论:

    a.如果此时父亲节点P的颜色是红色,则重绘兄弟节点S成红色、重绘父亲节点P成黑色,通过这样后原先删除掉的黑色节点就由父亲节点P补偿回来了,而兄弟节点S的整个分支没有改变,满足红黑树条件,就直接返回;

    红黑树_第6张图片

    b.如果父亲节点P的颜色是黑色,则重绘兄弟节点S成红色,此时虽然得到的整个子树是平衡的,但是原先经过兄弟节点S的子树都减少了一个黑色,此处需要以父亲节点P为参数步入步骤(4)重新调整;

    红黑树_第7张图片

    如果兄弟节点S的两个孩子不都是黑色,此时步入步骤(8)

  (8). 此时兄弟节点S是黑色、N是黑色,而且兄弟节点S的两个孩子至少有一个是红色的,但是父亲节点P多次递归已经不确定颜色了,然后当Parnet-Sibling-r_child不在一条线上面时(此时兄弟节点S的孩子由一个红色和一个黑色构成的时候,假设红色孩子记为r_child),需要先旋转成一条线,同时进行颜色的修正,把兄弟节点S改成红色且r_child改成黑色,经过这个旋转后的子树是满足二叉树性质的,但是N和新的兄弟节点S不平衡(本身这个操作不会涉及到N和父亲节点P),而且这个不平衡的情况刚好会fall through到下面步骤(9)的情况处理;而如果Parnet-Sibling-r_child在一条线上面(这其实就是前面旋转着色后的结果),直接进入步骤(9)处理

    红黑树_第8张图片

  (9). 此时兄弟节点S是黑色,且依次挂了红色孩子、黑色孙子一条线的子树,操作是通过以父亲节点P为中心进行旋转,让原来的兄弟节点S代替父亲节点P的颜色,同时重绘原来父亲节点P成黑色,重绘原来兄弟节点S的孩子成黑色。

  由于原先的父亲节点P和现在兄弟节点S的颜色是不确定的,无非做两种情况进行讨论:a. 父亲节点P原先是黑色的;b. 父亲节点P原先是红色的,看图可以直接分析出来,修改之后这条子树到其所有子叶的黑色节点数目和原先都是一样的,满足红黑树条件,删除结束。

    红黑树_第9张图片

三、代码实现

  1 import java.util.List;
  2 
  3 /**
  4  * 红黑树 
5 * 1)每个节点要么是红色要么是黑色 2)根节点是黑色的 3)每个叶子节点(NIL)是黑色 4)红色节点的的子节点都是黑色的 6 * 5)对每个节点,从该节点到其后代叶子节点的简单路径上,均包含数目相同的黑色节点 7 * 8 * 通常我们认为树末梢的节点还有两个为空的节点,这些空节点是黑色的,所以不必检测第三条 9 * 10 */ 11 public class RedBlackTree extends BinarySearchTree { 12 private static final boolean RED = true; 13 private static final boolean BLACK = false; 14 15 @Override 16 public BSTNode insert(K key, V value) { 17 BSTNode newNode = super.insert(key, value); 18 fixAfterInsert2(newNode.parent, newNode); 19 colorBlack(root);// 根节点染黑 20 size++; 21 return newNode; 22 } 23 24 /** 25 * 保持树的平衡,这里采用模式匹配的写法: y x z A B C D 26 * 27 * @param parent 新增节点的父节点 28 * @param newNode 新增节点 29 * @return 30 */ 31 private void fixAfterInsert(BSTNode parent, BSTNode newNode) { 32 if (parent == null) { 33 root = newNode; 34 return; 35 } 36 if (colorOf(parent) == RED && colorOf(newNode) == RED) { 37 // 虚位以待,把四种情况的ABC和xyz定好,然后统一处理 38 BSTNode A = null, B = null, C = null, D = null, x = null, y = null, z = null; 39 // case 1 左左 40 /* 41 * 42 * z y D x C A B 43 */ 44 if (parent.isLeftChild && newNode.isLeftChild) { 45 x = newNode; 46 A = x.left; 47 B = x.right; 48 49 y = parent; 50 C = y.right; 51 52 z = y.parent; 53 D = z.right; 54 55 changePeek(y, z); 56 } 57 // case 2 右右 58 /* 59 * 60 * x A y B z C D --> A x B y C z D 61 */ 62 else if (!parent.isLeftChild && !newNode.isLeftChild) { 63 z = newNode; 64 C = z.left; 65 D = z.right; 66 67 y = z.parent; 68 B = y.left; 69 70 x = y.parent; 71 A = x.left; 72 73 changePeek(y, x); 74 } 75 // case 3 左右 76 else if (parent.isLeftChild && !newNode.isLeftChild) { 77 y = newNode; 78 B = y.left; 79 C = y.right; 80 81 x = y.parent; 82 A = x.left; 83 84 z = x.parent; 85 D = z.right; 86 87 changePeek(y, z); 88 } 89 // case 4 右左 90 else if (!parent.isLeftChild && newNode.isLeftChild) { 91 y = newNode; 92 B = y.left; 93 C = y.right; 94 95 z = y.parent; 96 D = z.right; 97 98 x = z.parent; 99 A = x.left; 100 101 changePeek(y, x); 102 } 103 // ------------------统一变为一种形式,换父子链接并染色---------------------- 104 x.parent = y; 105 z.parent = y; 106 y.left = x; 107 y.right = z; 108 x.left = A; 109 if (A != null) { 110 A.parent = x; 111 A.isLeftChild = true; 112 } 113 x.right = B; 114 if (B != null) { 115 B.parent = x; 116 B.isLeftChild = false; 117 } 118 z.left = C; 119 z.right = D; 120 if (C != null) { 121 C.parent = z; 122 C.isLeftChild = true; 123 } 124 if (D != null) { 125 D.parent = z; 126 D.isLeftChild = false; 127 } 128 x.isLeftChild = true; 129 z.isLeftChild = false; 130 colorBlack(x); 131 colorBlack(z); 132 colorRed(y); 133 // 递归向上追溯 134 fixAfterInsert(y.parent, y); 135 } 136 137 } 138 139 private void fixAfterInsert2(BSTNode parent, BSTNode newNode) { 140 if (parent == null) { 141 root = newNode; 142 return; 143 } 144 if (colorOf(parent) == RED) { 145 // uncle存在且为红色 146 BSTNode grand = parent.parent; 147 BSTNode uncle = parent.isLeftChild ? grand.right : grand.left; 148 // uncle为红 149 if (uncle != null && colorOf(uncle) == RED) { 150 colorRed(grand); 151 colorBlack(parent); 152 colorBlack(uncle); 153 fixAfterInsert2(grand.parent, grand); 154 } else {// uncle为空=uncle为黑 155 if (parent.isLeftChild && newNode.isLeftChild) {// 左左型 156 colorRed(grand); 157 colorBlack(parent); 158 rightRotate(grand, parent); 159 } else if (parent.isLeftChild && !newNode.isLeftChild) {// 左右型 160 leftRotate(parent, newNode); 161 colorRed(grand); 162 colorBlack(newNode); 163 rightRotate(grand, newNode); 164 } else if (!parent.isLeftChild && !newNode.isLeftChild) {// 右右型 165 colorRed(grand); 166 colorBlack(parent); 167 leftRotate(grand, parent); 168 } else {// 右左型 169 rightRotate(parent, newNode); 170 colorRed(grand); 171 colorBlack(newNode); 172 leftRotate(grand, newNode); 173 } 174 } 175 } 176 } 177 178 /** 179 * 切换顶点,设施newPeek为新顶点 180 * 181 * @param newPeek 182 * 新顶点 183 * @param oldPeek 184 * 旧顶点 185 */ 186 private void changePeek(BSTNode newPeek, BSTNode oldPeek) { 187 newPeek.parent = oldPeek.parent; 188 newPeek.isLeftChild = oldPeek.isLeftChild; 189 if (oldPeek.parent != null) { 190 if (oldPeek.isLeftChild) 191 oldPeek.parent.left = newPeek; 192 else 193 oldPeek.parent.right = newPeek; 194 } else { 195 root = newPeek; 196 } 197 } 198 199 private void colorRed(BSTNode node) { 200 if (null != node) 201 node.isRed = true; 202 } 203 204 private void colorBlack(BSTNode node) { 205 if (null != node) 206 node.isRed = false; 207 } 208 209 /** 210 * 红黑树删除及修复 1、双支转单支 2、删除D,并顶替N 3、D为黑,需修复 4、N为红,很简单(N变黑即可) N为黑,系列复杂的修复 211 * 212 * @param key 213 */ 214 @Override 215 public void remove(K key) { 216 BSTNode toDelete = lookupNode(key); 217 if (toDelete == null) 218 return; 219 size--; 220 221 // 如果是严格的内部节点,拷贝后继元素的内容到待删节点,然后toDelete指向后继,合并到后面一同处理 222 if (toDelete.left != null && toDelete.right != null) { 223 BSTNode s = successor(toDelete);// 后继右子树的最左端 224 toDelete.key = s.key; 225 toDelete = s; // p指向其后继,是待删除的 226 } // toDelete has 2 children 227 228 // 此时,toDelete一定是单支,或者是叶子 229 // 用于顶替待删节点的 230 BSTNode N = (toDelete.left != null ? toDelete.left : toDelete.right); 231 // N是用来顶替toDelete的 232 if (N != null) { 233 // -------这一段是顶替操作----------- 234 // Link N to parent 235 N.parent = toDelete.parent; 236 if (toDelete.parent == null) { 237 root = N; 238 colorBlack(N); 239 } else if (toDelete.isLeft()) { // p是左孩子 240 toDelete.parent.left = N; 241 N.isLeftChild = true; 242 } else { // p是右孩子 243 toDelete.parent.right = N; 244 N.isLeftChild = false; 245 } 246 247 // Null out links so they are OK to use by fixAfterDeletion. 248 toDelete.left = toDelete.right = toDelete.parent = null; 249 // -------这一段是顶替操作 end----------- 250 251 // toDelete为黑才需要修复 252 if (colorOf(toDelete) == BLACK) 253 fixAfterDeletion(N); 254 } // toDelete has 1 children 255 else if (toDelete.parent == null) { // toDelete是叶子:1.它是根—— if it is the 256 // only node. 257 root = null;// 变成空树 258 } else { // toDelete是叶子:2.不是根,没有顶替者. 259 // toDelete为黑才需要修复 260 if (colorOf(toDelete) == BLACK) 261 fixAfterDeletion(toDelete);// 先修复再cut掉 262 263 // 以下代码执行切掉叶子的动作 264 if (toDelete.parent != null) { 265 if (toDelete == toDelete.parent.left) 266 toDelete.parent.left = null; 267 else if (toDelete == toDelete.parent.right) 268 toDelete.parent.right = null; 269 toDelete.parent = null; 270 } 271 } 272 273 } 274 275 private void fixAfterDeletion(BSTNode N) { 276 if (colorOf(N) == RED) {// N为红,简单变黑即可 277 colorBlack(N); 278 } 279 // N为黑,即double black,删除节点和顶替节点都为黑,进行若干种情况的讨论 280 // case1:N是新的根节点,且N为黑色,没有任何破坏 281 else if (N.parent == null) { 282 } else {// 为黑,且不是根节点 283 case2(N); 284 } 285 } 286 287 /*-------情况2:兄弟为红色,转移为兄弟为黑-------*/ 288 private void case2(BSTNode N) { 289 BSTNode parent = N.parent; 290 BSTNode sib = sib(N, parent); 291 if (colorOf(sib) == RED) { 292 colorBlack(sib); 293 colorRed(parent); 294 if (N.isLeft()) 295 leftRotate(parent, N); 296 else 297 rightRotate(parent, N); 298 } 299 case3(N);// sib must be black. 300 } 301 302 private BSTNode sib(BSTNode N, BSTNode parent) { 303 BSTNode sib; 304 if (N.isLeft()) { 305 sib = parent.right; 306 } else { 307 sib = parent.left; 308 } 309 return sib; 310 } 311 312 /*-------情况3:兄弟为黑的前提下,讨论兄弟的双子为黑(兄弟可以被染红) 313 * 1.父为红色,双子为黑或者不为黑都走向case4 314 * 2.父为黑色,兄弟染红,递归父节点*/ 315 private void case3(BSTNode N) { 316 BSTNode parent = N.parent; 317 BSTNode sib = sib(N, parent); 318 /* 2.父为黑色,兄弟染红,递归父节点 */ 319 if (colorOf(parent) == BLACK && (sib.left == null || colorOf(sib.left) == BLACK) 320 && (sib.right == null || colorOf(sib.right) == BLACK)) { 321 colorRed(sib); 322 fixAfterDeletion(parent); 323 } else { 324 case4(N); 325 } 326 } 327 328 /*-------情况4.1:P为红,兄弟为黑,且兄弟的双子为黑——父兄反色,即可 329 * 4.2 P红或者黑,兄弟为黑,无论哪一子为红,都转移到case5*/ 330 private void case4(BSTNode N) { 331 BSTNode parent = N.parent; 332 BSTNode sib = sib(N, parent); 333 if (colorOf(parent) == RED && colorOf(sib.left) == BLACK && colorOf(sib.right) == BLACK) { 334 colorRed(sib); 335 colorBlack(parent);// 刚好平衡 336 } else { 337 case5(N); 338 } 339 } 340 341 /*-------情况5,兄弟向内的孩子为红,通过旋转转移为case6:向外的孩子为红*/ 342 private void case5(BSTNode N) { 343 BSTNode parent = N.parent; 344 BSTNode sib = sib(N, parent); 345 if (N.isLeft() && colorOf(sib.right) == BLACK) { 346 // s->color = RED; 347 // s->left->color = BLACK; 348 // rotate_right(s); 349 colorBlack(sib.left); 350 colorRed(sib); 351 rightRotate(sib, sib.left);// 兄弟的外侧孩子变为红色 352 } else if (N.isRight() && colorOf(sib.left) == BLACK) { 353 colorRed(sib); 354 colorBlack(sib.right); 355 leftRotate(sib, sib.right); 356 } 357 case6(N); 358 } 359 360 /*-------情况6兄弟向外的孩子为红 361 * 兄弟染为父节点的颜色 362 * 父节点染黑 363 * 父节点旋转*/ 364 private void case6(BSTNode N) { 365 BSTNode parent = N.parent; 366 BSTNode sib = sib(N, parent); 367 setColor(sib, colorOf(parent)); 368 colorBlack(parent); 369 if (N.isLeft()) { 370 colorBlack(sib.right); 371 leftRotate(parent, sib); 372 } else { 373 colorBlack(sib.left); 374 rightRotate(parent, sib); 375 } 376 } 377 378 private void setColor(BSTNode sib, boolean colorOf) { 379 if (sib != null) 380 sib.isRed = colorOf; 381 } 382 383 private BSTNode rightOf(BSTNode parent) { 384 return parent.right; 385 } 386 387 private BSTNode leftOf(BSTNode parent) { 388 return parent.left; 389 } 390 391 private boolean colorOf(BSTNode x) { 392 if (x == null) 393 return false; 394 return x.isRed; 395 } 396 397 @Override 398 public String toString() { 399 StringBuilder sb = new StringBuilder(); 400 List>> lists = super.levelOrder(); 401 for (List> l : lists) { 402 for (BSTNode n : l) { 403 sb.append(n.toString() + "\t"); 404 } 405 sb.append("\n"); 406 } 407 return sb.toString(); 408 } 409 }
View Code

  原文链接:http://www.sohu.com/a/131380870_479559

你可能感兴趣的:(红黑树)