目录:
- 红黑树的特性
- 节点不是红色就是黑色
- 根节点为黑色
- 叶子节点为黑色
- 每个红色节点其子节点必须是黑色节点。
- 从一个节点到一个null引用的每一条路径必须包含相同数目的黑色节点
注意:
特性(3)中的叶子节点,是只为空(NIL或null)的节点。
特性(5)确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
- 红黑树维持其特性的方法:变色和旋转
- 算法
- 插入:主要根据其父节点和父节点的兄弟节点进行变色和旋转
- 自底向上插入
- 自顶向下插入
- 删除
- 插入:主要根据其父节点和父节点的兄弟节点进行变色和旋转
- 应用
它是一颗二叉查找树,适合查找,不适合文件存储,别说让它和B树、B+树比较的话。
红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。 例如,Java集合中的TreeSet、TreeMap、HashMap和CurrentHashMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。 - 红黑树和平衡二叉树对比
经验指出,平均红黑树大约和平均AVL树一样深,从而查找时间一般接近最优。
红黑树的优点是执行插入所需要的开销相对较低,另外就是实践中发生的旋转相对较少
一.红黑树维持其特性的方法:变色和旋转
-
红黑树的基本操作包括删除和添加。在删除或者添加一个节点的时候就有可能打破原有的红黑树维持的平衡,那么就需要通过变色和旋转的方式来使红黑树重新达到平衡。着色是非常简单的,直接将节点的颜色改变就可以了,多以要理解红黑树,就必须需要懂得如何进行旋转,旋转又分为左旋和右转,两个操作相反的,所以理解了一个旋转的操作就很容易理解另一个旋转了。
-
左旋
如图所示,红色节点为旋转支点,支点往左子树移动即为左旋。左旋之后我们可以看到原支点的位置被原支点的右子节点代替,新支点的左子节点变为了原来为父节点的原支点,新支点的左子节点变为原支点的右子节点,因此左旋操作总共右3个节点,以为旋转前的结构举例,分别为红色节点(原支点),黄色节点(新支点)和L节点。
-
右旋
右旋操作和左旋相反的,两者互反。依然是红色作为旋转支点,右旋后黄色节点代替了红色节点原来的位置,黄色节点的右节点旋转后变为红色节点的左节点 。
-
二.插入操作
方法一:自底向上插入,插入操作步骤
- 将新结点设置为红色插入到红黑树中
这里为什么需要设置成红色呢?主要是为了满足特性5,这样在插入节点后就少解决了一个冲突,也就少一点麻烦。插入完成后,我们来看一下还有那些特性是有可能发生冲突的,特性1每个节点不是红色就是黑色的,这明显没有冲突,特性2根节点为黑色,当插入节点为根节点的时候就会有冲突了,这种就很简单了,直接将根节点着色为黑色即可。特性3每个叶子节点都是黑色,这个明显没有冲突。特性4每个红色节点的子节点都是黑色的,这个特性就有可能会冲突,因为在插入新节点的时候我们无法确定新节点的父节点的颜色是黑色的还是红色,如果新节点的父节为黑色,那么就不会有冲突,否则就会违背了特性4。特性5任意节点,到其任意子节点的所有路径都包含相同的黑色节点,因为我们插入的新节点被着色为红色,所以并不会影响到每个路径的黑色节点的数量,因此也不会有冲突。综上所诉,那么在插入新节点的时候,只有特性4有可能发生冲突。 - 判断新插入的结点的父结点的颜色
- 如果新插入的结点的父结点是黑色,那么插入结束。
- 如果新插入的结点的父结点是红色,则分为两种情况处理:
- 若其父结点的兄弟结点是黑色,则分为两种情况处理:
- 情况1:新节点为左节点,解决:g右旋,p变黑色,g变红色,其它不变。还有它们的子结点的转移,下同
- 情况2:新节点为右节点,解决:p先左旋(和情况1相同了),再g右旋,并且n变黑色,g变红色。
- 若其父结点的兄弟结点是红色,则分为两种情况处理:
- 情况3:新节点为左节点
- 情况4:新节点为右节点
上面两种情况解决方法:- 只需要将祖父结点变为红色节点,父结点和其兄弟结点变为黑色即可
- 但是这只是树的局部的平衡,这里又要分两种情况了
- 如果g的父结点是一个黑色,那么插入结束
- 如果g的父结点也是一个红结点呢
这里又分为两种情况处理:- g结点的父结点的兄弟结点是黑色
这种情况是不是就上面讨论的【情况1】,那么也有可能是【情况2】
解决方法:按照上面讨论的解决方法解决。
例:插入n结点的两次维持红黑树过程 - g结点的父结点的兄弟结点是红色
这种情况是不是就上面讨论的【情况3】,那么也有可能是【情况4】
解决方法:只需要将祖父结点变为红色节点,父结点和其兄弟结点变为黑色。但是同样这可能只是局部平衡,所以需要自底向上调整,直到根结点为止。当祖父节点为根结点的时候,我们直接将根节点着色为黑色即可,因为祖父节点的两个子节点都是黑色的,所以变为黑色后仍然是平衡的
示例:
- g结点的父结点的兄弟结点是黑色
- 只需要将祖父结点变为红色节点,父结点和其兄弟结点变为黑色即可
- 若其父结点的兄弟结点是黑色,则分为两种情况处理:
- 上面分析的情况插入的结点在左子树,如果在右子树,分析同理
方法二:自顶向下插入,插入操作步骤
- 将需要插入的结点设置为红色放在其所在的位置
- 从根结点到该结点的最短路径中,从根结点自顶向下,当看到一个结点X有两个红儿子的时候,可使X呈红色而让它的两个儿子是黑的。(如果X是根结点,则在颜色翻转后它将是红色,但是为恢复性质2可以直接着色成黑色)。
只有当X的父节点P也是红色的时候这种翻转将破坏红黑的法则。但是此时可以应用适当的旋转解决。
如果X的父结点的兄弟是红色会如何呢?这种可能已经被从顶向下过程中的行动所排除,因此X的父结点的兄弟结点不可能是红色!
自顶向下插入实现代码:
public class RedBlackTree<AnyType extends Comparable<? super AnyType>>{
private RedBlackNode<AnyType> header;
private RedBlackNode<AnyType> nullNode;
private static final int BLACK = 1; // BLACK must be 1
private static final int RED = 0;
//静态内部类,作为红黑树的结点
private static class RedBlackNode<AnyType>{
AnyType element; // The data in the node
RedBlackNode<AnyType> left; // Left child
RedBlackNode<AnyType> right; // Right child
int color; // Color
RedBlackNode( AnyType theElement ){
this( theElement, null, null );
}
RedBlackNode( AnyType theElement, RedBlackNode<AnyType> lt, RedBlackNode<AnyType> rt ){
element = theElement;
left = lt;
right = rt;
color = RedBlackTree.BLACK;
}
}
public RedBlackTree( ){
nullNode = new RedBlackNode<>( null );
nullNode.left = nullNode.right = nullNode;
header = new RedBlackNode<>( null );
header.left = header.right = nullNode;
}
private int compare( AnyType item, RedBlackNode<AnyType> t ){
if( t == header )
return 1;
else
return item.compareTo( t.element );
}
使用这些属性用来辅助插入
// Used in insert routine and its helpers
private RedBlackNode<AnyType> current;//当前
private RedBlackNode<AnyType> parent;//父亲
private RedBlackNode<AnyType> grand;//祖父
private RedBlackNode<AnyType> great;//曾祖父
/**
* 1.插入
* @param item the item to insert.
*/
public void insert( AnyType item ){
current = parent = grand = header;
nullNode.element = item;
while( compare( item, current ) != 0 ){
great = grand; grand = parent; parent = current;
current = compare( item, current ) < 0 ? current.left : current.right;
// Check if two red children; fix if so
if( current.left.color == RED && current.right.color == RED )
handleReorient( item );
}
// Insertion fails if already present
if( current != nullNode )
return;
current = new RedBlackNode<>( item, nullNode, nullNode );
// Attach to parent
if( compare( item, parent ) < 0 )
parent.left = current;
else
parent.right = current;
handleReorient( item );
}
/**
* 2.删除
* @param x the item to remove.
* @throws UnsupportedOperationException if called.
*/
public void remove( AnyType x )
{
throw new UnsupportedOperationException( );
}
/**
* 3.查找:最小元
* @return the smallest item or throw UnderflowExcepton if empty.
*/
public AnyType findMin( )
{
if( isEmpty( ) ) {
System.out.println("空树!!!");
}
RedBlackNode<AnyType> itr = header.right;
while( itr.left != nullNode )
itr = itr.left;
return itr.element;
}
/**
* 4.查找:最大元
* @return the largest item or throw UnderflowExcepton if empty.
*/
public AnyType findMax( ){
if( isEmpty( ) ) {
System.out.println("空树!!!");
}
RedBlackNode<AnyType> itr = header.right;
while( itr.right != nullNode )
itr = itr.right;
return itr.element;
}
/**
* 5.包含
* @param x the item to search for.
* @return true if x is found; otherwise false.
*/
public boolean contains( AnyType x ){
nullNode.element = x;
current = header.right;
for( ; ; ){
if( x.compareTo( current.element ) < 0 )
current = current.left;
else if( x.compareTo( current.element ) > 0 )
current = current.right;
else if( current != nullNode )
return true;
else
return false;
}
}
/**
* 6.置空
*/
public void makeEmpty( )
{
header.right = nullNode;
}
/**
* 7.中序遍历
*/
public void printTree( )
{
if( isEmpty( ) )
System.out.println( "Empty tree" );
else
printTree( header.right );
}
/**
* Internal method to print a subtree in sorted order.
* @param t the node that roots the subtree.
*/
private void printTree( RedBlackNode<AnyType> t )
{
if( t != nullNode )
{
printTree( t.left );
System.out.println( t.element+",颜色:"+t.color);
printTree( t.right );
}
}
/**
* Test if the tree is logically empty.
* @return true if empty, false otherwise.
*/
public boolean isEmpty( )
{
return header.right == nullNode;
}
/**
* 颜色、位置调整
* Internal routine that is called during an insertion
* if a node has two red children. Performs flip and rotations.
* @param item the item being inserted.
*/
private void handleReorient( AnyType item )
{
// Do the color flip
current.color = RED;
current.left.color = BLACK;
current.right.color = BLACK;
if( parent.color == RED ) // Have to rotate
{
grand.color = RED;
if( ( compare( item, grand ) < 0 ) !=
( compare( item, parent ) < 0 ) )
parent = rotate( item, grand ); // Start dbl rotate
current = rotate( item, great );
current.color = BLACK;
}
header.right.color = BLACK; // Make root black
}
/**
* 双旋
* Internal routine that performs a single or double rotation.
* Because the result is attached to the parent, there are four cases.
* Called by handleReorient.
* @param item the item in handleReorient.
* @param parent the parent of the root of the rotated subtree.
* @return the root of the rotated subtree.
*/
private RedBlackNode<AnyType> rotate( AnyType item, RedBlackNode<AnyType> parent )
{
if( compare( item, parent ) < 0 )
return parent.left = compare( item, parent.left ) < 0 ?
rotateWithLeftChild( parent.left ) : // LL
rotateWithRightChild( parent.left ) ; // LR
else
return parent.right = compare( item, parent.right ) < 0 ?
rotateWithLeftChild( parent.right ) : // RL
rotateWithRightChild( parent.right ); // RR
}
/**
* 右旋
* Rotate binary tree node with left child.
*/
private RedBlackNode<AnyType> rotateWithLeftChild( RedBlackNode<AnyType> k2 )
{
RedBlackNode<AnyType> k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
return k1;
}
/**
* 左旋
* Rotate binary tree node with right child.
*/
private RedBlackNode<AnyType> rotateWithRightChild( RedBlackNode<AnyType> k1 )
{
RedBlackNode<AnyType> k2 = k1.right;
k1.right = k2.left;
k2.left = k1;
return k2;
}
}
测试
public class Test {
public static void main(String[] args) {
RedBlackTree<Integer> red=new RedBlackTree();
red.insert(30);
red.insert(15);
red.insert(70);
red.insert(10);
red.insert(20);
red.insert(60);
red.insert(85);
red.insert(5);
red.insert(50);
red.insert(65);
red.insert(80);
red.insert(90);
red.insert(40);
red.insert(55);
red.insert(45);
red.printTree();//在遍历的时候还打印结点的颜色
}
}
三.删除操作
二叉排序树删除一个节点的时候有以下3种情况:
- 删除的节点没有子节点
- 删除的节点只有一个子节点
- 删除的节点有两个子节点
红黑树也以此分析:
- 情况一:删除的节点没有子节点
- 被删结点为红色:直接将结点删除即可,不破坏任何红黑树的性质。
- 被删结点为黑色,需要修复
- 情况二:删除的节点只有一个子节点
- 被删结点为红色
这种情况不存在,因为其子节点必定是黑色,那么其子节点的null结点和被删除结点的null结点必定不符合特性5 - 被删结点为黑色
这种组合下,被删结点node的另一个子结点value必然为红色,此时直接将node删掉,用value代替node的位置,并将value着黑即可。
- 被删结点为红色
- 情况三:删除的节点X有两个子节点
令X为当前结点,T是它的兄弟结点,而P是它们的父亲
对于这种情况,我们通过将X和它的后继节点N的值交换的方 式,将删除节点X转换为删除后继节点N,而后继节点只可能是以下两种情况:- N是叶子节点 --- > 对应情况一
- N有一个子节点 ---- > 对应情况二
-
下面我们讨论需要修复的情况:
注:兄弟结点是黑色,且没有子结点
如果父结点是红色,则将父结点设置为黑色,兄弟结点设置为红色
如果父结点是黑色,则将兄弟结点设置为红色,将父结点到根结点的路径上一旦遇到红色结点,就将红色结点设为黑色,然后进行往路径一方进行旋转,让其多出一个黑色结点。
删除实现代码:
/**
* 删除 叶子节点 后的修复过程
* @param deletedNode 被删除的节点
* @param deletedNodeParent 被删除节点的父节点
*/
private void deleteLeafFix(Node deletedNode){
while((deletedNode != root) && (BLACK == deletedNode.color)){
Node parent = deletedNode.parentNode;
Node brother = getBrother(deletedNode);
if(deletedNode.key.compareTo(parent.key) > 0){ // 删除的是右叶子节点
if(RED == brother.color){ // case5: 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.rightNode.color = RED;
rightRotation(parent);
break;
}else{
if((null == brother.leftNode) && (null == brother.rightNode)){ // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
}else{
if((null != brother.leftNode) && (RED == brother.leftNode.color)){// case1: 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的)
//case3: 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.leftNode.color = BLACK;
rightRotation(parent);
break;
}else{// case2: 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
brother.rightNode.color = BLACK;
brother.color = RED;
leftRotation(brother);
}
}
}
}else{// 删除的是左叶子节点
if(RED == brother.color){ // case5 : 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.leftNode.color = RED;
leftRotation(parent);
break;
}else{
if((null == brother.leftNode) && (null == brother.rightNode)){ // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
}else{
if((null != brother.rightNode) && (RED == brother.rightNode.color)){ // case1 : 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
// case3 : 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.rightNode.color = BLACK;
leftRotation(parent);
break;
}else{ // case2: 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的)
brother.leftNode.color = BLACK;
brother.color = RED;
rightRotation(brother);
}
}
}
}
}
deletedNode.color = BLACK;
}
private Node getBrother(Node node){
if(null == node){
return null;
}
Node parent = node.parentNode;
if(null == parent){
return null;
}
if(node.key.compareTo(parent.key) > 0){
return parent.leftNode;
}else{
return parent.rightNode;
}
}
public boolean delete(K key){
if(null != key){
if(null != root){
return deleteNode(key, root, null);
}
}
return false;
}
private boolean deleteNode(K key, Node current, Node parent){
if(null != current){
if(key.compareTo(current.key) > 0){
return deleteNode(key, current.rightNode, current);
}
if(key.compareTo(current.key) < 0){
return deleteNode(key, current.leftNode, current);
}
if(key.compareTo(current.key) == 0){
if((null != current.leftNode) && (null != current.rightNode)){ //将要删除的节点下有两个子节点
dleTwoChildrenNode(current);
return true;
}else{
if((null == current.leftNode) && (null == current.rightNode)){ //将要删除的节点没有子节点
deleteLeafFix(current);
if(current.key.compareTo(parent.key) > 0){
parent.rightNode = null;
}else{
parent.leftNode = null;
}
return true;
}else{ // 将要删除的节点下有一个子节点,
dleOneChildNode(current);
return true;
}
}
}
}
return false;
}
private void dleOneChildNode(Node delNode){
Node replaceNode = (null == delNode.leftNode) ? delNode.rightNode : delNode.leftNode;
deltetLeafNode(delNode, replaceNode);
}
/**
* 处理被删除节点有两个子节点的情况
* @param target 将要被删除的节点
*/
private void dleTwoChildrenNode(Node target){
Node replaceNode = successor(target);
if((null == replaceNode.rightNode) && (null == replaceNode.leftNode)){
deltetLeafNode(target, replaceNode);
}else{
target.key = replaceNode.key;
target.value = replaceNode.value;
dleOneChildNode(replaceNode);
}
}
private void deltetLeafNode(Node target, Node replaceNode){
target.key = replaceNode.key;
target.value = replaceNode.value;
deleteLeafFix(replaceNode);
if(replaceNode == replaceNode.parentNode.rightNode){
replaceNode.parentNode.rightNode = null;
}else{
replaceNode.parentNode.leftNode = null;
}
}
//找后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"
private Node successor(Node node) {
if (node == null){
return null;
}
if (null != node.rightNode) { // 获取 后继节点
Node p = node.rightNode;
while (null != p.leftNode){
p = p.leftNode;
}
return p;
} else {
Node p = node.parentNode;
Node ch = node;
while (p != null && ch == p.rightNode) {
ch = p;
p = p.parentNode;
}
return p;
}
}
public static void main(String[] args) {
RedBlackTree<Integer, String> bst = new RedBlackTree<Integer, String>();
bst.put(100, "v100");
bst.put(50, "v50");
bst.put(150, "v150");
bst.put(20, "v20");
bst.put(85, "v85");
bst.put(10, "v10");
bst.put(15, "a15");
bst.put(75, "v75");
bst.put(95, "v95");
bst.put(65, "v65");
bst.put(76, "v76");
bst.put(60, "v60");
bst.put(66, "v66");
bst.put(61, "v61");
// 当前节点是左节点 的 5中情况
//bst.delete(15); // 1. 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
// 2. 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的
//bst.put(140, "v140");
//bst.delete(95);
// 4. 兄弟节点是黑色的,且没有子节点
//bst.delete(66);
//5. 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
//bst.delete(95);
//bst.delete(15);
System.out.println(bst.getRoot());
}