R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每一个节点上都有存储位表示节点的颜色,能够是红(Red)或黑(Black)。 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每一个节点上都有存储位表示节点的颜色,能够是红(Red)或黑(Black)。java
红黑树的特性:node
(1)每一个节点或者是黑色,或者是红色。算法
(2)根节点是黑色。函数
(3)每一个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]post
(4)若是一个节点是红色的,则它的子节点必须是黑色的。测试
(5)从一个节点到该节点的子孙节点的全部路径上包含相同数目的黑节点。this
注意:3d
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。code
(02) 特性(5),确保没有一条路径会比其余路径长出俩倍。于是,红黑树是相对是接近平衡的二叉树。blog
红黑树的时间复杂度为: O(lgn)
下面经过“数学概括法”对红黑树的时间复杂度进行证实。
定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).
证实: "一棵含有n个节点的红黑树的高度至多为2log(n+1)" 的逆否命题是 "高度为h的红黑树,它的包含的内节点个数至少为 2^{h/2}-1个"。 咱们只须要证实逆否命题,便可证实原命题为真;即只需证实 "高度为h的红黑树,它的包含的内节点个数至少为 2^{h/2}-1个"。 从某个节点x出发(不包括该节点)到达一个叶节点的任意一条路径上,黑色节点的个数称为该节点的黑高度,记为bh(x)。 由红黑树的"特性(4)"可知 bh(x)>=h/2;进而,咱们只需证实 "高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"便可。 到这里,咱们将须要证实的定理已经由"一棵含有n个节点的红黑树的高度至多为2log(n+1)" 转变成只须要证实"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。
下面经过"数学概括法"开始论证高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。
(01) 当树的高度h=0时,内节点个数是0,bh(x) 为0,2^bh(x)-1 也为 0。显然,原命题成立。
(02) 当h>0,且树的高度为 h-1 时,它包含的节点个数至少为 2^{bh(x)-1}-1。这个是根据(01)推断出来的! 下面,由树的高度为 h-1 的已知条件推出“树的高度为 h 时,它所包含的节点树为 2^bh(x)-1”。 当树的高度为 h 时, 对于节点x(x为根节点),其黑高度为bh(x)。 对于节点x的左右子树,它们黑高度为 bh(x) 或者 bh(x)-1。 根据(02)的已知条件,咱们已知 "x的左右子树,即高度为 h-1 的节点,它包含的节点至少为 2^{bh(x)-1}-1 个"; 因此,节点x所包含的节点至少为 ( 2^{bh(x)-1}-1 ) + ( 2^{bh(x)-1}-1 ) + 1 = 2^{bh(x)-1}。即节点x所包含的节点至少为 2^{bh(x)-1} 。 所以,原命题成立。 由(01)、(02)得出,"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。所以,“一棵含有n个节点的红黑树的高度至多为2log(n+1)”。
红-黑树主要经过三种方式对平衡进行修正,改变节点颜色、左旋和右旋。这看起来有点抽象,咱们分别来介绍它们。
改变节点颜色比较容易理解,由于它违背了规则3。假设如今有个节点E,而后插入节点A和节点S,节点A在左子节点,S在右子节点,目前是平衡的。若是此时再插一个节点,那么就出现了不平衡了,由于红色节点的子节点必须为黑色,可是新插的节点是红色的。因此这时候就必须改变节点颜色了。因此咱们将根的两个子节点从红色变为黑色(至于为何都要变,下面插入的时候会详细介绍),将父节点会从黑色变成红色。能够用以下示意图表示一下:
一般左旋操做用于将一个向右倾斜的红色连接旋转为向左连接。示意图以下:
左旋有个很萌萌哒的动态示意图,能够方便理解:
右旋可左旋恰好相反,这里再也不赘述,直接看示意图:
固然咯,右旋也有个萌萌的动态图:
这里主要介绍了红-黑树对平衡的三种修正方式,你们有个感性的认识,那么何时该修正呢?何时该用哪一种修正呢?这将是接下来咱们要探讨的问题。
红-黑树的基本操做是添加、删除和旋转。对红-黑树进行添加或删除后,可能会破坏其平衡性,会用到哪一种旋转方式去修正呢?咱们首先对红-黑树的节点作一介绍,而后分别对左旋和右旋的具体实现作一分析,最后咱们探讨下红-黑树的具体操做。
红-黑树是对二叉搜索树的改进,因此其节点与二叉搜索树是差很少的,只不过在它基础上增长了一个boolean型变量来表示节点的颜色,具体看RBNode<T>类:
public class RBNode<T extends Comparable<T>>{ boolean color; //颜色 T key; //关键字(键值) RBNode<T> left; //左子节点 RBNode<T> right; //右子节点 RBNode<T> parent; //父节点 public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) { this.key = key; this.color = color; this.parent = parent; this.left = left; this.right = right; } public T getKey() { return key; } public String toString() { return "" + key + (this.color == RED? "R" : "B"); } }
//算法导论伪代码 LEFT-ROTATE(T, x) 01 y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操做 02 right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子 03 p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x 04 p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲” 05 if p[x] = nil[T] 06 then root[T] ← y // 状况1:若是 “x的父亲” 是空节点,则将y设为根节点 07 else if x = left[p[x]] 08 then left[p[x]] ← y // 状况2:若是 x是它父节点的左孩子,则将y设为“x的父节点的左孩子” 09 else right[p[x]] ← y // 状况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子” 10 left[y] ← x // 将 “x” 设为 “y的左孩子” 11 p[x] ← y // 将 “x的父节点” 设为 “y”
/*************对红黑树节点x进行左旋操做 ******************/ /* * 左旋示意图:对节点x进行左旋 * p p * / / * x y * / \ / \ * lx y -----> x ry * / \ / \ * ly ry lx ly * 左旋作了三件事: * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) * 3. 将y的左子节点设为x,将x的父节点设为y */ private void leftRotate(RBNode<T> x) { //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) RBNode<T> y = x.right; x.right = y.left; if(y.left != null) y.left.parent = x; //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) y.parent = x.parent; if(x.parent == null) { this.root = y; //若是x的父节点为空,则将y设为父节点 } else { if(x == x.parent.left) //若是x是左子节点 x.parent.left = y; //则也将y设为左子节点 else x.parent.right = y;//不然将y设为右子节点 } //3. 将y的左子节点设为x,将x的父节点设为y y.left = x; x.parent = y; }
RIGHT-ROTATE(T, y) 01 x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操做 02 left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子 03 p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y 04 p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲” 05 if p[y] = nil[T] 06 then root[T] ← x // 状况1:若是 “y的父亲” 是空节点,则将x设为根节点 07 else if y = right[p[y]] 08 then right[p[y]] ← x // 状况2:若是 y是它父节点的右孩子,则将x设为“y的父节点的左孩子” 09 else left[p[y]] ← x // 状况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子” 10 right[x] ← y // 将 “y” 设为 “x的右孩子” 11 p[y] ← x // 将 “y的父节点” 设为 “x”
/*************对红黑树节点y进行右旋操做 ******************/ /* * 左旋示意图:对节点y进行右旋 * p p * / / * y x * / \ / \ * x ry -----> lx y * / \ / \ * lx rx rx ry * 右旋作了三件事: * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时) * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右) * 3. 将x的右子节点设为y,将y的父节点设为x */ private void rightRotate(RBNode<T> y) { //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) RBNode<T> x = y.left; y.left = x.right; if(x.right != null) x.right.parent = y; //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) x.parent = y.parent; if(y.parent == null) { this.root = x; //若是x的父节点为空,则将y设为父节点 } else { if(y == y.parent.right) //若是x是左子节点 y.parent.right = x; //则也将y设为左子节点 else y.parent.left = x;//不然将y设为右子节点 } //3. 将y的左子节点设为x,将x的父节点设为y x.right = y; y.parent = x; }
第一步: 将红黑树看成一颗二叉查找树,将节点插入。 红黑树自己就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,不管是左旋仍是右旋,若旋转以前这棵树是二叉查找树,旋转以后它必定仍是二叉查找树。这也就意味着,任何的旋转和从新着色操做,都不会改变它仍然是一颗二叉查找树的事实。 第二步:将插入的节点着色为"红色"。 将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着咱们须要处理的状况越少。接下来,就要努力的让这棵树知足其它性质便可;知足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈
第三步: 经过一系列的旋转或着色等操做,使之从新成为一颗红黑树。
根据被插入节点的父节点的状况,能够将"当节点z被着色为红色节点,并插入二叉树"划分为三种状况来处理。 根据被插入节点的父节点的状况,能够将"当节点z被着色为红色节点,并插入二叉树"划分为三种状况来处理。
① 状况说明:被插入的节点是根节点。 处理方法:直接把此节点涂为黑色。
② 状况说明:被插入的节点的父节点是黑色。 处理方法:什么也不须要作。节点被插入后,仍然是红黑树。
③ 状况说明:被插入的节点的父节点是红色。 处理方法:那么,该状况与红黑树的“特性(5)”相冲突。这种状况下,被插入节点是必定存在非空祖父节点的;进一步的讲,被插入节点也必定存在叔叔节点(即便叔叔节点为空,咱们也视之为存在,空节点自己就是黑色节点)。理解这点以后,咱们依据"叔叔节点的状况",将这种状况进一步划分为3种状况(Case)。
分析完了红-黑树中主要的旋转操做,接下来咱们开始分析常见的插入、删除等操做了。这里先分析插入操做。 因为红-黑树是二叉搜索树的改进,因此插入操做的前半工做时相同的,即先找到待插入的位置,再将节点插入,先来看看插入的前半段代码:
RB-INSERT(T, z) y ← nil[T] // 新建节点“y”,将y设为空节点。 x ← root[T] // 设“红黑树T”的根节点为“x” while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y” do y ← x if key[z] < key[x] then x ← left[x] else x ← right[x] p[z] ← y // 设置 “z的父亲” 为 “y” if y = nil[T] then root[T] ← z // 状况1:若y是空节点,则将z设为根 else if key[z] < key[y] then left[y] ← z // 状况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子” else right[y] ← z // 状况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” left[z] ← nil[T] // z的左孩子设为空 right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。 color[z] ← RED // 将z着色为“红色” RB-INSERT-FIXUP(T, z) // 经过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树
结合伪代码以及为代码上面的说明,先理解RB-INSERT。理解了RB-INSERT以后,咱们接着对 RB-INSERT-FIXUP的伪代码进行说明。
添加修正操做的伪代码《算法导论》
RB-INSERT-FIXUP(T, z) while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行如下处理。 do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行如下处理。 then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)” if color[y] = RED // Case 1条件:叔叔是红色 then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。 color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。 color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。 z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点) else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子 then z ← p[z] ▹ Case 2 // (01) 将“父节点”做为“新的当前节点”。 LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。 color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。 color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。 RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。 else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操做中“right”和“left”交换位置,而后依次执行。 color[root[T]] ← BLACK
/*********************** 向红黑树中插入节点 **********************/ public void insert(T key) { RBNode<T> node = new RBNode<T>(key, RED, null, null, null); if(node != null) insert(node); } //将节点插入到红黑树中,这个过程与二叉搜索树是同样的 private void insert(RBNode<T> node) { RBNode<T> current = null; //表示最后node的父节点 RBNode<T> x = this.root; //用来向下搜索用的 //1. 找到插入的位置 while(x != null) { current = x; int cmp = node.key.compareTo(x.key); if(cmp < 0) x = x.left; else x = x.right; } node.parent = current; //找到了位置,将当前current做为node的父节点 //2. 接下来判断node是插在左子节点仍是右子节点 if(current != null) { int cmp = node.key.compareTo(current.key); if(cmp < 0) current.left = node; else current.right = node; } else { this.root = node; } //3. 将它从新修整为一颗红黑树 insertFixUp(node); }
这与二叉搜索树中实现的思路如出一辙,这里再也不赘述,主要看看方法里面最后一步insertFixUp操做。由于插入后可能会致使树的不平衡,insertFixUp方法里主要是分状况讨论,分析什么时候变色,什么时候左旋,什么时候右旋。咱们先从理论上分析具体的状况,而后再看insertFixUp方法的具体实现。 若是是第一次插入,因为原树为空,因此只会违反红-黑树的规则2,因此只要把根节点涂黑便可;若是插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不须要作;可是遇到以下三种状况时,咱们就要开始变色和旋转了:
private void insertFixUp(RBNode<T> node) { RBNode<T> parent, gparent; //定义父节点和祖父节点 //须要修整的条件:父节点存在,且父节点的颜色是红色 while(((parent = parentOf(node)) != null) && isRed(parent)) { gparent = parentOf(parent);//得到祖父节点 //若父节点是祖父节点的左子节点,下面else与其相反 if(parent == gparent.left) { RBNode<T> uncle = gparent.right; //得到叔叔节点 //case1: 叔叔节点也是红色 if(uncle != null && isRed(uncle)) { setBlack(parent); //把父节点和叔叔节点涂黑 setBlack(uncle); setRed(gparent); //把祖父节点涂红 node = gparent; //将位置放到祖父节点处 continue; //继续while,从新判断 } //case2: 叔叔节点是黑色,且当前节点是右子节点 if(node == parent.right) { leftRotate(parent); //从父节点处左旋 RBNode<T> tmp = parent; //而后将父节点和本身调换一下,为下面右旋作准备 parent = node; node = tmp; } //case3: 叔叔节点是黑色,且当前节点是左子节点 setBlack(parent); setRed(gparent); rightRotate(gparent); } else { //若父节点是祖父节点的右子节点,与上面的彻底相反,本质同样的 RBNode<T> uncle = gparent.left; //case1: 叔叔节点也是红色 if(uncle != null & isRed(uncle)) { setBlack(parent); setBlack(uncle); setRed(gparent); node = gparent; continue; } //case2: 叔叔节点是黑色的,且当前节点是左子节点 if(node == parent.left) { rightRotate(parent); RBNode<T> tmp = parent; parent = node; node = tmp; } //case3: 叔叔节点是黑色的,且当前节点是右子节点 setBlack(parent); setRed(gparent); leftRotate(gparent); } } //将根节点设置为黑色 setBlack(this.root); }
/*********************** 删除红黑树中的节点 **********************/ public void remove(T key) { RBNode<T> node; if((node = search(root, key)) != null) remove(node); } private void remove(RBNode<T> node) { RBNode<T> child, parent; boolean color; //1. 被删除的节点“左右子节点都不为空”的状况 if((node.left != null) && (node.right != null)) { //先找到被删除节点的后继节点,用它来取代被删除节点的位置 RBNode<T> replace = node; // 1). 获取后继节点 replace = replace.right; while(replace.left != null) replace = replace.left; // 2). 处理“后继节点”和“被删除节点的父节点”之间的关系 if(parentOf(node) != null) { //要删除的节点不是根节点 if(node == parentOf(node).left) parentOf(node).left = replace; else parentOf(node).right = replace; } else { //不然 this.root = replace; } // 3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系 child = replace.right; //后继节点确定不存在左子节点! parent = parentOf(replace); color = colorOf(replace);//保存后继节点的颜色 if(parent == node) { //后继节点是被删除节点的子节点 parent = replace; } else { //不然 if(child != null) setParent(child, parent); parent.left = child; replace.right = node.right; setParent(node.right, replace); } replace.parent = node.parent; replace.color = node.color; //保持原来位置的颜色 replace.left = node.left; node.left.parent = replace; if(color == BLACK) { //4. 若是移走的后继节点颜色是黑色,从新修整红黑树 removeFixUp(child, parent);//将后继节点的child和parent传进去 } node = null; return; } }
package tree; /** * @description implementation of Red-Black Tree by Java * @author eson_15 * @param <T> * @date 2016-4-18 19:27:28 */ public class RBTree<T extends Comparable<T>> { private RBNode<T> root; //根节点 private static final boolean RED = false; //定义红黑树标志 private static final boolean BLACK = true; //内部类:节点类 public class RBNode<T extends Comparable<T>>{ boolean color; //颜色 T key; //关键字(键值) RBNode<T> left; //左子节点 RBNode<T> right; //右子节点 RBNode<T> parent; //父节点 public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) { this.key = key; this.color = color; this.parent = parent; this.left = left; this.right = right; } public T getKey() { return key; } public String toString() { return "" + key + (this.color == RED? "R" : "B"); } } public RBTree() { root = null; } public RBNode<T> parentOf(RBNode<T> node) { //得到父节点 return node != null? node.parent : null; } public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点 if(node != null) node.parent = parent; } public boolean colorOf(RBNode<T> node) { //得到节点的颜色 return node != null? node.color : BLACK; } public boolean isRed(RBNode<T> node) { //判断节点的颜色 return (node != null)&&(node.color == RED)? true : false; } public boolean isBlack(RBNode<T> node) { return !isRed(node); } public void setRed(RBNode<T> node) { //设置节点的颜色 if(node != null) node.color = RED; } public void setBlack(RBNode<T> node) { if(node != null) { node.color = BLACK; } } public void setColor(RBNode<T> node, boolean color) { if(node != null) node.color = color; } /***************** 前序遍历红黑树 *********************/ public void preOrder() { preOrder(root); } private void preOrder(RBNode<T> tree) { if(tree != null) { System.out.print(tree.key + " "); preOrder(tree.left); preOrder(tree.right); } } /***************** 中序遍历红黑树 *********************/ public void inOrder() { inOrder(root); } private void inOrder(RBNode<T> tree) { if(tree != null) { preOrder(tree.left); System.out.print(tree.key + " "); preOrder(tree.right); } } /***************** 后序遍历红黑树 *********************/ public void postOrder() { postOrder(root); } private void postOrder(RBNode<T> tree) { if(tree != null) { preOrder(tree.left); preOrder(tree.right); System.out.print(tree.key + " "); } } /**************** 查找红黑树中键值为key的节点 ***************/ public RBNode<T> search(T key) { return search(root, key); // return search2(root, key); //使用递归的方法,本质同样的 } private RBNode<T> search(RBNode<T> x, T key) { while(x != null) { int cmp = key.compareTo(x.key); if(cmp < 0) x = x.left; else if(cmp > 0) x = x.right; else return x; } return x; } //使用递归 private RBNode<T> search2(RBNode<T> x, T key) { if(x == null) return x; int cmp = key.compareTo(x.key); if(cmp < 0) return search2(x.left, key); else if(cmp > 0) return search2(x.right, key); else return x; } /**************** 查找最小节点的值 **********************/ public T minValue() { RBNode<T> node = minNode(root); if(node != null) return node.key; return null; } private RBNode<T> minNode(RBNode<T> tree) { if(tree == null) return null; while(tree.left != null) { tree = tree.left; } return tree; } /******************** 查找最大节点的值 *******************/ public T maxValue() { RBNode<T> node = maxNode(root); if(node != null) return node.key; return null; } private RBNode<T> maxNode(RBNode<T> tree) { if(tree == null) return null; while(tree.right != null) tree = tree.right; return tree; } /********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/ public RBNode<T> successor(RBNode<T> x) { //若是x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点” if(x.right != null) return minNode(x.right); //若是x没有右子节点,会出现如下两种状况: //1. x是其父节点的左子节点,则x的后继节点为它的父节点 //2. x是其父节点的右子节点,则先查找x的父节点p,而后对p再次进行这两个条件的判断 RBNode<T> p = x.parent; while((p != null) && (x == p.right)) { //对应状况2 x = p; p = x.parent; } return p; //对应状况1 } /********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/ public RBNode<T> predecessor(RBNode<T> x) { //若是x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点” if(x.left != null) return maxNode(x.left); //若是x没有左子节点,会出现如下两种状况: //1. x是其父节点的右子节点,则x的前驱节点是它的父节点 //2. x是其父节点的左子节点,则先查找x的父节点p,而后对p再次进行这两个条件的判断 RBNode<T> p = x.parent; while((p != null) && (x == p.left)) { //对应状况2 x = p; p = x.parent; } return p; //对应状况1 } /*************对红黑树节点x进行左旋操做 ******************/ /* * 左旋示意图:对节点x进行左旋 * p p * / / * x y * / \ / \ * lx y -----> x ry * / \ / \ * ly ry lx ly * 左旋作了三件事: * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) * 3. 将y的左子节点设为x,将x的父节点设为y */ private void leftRotate(RBNode<T> x) { //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) RBNode<T> y = x.right; x.right = y.left; if(y.left != null) y.left.parent = x; //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) y.parent = x.parent; if(x.parent == null) { this.root = y; //若是x的父节点为空,则将y设为父节点 } else { if(x == x.parent.left) //若是x是左子节点 x.parent.left = y; //则也将y设为左子节点 else x.parent.right = y;//不然将y设为右子节点 } //3. 将y的左子节点设为x,将x的父节点设为y y.left = x; x.parent = y; } /*************对红黑树节点y进行右旋操做 ******************/ /* * 左旋示意图:对节点y进行右旋 * p p * / / * y x * / \ / \ * x ry -----> lx y * / \ / \ * lx rx rx ry * 右旋作了三件事: * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时) * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右) * 3. 将x的右子节点设为y,将y的父节点设为x */ private void rightRotate(RBNode<T> y) { //1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) RBNode<T> x = y.left; y.left = x.right; if(x.right != null) x.right.parent = y; //2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) x.parent = y.parent; if(y.parent == null) { this.root = x; //若是x的父节点为空,则将y设为父节点 } else { if(y == y.parent.right) //若是x是左子节点 y.parent.right = x; //则也将y设为左子节点 else y.parent.left = x;//不然将y设为右子节点 } //3. 将y的左子节点设为x,将x的父节点设为y x.right = y; y.parent = x; } /*********************** 向红黑树中插入节点 **********************/ public void insert(T key) { RBNode<T> node = new RBNode<T>(key, RED, null, null, null); if(node != null) insert(node); } //将节点插入到红黑树中,这个过程与二叉搜索树是同样的 private void insert(RBNode<T> node) { RBNode<T> current = null; //表示最后node的父节点 RBNode<T> x = this.root; //用来向下搜索用的 //1. 找到插入的位置 while(x != null) { current = x; int cmp = node.key.compareTo(x.key); if(cmp < 0) x = x.left; else x = x.right; } node.parent = current; //找到了位置,将当前current做为node的父节点 //2. 接下来判断node是插在左子节点仍是右子节点 if(current != null) { int cmp = node.key.compareTo(current.key); if(cmp < 0) current.left = node; else current.right = node; } else { this.root = node; } //3. 将它从新修整为一颗红黑树 insertFixUp(node); } private void insertFixUp(RBNode<T> node) { RBNode<T> parent, gparent; //定义父节点和祖父节点 //须要修整的条件:父节点存在,且父节点的颜色是红色 while(((parent = parentOf(node)) != null) && isRed(parent)) { gparent = parentOf(parent);//得到祖父节点 //若父节点是祖父节点的左子节点,下面else与其相反 if(parent == gparent.left) { RBNode<T> uncle = gparent.right; //得到叔叔节点 //case1: 叔叔节点也是红色 if(uncle != null && isRed(uncle)) { setBlack(parent); //把父节点和叔叔节点涂黑 setBlack(uncle); setRed(gparent); //把祖父节点涂红 node = gparent; //将位置放到祖父节点处 continue; //继续while,从新判断 } //case2: 叔叔节点是黑色,且当前节点是右子节点 if(node == parent.right) { leftRotate(parent); //从父节点处左旋 RBNode<T> tmp = parent; //而后将父节点和本身调换一下,为下面右旋作准备 parent = node; node = tmp; } //case3: 叔叔节点是黑色,且当前节点是左子节点 setBlack(parent); setRed(gparent); rightRotate(gparent); } else { //若父节点是祖父节点的右子节点,与上面的彻底相反,本质同样的 RBNode<T> uncle = gparent.left; //case1: 叔叔节点也是红色 if(uncle != null & isRed(uncle)) { setBlack(parent); setBlack(uncle); setRed(gparent); node = gparent; continue; } //case2: 叔叔节点是黑色的,且当前节点是左子节点 if(node == parent.left) { rightRotate(parent); RBNode<T> tmp = parent; parent = node; node = tmp; } //case3: 叔叔节点是黑色的,且当前节点是右子节点 setBlack(parent); setRed(gparent); leftRotate(gparent); } } //将根节点设置为黑色 setBlack(this.root); } /*********************** 删除红黑树中的节点 **********************/ public void remove(T key) { RBNode<T> node; if((node = search(root, key)) != null) remove(node); } private void remove(RBNode<T> node) { RBNode<T> child, parent; boolean color; //1. 被删除的节点“左右子节点都不为空”的状况 if((node.left != null) && (node.right != null)) { //先找到被删除节点的后继节点,用它来取代被删除节点的位置 RBNode<T> replace = node; // 1). 获取后继节点 replace = replace.right; while(replace.left != null) replace = replace.left; // 2). 处理“后继节点”和“被删除节点的父节点”之间的关系 if(parentOf(node) != null) { //要删除的节点不是根节点 if(node == parentOf(node).left) parentOf(node).left = replace; else parentOf(node).right = replace; } else { //不然 this.root = replace; } // 3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系 child = replace.right; //后继节点确定不存在左子节点! parent = parentOf(replace); color = colorOf(replace);//保存后继节点的颜色 if(parent == node) { //后继节点是被删除节点的子节点 parent = replace; } else { //不然 if(child != null) setParent(child, parent); parent.left = child; replace.right = node.right; setParent(node.right, replace); } replace.parent = node.parent; replace.color = node.color; //保持原来位置的颜色 replace.left = node.left; node.left.parent = replace; if(color == BLACK) { //4. 若是移走的后继节点颜色是黑色,从新修整红黑树 removeFixUp(child, parent);//将后继节点的child和parent传进去 } node = null; return; } } //node表示待修正的节点,即后继节点的子节点(由于后继节点被挪到删除节点的位置去了) private void removeFixUp(RBNode<T> node, RBNode<T> parent) { RBNode<T> other; while((node == null || isBlack(node)) && (node != this.root)) { if(parent.left == node) { //node是左子节点,下面else与这里的恰好相反 other = parent.right; //node的兄弟节点 if(isRed(other)) { //case1: node的兄弟节点other是红色的 setBlack(other); setRed(parent); leftRotate(parent); other = parent.right; } //case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的 if((other.left == null || isBlack(other.left)) && (other.right == null || isBlack(other.right))) { setRed(other); node = parent; parent = parentOf(node); } else { //case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色 if(other.right == null || isBlack(other.right)) { setBlack(other.left); setRed(other); rightRotate(other); other = parent.right; } //case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.right); leftRotate(parent); node = this.root; break; } } else { //与上面的对称 other = parent.left; if (isRed(other)) { // Case 1: node的兄弟other是红色的 setBlack(other); setRed(parent); rightRotate(parent); other = parent.left; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.left==null || isBlack(other.left)) { // Case 3: node的兄弟other是黑色的,而且other的左子节点是红色,右子节点为黑色。 setBlack(other.right); setRed(other); leftRotate(other); other = parent.left; } // Case 4: node的兄弟other是黑色的;而且other的左子节点是红色的,右子节点任意颜色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.left); rightRotate(parent); node = this.root; break; } } } if (node!=null) setBlack(node); } /****************** 销毁红黑树 *********************/ public void clear() { destroy(root); root = null; } private void destroy(RBNode<T> tree) { if(tree == null) return; if(tree.left != null) destroy(tree.left); if(tree.right != null) destroy(tree.right); tree = null; } /******************* 打印红黑树 *********************/ public void print() { if(root != null) { print(root, root.key, 0); } } /* * key---节点的键值 * direction--- 0:表示该节点是根节点 * 1:表示该节点是它的父节点的左子节点 * 2:表示该节点是它的父节点的右子节点 */ private void print(RBNode<T> tree, T key, int direction) { if(tree != null) { if(0 == direction) System.out.printf("%2d(B) is root\n", tree.key); else System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left"); print(tree.left, tree.key, -1); print(tree.right, tree.key, 1); } } }
下面附上测试程序吧:
package test; import tree.RBTree; public class RBTreeTest { private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80}; private static final boolean mDebugInsert = true; // "插入"动做的检测开关(false,关闭;true,打开) private static final boolean mDebugDelete = true; // "删除"动做的检测开关(false,关闭;true,打开) public static void main(String[] args) { int i, ilen = a.length; RBTree<Integer> tree = new RBTree<Integer>(); System.out.printf("== 原始数据: "); for(i=0; i<ilen; i++) System.out.printf("%d ", a[i]); System.out.printf("\n"); for(i=0; i<ilen; i++) { tree.insert(a[i]); // 设置mDebugInsert=true,测试"添加函数" if (mDebugInsert) { System.out.printf("== 添加节点: %d\n", a[i]); System.out.printf("== 树的详细信息: \n"); tree.print(); System.out.printf("\n"); } } System.out.printf("== 前序遍历: "); tree.preOrder(); System.out.printf("\n== 中序遍历: "); tree.inOrder(); System.out.printf("\n== 后序遍历: "); tree.postOrder(); System.out.printf("\n"); System.out.printf("== 最小值: %s\n", tree.minValue()); System.out.printf("== 最大值: %s\n", tree.maxValue()); System.out.printf("== 树的详细信息: \n"); tree.print(); System.out.printf("\n"); // 设置mDebugDelete=true,测试"删除函数" if (mDebugDelete) { for(i=0; i<ilen; i++) { tree.remove(a[i]); System.out.printf("== 删除节点: %d\n", a[i]); System.out.printf("== 树的详细信息: \n"); tree.print(); System.out.printf("\n"); } } } }