AVL树、红黑树

from:http://blog.csdn.net/liyong199012/article/details/29219261

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 “An algorithm for the organization of information” 中发表了它。

一、AVL树的旋转规律

      AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法。但是要进行预先或随后做一次或多次所谓的”AVL旋转”。

      假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行进行的规律可归纳为下列四种情况:

1. LL型

    平衡二叉树某一节点的左孩子的左子树上插入一个新的节点,使得该节点不再平衡。这时只需要把树向右旋转一次即可,如图所示,原A的左孩子B变为父结点,A变为其右孩子,而原B的右子树变为A的左子树,注意旋转之后Brh是A的左子树(图上忘在A于Brh之间标实线)

《AVL树、红黑树》

2. RR型

    平衡二叉树某一节点的右孩子的右子树上插入一个新的节点,使得该节点不再平衡。这时只需要把树向左旋转一次即可,如图所示,原A右孩子B变为父结点,A变为其左孩子,而原B的左子树Blh将变为A的右子树。

《AVL树、红黑树》

3. LR型

      平衡二叉树某一节点的左孩子的右子树上插入一个新的节点,使得该节点不再平衡。这时需要旋转两次,仅一次的旋转是不能够使二叉树再次平衡。如图所示,在B节点按照RR型向左旋转一次之后,二叉树在A节点仍然不能保持平衡,这时还需要再向右旋转一次。

《AVL树、红黑树》


4. RL型

      平衡二叉树某一节点的右孩子的左子树上插入一个新的节点,使得该节点不再平衡。同样,这时需要旋转两次,旋转方向刚好同LR型相反。

《AVL树、红黑树》

二、AVL树的基本操作

1.插入

      向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。 

     在平衡的的二叉排序树Balanced BST上插入一个新的数据元素e的递归算法可描述如下:

           若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增1;

          若e的关键字和BBST的根结点的关键字相等,则不进行;

          若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度,则将根结点的平衡因子更改为 0,BBST的深度不变; BBST的根结点的平衡因子为0(左、右子树的深度相等):则将根结点的平衡因子更改为1,BBST的深度增1;BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根结点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变;若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的 右子树深度增加(+1)时,分别就不同情况处理之。


2.删除


     从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。

     删除操作需要考虑的情况较多,具体见代码实现吧。

3.查找


     在AVL树中查找同在一般BST完全一样的进行,所以耗费 O(log n) 时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查询而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)

三、代码实现

    时间仓促,对于插入、删除操作没有就各种情况配上插图,代码里面有一些注释,可以对着代码理解。日后再研究这个的时候定配上插图。

package ly.dataStructures.tree;
import java.util.Comparator;
/**
 * AVL树
 * @author 无间道风云
 * 2014.0526
 * @param <AnyType>
 */
public class AvlTree<AnyType extends Comparable<? super AnyType>> {
	private AvlNode<AnyType> root;
	private Comparator<? super AnyType> cmp;	
	/*********  AVL树节点数据结构定义   **********/
	private static class AvlNode<AnyType>{
		AnyType element;
		AvlNode<AnyType> left;
		AvlNode<AnyType> right;
		int height;		
		AvlNode(AnyType theElement){
			this(theElement, null, null);
		}		
		AvlNode(AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt){
			element = theElement;
			left = lt;
			right = rt;
			height = 0;
		}
	}	
	public AvlTree(){
		root = null;
	}	
	public void makeEmpty(){
		root = null;
	}	
	public boolean isEmpty(){
		return root == null;		
	}	
	public void insert(AnyType element){
		root = insert(element, root);			
	}	
	public boolean contains(AnyType x){
		return contains(x, root);
	}	
	public void remove(AnyType element){
		root = remove(element, root);
	}	
	private int myCompare(AnyType lhs, AnyType rhs){
		if(cmp != null)
			return cmp.compare(lhs, rhs);
		else
			return ((Comparable)lhs).compareTo(rhs);
	}	
	private boolean contains(AnyType x, AvlNode<AnyType> t){
		//空树处理
		if(t == null)
			return false;
		//正常情况处理
		//@方式一:对Comparable型的对象进行比较
		//int compareResult = x.compareTo(t.element);
		//@方式二:使用一个函数对象而不是要求这些项是Comparable的
		int compareResult = myCompare(x, t.element);
		if(compareResult < 0)
			return contains(x, t.left);
		else if(compareResult > 0)
			return contains(x, t.right);
		else
			return true;
	}	
	private int height(AvlNode<AnyType> t){
		return t == null ? -1 : t.height;
	}	
	private AvlNode<AnyType> findMin(AvlNode<AnyType> t){
		if(t == null)
			return null;
		if(t.left == null)
			return t;
		return findMin(t.left);
	}	
	private AvlNode<AnyType> findMax(AvlNode<AnyType> t){
		if(t == null)
			return null;
		if(t.right == null)
			return t;
		return findMax(t.right);
	}	
	private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t){
		if(t == null)
			return new AvlNode<AnyType>(x, null, null);		
		int compareResult = myCompare(x, t.element);
		if(compareResult < 0){
			t.left = insert(x, t.left);
			if(height(t.left)-height(t.right) == 2){
				if(myCompare(x, t.left.element) < 0)		//左左情况
					t = rotateWithLeftChild(t);
				else									//左右情况
					t = doubleWithLeftChild(t);
			}
		}else if(compareResult > 0){
			t.right = insert(x, t.right);
			if(height(t.right)-height(t.left) == 2){
				if(myCompare(x, t.right.element) < 0)		//右左情况
					t = doubleWithRightChild(t);
				else										//右右情况
					t = rotateWithRightChild(t);
			}
		}		
		//完了之后更新height值
		t.height = Math.max(height(t.left), height(t.right))+1;
		return t;
	}	
	private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t){
		if(t == null)
			return null;
		int compareResult = myCompare(x, t.element);
		if(compareResult < 0){
			t.left = remove(x, t.left);
			//完了之后验证该子树是否平衡
			if(t.right != null){		//若右子树为空,则一定是平衡的,此时左子树相当对父节点深度最多为1, 所以只考虑右子树非空情况
				if(t.left == null){		//若左子树删除后为空,则需要判断右子树
					if(height(t.right)-t.height == 2){
						AvlNode<AnyType> k = t.right;
						if(k.right != null){		//右子树存在,按正常情况单旋转
							System.out.println("-----------------------------------------------------------------------------11111");
							t = rotateWithRightChild(t);
						}else{						//否则是右左情况,双旋转				
							System.out.println("-----------------------------------------------------------------------------22222");
							t = doubleWithRightChild(t);
						}
					}
				}else{					//否则判断左右子树的高度差
					//左子树自身也可能不平衡,故先平衡左子树,再考虑整体
					AvlNode<AnyType> k = t.left;
					//删除操作默认用右子树上最小节点补删除的节点
					//k的左子树高度不低于k的右子树
					if(k.right != null){
						if(height(k.left)-height(k.right) == 2){
							AvlNode<AnyType> m = k.left;
							if(m.left != null){		//左子树存在,按正常情况单旋转
								System.out.println("-----------------------------------------------------------------------------33333");
								k = rotateWithLeftChild(k);
							}else{						//否则是左右情况,双旋转				
								System.out.println("-----------------------------------------------------------------------------44444");
								k = doubleWithLeftChild(k);								
							}
						}
					}else{
						if(height(k.left) - k.height ==2){
							AvlNode<AnyType> m = k.left;
							if(m.left != null){		//左子树存在,按正常情况单旋转
								System.out.println("-----------------------------------------------------------------------------hhhhh");
								k = rotateWithLeftChild(k);
							}else{						//否则是左右情况,双旋转				
								System.out.println("-----------------------------------------------------------------------------iiiii");
								k = doubleWithLeftChild(k);
							}
						}
					}					
					if(height(t.right)-height(t.left) == 2){
						//右子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
						System.out.println("-----------------------------------------------------------------------------55555");
						t = rotateWithRightChild(t);
					}
				}
			}
			//完了之后更新height值
			t.height = Math.max(height(t.left), height(t.right))+1;
		}else if(compareResult > 0){
			t.right = remove(x, t.right);
			//下面验证子树是否平衡
			if(t.left != null){			//若左子树为空,则一定是平衡的,此时右子树相当对父节点深度最多为1
				if(t.right == null){		//若右子树删除后为空,则只需判断左子树
					if(height(t.left)-t.height ==2){
						AvlNode<AnyType> k = t.left;
						if(k.left != null){
							System.out.println("-----------------------------------------------------------------------------66666");
							t = rotateWithLeftChild(t);
						}else{
							System.out.println("-----------------------------------------------------------------------------77777");
							t = doubleWithLeftChild(t);
						}
					}					
				}else{				//若右子树删除后非空,则判断左右子树的高度差
					//右子树自身也可能不平衡,故先平衡右子树,再考虑整体
					AvlNode<AnyType> k = t.right;
					//删除操作默认用右子树上最小节点(靠左)补删除的节点
					//k的右子树高度不低于k的左子树					
					if(k.left != null){
						if(height(k.right)-height(k.left) == 2){
							AvlNode<AnyType> m = k.right;
							if(m.right != null){		//右子树存在,按正常情况单旋转
								System.out.println("-----------------------------------------------------------------------------88888");
								k = rotateWithRightChild(k);
							}else{						//否则是右左情况,双旋转				
								System.out.println("-----------------------------------------------------------------------------99999");
								k = doubleWithRightChild(k);
							}
						}
					}else{
						if(height(k.right)-k.height == 2){
							AvlNode<AnyType> m = k.right;
							if(m.right != null){		//右子树存在,按正常情况单旋转
								System.out.println("-----------------------------------------------------------------------------aaaaa");
								k = rotateWithRightChild(k);
							}else{						//否则是右左情况,双旋转				
								System.out.println("-----------------------------------------------------------------------------bbbbb");
								k = doubleWithRightChild(k);
							}
						}
					}					
					if(height(t.left) - height(t.right) == 2){
						//左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
						System.out.println("-----------------------------------------------------------------------------ccccc");
						t = rotateWithLeftChild(t);			
					}
				}
			}
			//完了之后更新height值
			t.height = Math.max(height(t.left), height(t.right))+1;
		}else if(t.left != null && t.right != null){
			//默认用其右子树的最小数据代替该节点的数据并递归的删除那个节点
			t.element = findMin(t.right).element;
			t.right = remove(t.element, t.right);			
			if(t.right == null){		//若右子树删除后为空,则只需判断左子树与根的高度差
				if(height(t.left)-t.height ==2){
					AvlNode<AnyType> k = t.left;
					if(k.left != null){
						System.out.println("-----------------------------------------------------------------------------ddddd");
						t = rotateWithLeftChild(t);
					}else{
						System.out.println("-----------------------------------------------------------------------------eeeee");
						t = doubleWithLeftChild(t);
					}
				}					
			}else{				//若右子树删除后非空,则判断左右子树的高度差
				//右子树自身也可能不平衡,故先平衡右子树,再考虑整体
				AvlNode<AnyType> k = t.right;
				//删除操作默认用右子树上最小节点(靠左)补删除的节点
				
				if(k.left != null){
					if(height(k.right)-height(k.left) == 2){
						AvlNode<AnyType> m = k.right;
						if(m.right != null){		//右子树存在,按正常情况单旋转
							System.out.println("-----------------------------------------------------------------------------fffff");
							k = rotateWithRightChild(k);
						}else{						//否则是右左情况,双旋转				
							System.out.println("-----------------------------------------------------------------------------ggggg");
							k = doubleWithRightChild(k);
						}
					}	
				}else{
					if(height(k.right)-k.height == 2){
						AvlNode<AnyType> m = k.right;
						if(m.right != null){		//右子树存在,按正常情况单旋转
							System.out.println("-----------------------------------------------------------------------------hhhhh");
							k = rotateWithRightChild(k);
						}else{						//否则是右左情况,双旋转				
							System.out.println("-----------------------------------------------------------------------------iiiii");
							k = doubleWithRightChild(k);
						}
					}	
				}
				//左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
				if(height(t.left) - height(t.right) == 2){
					System.out.println("-----------------------------------------------------------------------------jjjjj");
					t = rotateWithLeftChild(t);			
				}
			}
			//完了之后更新height值
			t.height = Math.max(height(t.left), height(t.right))+1;
		}else{
			System.out.println("-----------------------------------------------------------------------------kkkkk");
			t = (t.left != null)?t.left:t.right;		
		}
		return t;		
	}	
	//左左情况单旋转
	private AvlNode<AnyType> rotateWithLeftChild(AvlNode<AnyType> k2){
		AvlNode<AnyType> k1 = k2.left;
		k2.left = k1.right;		
		k1.right = k2;		
		k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
		k1.height = Math.max(height(k1.left), k2.height) + 1;		
		return k1;		//返回新的根
	}	
	//右右情况单旋转
	private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> k2){
		AvlNode<AnyType> k1 = k2.right;
		k2.right = k1.left;
		k1.left = k2;		
		k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
		k1.height = Math.max(height(k1.right), k2.height) + 1;		
		return k1;		//返回新的根	
	}	
	//左右情况
	private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> k3){		
		try{
			k3.left = rotateWithRightChild(k3.left);
		}catch(NullPointerException e){
			System.out.println("k.left.right为:"+k3.left.right);
			throw e;
		}
		return rotateWithLeftChild(k3);		
	}	
	//右左情况
	private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> k3){
		try{
			k3.right = rotateWithLeftChild(k3.right);
		}catch(NullPointerException e){
			System.out.println("k.right.left为:"+k3.right.left);
			throw e;
		}		
		return rotateWithRightChild(k3);
	}
}
/*注明:由于删除操作考虑的情况甚多,代码中出现的打印信息主要为方便排错*/

二、红黑树

为什么选择红黑树作为底层实现

红黑树是一种类平衡树, 但它不是高度的平衡树, 但平衡的效果已经很好了. 补充说明另一种 AVL 树, 我之前的博文: 《编程珠玑,字字珠玑》读书笔记完结篇——AVL树


STL map , linux (这个说大了), 他们都有红黑树的应用. 当你对搜索的效率要求较高,并且数据经常改动的情景,你可以用红黑树, 也就是 map.


至于, 为什么不用 AVL 树作为底层实现, 那是因为 AVL 树是高度平衡的树, 而每一次对树的修改, 都要 rebalance, 这里的开销会比红黑树大. 红黑树插入只要两次旋转, 删除至多三次旋转. 但不可否认的是, AVL 树搜索的效率是非常稳定的. 选取红黑树, 我认为是一种折中的方案.

1. 如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度。

2. 其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。

3. map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。


红黑树并没有我们想象的那么难 上、下两篇已经完成, 希望能帮助到大家.

红黑树并没有想象的那么难, 初学者觉得晦涩难读可能是因为情况太多. 红黑树的情况可以通过归结, 通过合并来得到更少的情况, 如此可以加深对红黑树的理解. 网络上的大部分红黑树的讲解因为没有「合并」. 红黑树的五个性质:

《AVL树、红黑树》

性质1. 节点是红色或黑色。

性质2. 根是黑色。

性质3. 所有叶子都是黑色(叶子是NIL节点)。

性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

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

红黑树的数据结构

摘自 sgi stl 红黑树数据结构定义:

typedef bool _Rb_tree_Color_type;
const _Rb_tree_Color_type _S_rb_tree_red = false;
const _Rb_tree_Color_type _S_rb_tree_black = true;

struct _Rb_tree_node_base
{
  typedef _Rb_tree_Color_type _Color_type;
  typedef _Rb_tree_node_base* _Base_ptr;

  _Color_type _M_color;
  _Base_ptr _M_parent;
  _Base_ptr _M_left;
  _Base_ptr _M_right;

  static _Base_ptr _S_minimum(_Base_ptr __x)
  {
    while (__x->_M_left != 0) __x = __x->_M_left;
    return __x;
  }

  static _Base_ptr _S_maximum(_Base_ptr __x)
  {
    while (__x->_M_right != 0) __x = __x->_M_right;
    return __x;
  }
};

template <class _Value>
struct _Rb_tree_node : public _Rb_tree_node_base
{
  typedef _Rb_tree_node<_Value>* _Link_type;
  _Value _M_value_field;
};

二叉搜索树的插入删除操作

在展开红黑树之前, 首先来看看普通二叉搜索树的插入和删除. 插入很容易理解, 比当前值大就往右走, 比当前值小就往左走. 详细展开的是删除操作.

二叉树的删除操作有一个技巧, 即在查找到需要删除的节点 X; 接着我们找到要么在它的左子树中的最大元素节点 M、要么在它的右子树中的最小元素节点 M, 并交换(M,X). 此时, M 节点必然至多只有一个孩子; 最后一个步骤就是用 M 的子节点代替 M 节点就完成了. 所以, 所有的删除操作最后都会归结为删除一个至多只有一个孩子的节点, 而我们删除这个节点后, 用它的孩子替换就好了. 将会看到 sgi stl map 就是这样的策略.

在红黑树删除操作讲解中, 我们假设代替 M 的节点是 N(下面的讲述不再出现 M).

红黑树的插入

插入新节点总是红色节点, 因为不会破坏性质 5, 尽可能维持所有性质.

假设, 新插入的节点为 N, N 节点的父节点为 P, P 的兄弟(N 的叔父)节点为 U, P 的父亲(N 的爷爷)节点为 G. 所以有如下的印象图:

《AVL树、红黑树》

插入节点的关键是:

  1. 插入新节点总是红色节点

  2. 如果插入节点的父节点是黑色, 能维持性质

  3. 如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质

插入算法详解如下, 走一遍红黑树维持其性质的过程:

第 0.0 种情况, N 为根节点, 直接 N->黑. over 第 0.1 种情况, N 的父节点为黑色, 这不违反红黑树的五种性质. over

第 1 种情况, N,P,U 都红(G 肯定黑). 策略: G->红, N,P->黑. 此时, G 红, 如果 G 的父亲也是红, 性质又被破坏了, HACK: 可以将 GPUN 看成一个新的红色 N 节点, 如此递归调整下去; 特俗的, 如果碰巧将根节点染成了红色, 可以在算法的最后强制 root->黑.

《AVL树、红黑树》

第 2 种情况, P 为红, N 为 P 右孩子, N 为红, U 为黑或缺少. 策略: 旋转变换, 从而进入下一种情况:

《AVL树、红黑树》

第 3 种情况, 可能由第二种变化而来, 但不是一定: P 为红, N 为 P 左孩子, N 为红. 策略: 旋转, 交换 P,G 颜色, 调整后, 因为 P 为黑色, 所以不怕 P 的父节点是红色的情况. over

《AVL树、红黑树》

红黑树的插入就为上面的三种情况. 你可以做镜像变换从而得到其他的情况.

红黑树的删除

假设 N 节点见上面普通二叉树删除中的定义, P 为 N 父节点, S 为 N 的兄弟节点, SL,SR 分别是 S 的左右子节点. 有如下印象图:

《AVL树、红黑树》 N 没有任何的孩子!

删除节点的关键是:

  1. 如果删除的是红色节点, 不破坏性质

  2. 如果删除的是黑色节点, 那么这个路径上就会少一个黑色节点, 破坏了性质. 故删除算法就是通过重新着色或旋转, 来维持性质

删除算法详解如下, 走一遍红黑树维持其性质的过程:

第 0.0 情况, N 为根节点. over 第 0.1 情况, 删除的节点为红. over 第 0.2 情况, 删除节点为黑, N 为红. 策略: N->黑, 重新平衡. over

第 1 情况, N,P,S,SR,SL 都黑. 策略: S->红. 通过 PN,PS 的黑色节点数量相同了, 但会比其他路径多一个, 解决的方法是在 P 上从情况 0 开始继续调整. 为什么要这样呢? HANKS: 因为既然 PN,PS 路径上的黑节点数量相同而且比其他路径会少一个黑节点, 那何不将其整体看成了一个 N 节点! 这是递归原理.

《AVL树、红黑树》

第 2 情况, S 红, 根据红黑树性质 P,SL,SR 一定黑. 策略: 旋转, 交换 P,S 颜色. 处理后关注的范围缩小, 下面的情况对应下面的框图, 算法从框图重新开始, 进入下一个情况:

《AVL树、红黑树》

第 2.1 情况, S,SL,SR 都黑. 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点, 通过 S 的黑节点个数不变, 所以维持了性质 5. over. 将看到, sgi stl map 源代码中将第 2.1 和第 1 情况合并成一种情况, 下节展开.

《AVL树、红黑树》

第 2.2.1 情况, S,SR 黑, SL 红. 策略: 旋转, 变换 SL,S 颜色. 从而又进入下一种情况:

《AVL树、红黑树》 第 2.2.2 情况, S 黑, SR 红. 策略: 旋转, 交换 S,P 颜色, SR->黑色, 重新获得平衡.

《AVL树、红黑树》

上面情况标号 X.X.X 并不是说这些关系是嵌套的, 只是这样展开容易理解. 此时, 解释三个地方:

  1. 通过 N 的黑色节点数量多了一个

  2. 通过 SL 的黑色节点数量不变

  3. 通过 SR 的黑色节点数量不变

红黑树删除重新调整伪代码如下:

// 第 0.0 情况, N 为根节点. over
if N.parent == NULL:
    return;

// 第 0.1 情况, 删除的节点为红. over
if color == RED:
    return;

// 第 0.2 情况, 删除节点为黑, N 为红, 简单变换: N->黑, 重新平衡. over
if color == BLACK && N.color == RED:
    N.color = BLACK;

// 第 1 种情况, N,P,S,SR,SL 都黑. 策略: S->红. 通过 N,S 的黑色节点数量相同了, 但会比其他路径多一个, 解决的方法是在 P 上从情况 0 开始继续调整.
if N,P,S,SR,SL.color == BLACK:
    S.color = RED;

    // 调整节点关系
    N = P
    N.parent = P.parent
    S = P.paernt.another_child
    SL = S.left_child
    SR = S.right_child
    continue;

// 第 2 情况, S 红, 根据红黑树性质 P,SR,SL 一定黑. 旋转, 交换 P,S 颜色. 此时关注的范围缩小, 下面的情况对应下面的框图, 算法从框图重新开始.
if S.color == RED:
    rotate(P);
    swap(P.color,S.color);

    // 调整节点关系
    S = P.another_child
    SL = S.left_child
    SR = S.right_child

// 第 2.1 情况, S,SL,SR 都黑. 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点, 通过 S 的黑节点个数不变, 所以维持了性质 5. over. 将看到, sgi stl map 源代码中将第 2.1 和第 1 情况合并成一种情况, 下节展开.
if S,SL,SR.color == BLACK:
    P.color = BLACK;
    S.color = RED;
    return

// 第 2.2.1 情况, S,SR 黑, SL 红. 策略: 旋转, 变换 SL,S 颜色. 从而又进入下一种情况:
if  S,SR.color == BLACK && SL.color == RED:
    rotate(P);
    swap(S.color,SL.color);

    // 调整节点关系
    S = SL
    SL = S.left_child
    SR = S.right_child

// 第 2.2.2 情况, S 黑, SR 红. 策略: 旋转, 交换 S,P 颜色.
if S.color == BLACK && SR.color == RED:
    rotate(P);
    swap(P.color,S.color);
    return;

总结

所以, 插入的情况只有三种, 删除的情况只有两种. 上面的分析, 做镜像处理, 就能得到插入删除的全部算法, 脑补吧. 从上面的分析来看, 红黑树具有以下特性: 插入删除操作都是 0(lnN), 且最多旋转三次.

下节中会重点展开 sgi stl map 的源代码.

参考文档: wikipedia

捣乱 2013-9-25

http://daoluan.net

红黑树并没有我们想象的那么难(下)

红黑树并没有我们想象的那么难 上、下两篇已经完成, 希望能帮助到大家.

SGI STL map 实现概述

根据上一节的红黑树分析, 结合 sgi stl map 的实现, 看看红黑树的源码是如何实现的. 以下主要以代码的注释为主.

sgi stl map 底层实现是 _Rb_tree类, 为了方便管理, _Rb_tree 内置了 _M_header, 用于记录红黑树中的根节点, 最小节点和最大节点. 在插入删除中都会对其进行维护. 找到一副美艳的图片:

《AVL树、红黑树》

我只会展开插入和删除的代码. _Rb_tree 有 insert_unique() 和 insert_equal() 两种, 前者不允许有重复值, 后者可以. insert_unique() 判断是否有重复值的方法利用了二叉搜索树的性质. 细节请参看下面的代码.

为什么选择红黑树作为底层实现

红黑树是一种类平衡树, 但它不是高度的平衡树, 但平衡的效果已经很好了. 补充说明另一种 AVL 树, 我之前的博文: 《编程珠玑,字字珠玑》读书笔记完结篇——AVL树

用过 STL map 么, 你用过 linux 么(这个说大了), 他们都有红黑树的应用. 当你对搜索的效率要求较高,并且数据经常改动的情景,你可以用红黑树, 也就是 map.

至于, 为什么不用 AVL 树作为底层实现, 那是因为 AVL 树是高度平衡的树, 而每一次对树的修改, 都要 rebalance, 这里的开销会比红黑树大. 红黑树插入只要两次旋转, 删除至多三次旋转. 但不可否认的是, AVL 树搜索的效率是非常稳定的. 选取红黑树, 我认为是一种折中的方案.

红黑树源代码剖析

// sgi stl _Rb_tree 插入算法 insert_equal() 实现.
// 策略概述: insert_equal() 在红黑树找到自己的位置,
// 然后交由 _M_insert() 来处理接下来的工作.
// _M_insert() 会将节点插入红黑树中, 接着调整红黑树,
// 维持性质.
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_equal(const _Value& __v)
{
  // 在红黑树中有头结点和根节点的概念, 头结点位于根节点之上,
  // 头结点只为管理而存在, 根节点是真正存储数据的地方. 头结点和根节点互为父节点,
   // 是一种实现的技巧.
  _Link_type __y = _M_header; // 指向头结点
  _Link_type __x = _M_root(); // _M_header->_M_parent, 即指向根节点

  // 寻找插入的位置
  while (__x != 0) {
    __y = __x;

    // 小于当前节点要走左边, 大于等于当前节点走右边
    __x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ?
            _S_left(__x) : _S_right(__x);
  }
  // __x 为需要插入的节点的位置, __y 为其父节点
  return _M_insert(__x, __y, __v);
}

// sgi stl _Rb_tree 插入算法 insert_unique() 实现.
// 策略概述: insert_unique() 同样也在红黑树中找到自己的位置; 我们知道,
// 如果小于等于当前节点会往右走, 所以遇到一个相同键值的节点后, 会往右走一步,
// 接下来一直往左走, 所以下面的实现会对往左走的情况做特殊的处理.
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
pair<typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator,
     bool>
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_unique(const _Value& __v)
{
  _Link_type __y = _M_header; // 指向头结点
  _Link_type __x = _M_root(); // 指向根节点, 可能为空
  bool __comp = true;

  // 寻找插入的位置
  while (__x != 0) {
    __y = __x;
    __comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x));

    // 小于当前节点要走左边, 大于等于当前节点走右边
    __x = __comp ? _S_left(__x) : _S_right(__x);
  }

  iterator __j = iterator(__y); // 在 __y 上建立迭代器

  // 我认为下面判断树中是否有存在键值的情况有点绕,
  // 它充分利用了二叉搜索树的性质, 如此做很 hack, 但不易理解.
  // 要特别注意往左边插入的情况.

  // HACKS:
  // 下面的 if 语句是比 __x 小走左边的情况: 会发现, 如果插入一个已存在的键的话,
  // __y 最终会定位到已存在键的右子树的最左子树.
  // 譬如, 红黑树中已经存在一个键为 100 的节点, 其右孩子节点为 101,
  // 此时如果再插入键为 100 的节点, 因为 100<=100, 所以会往右走到达 101 节点,
  // 有 100<101, 继而往左走, 会一直往左走.大家稍微画一个例子就能理解.
  if (__comp)
    // 特殊情况, 如果 __j 指向了最左孩子, 那么肯定要插入新节点.
    if (__j == begin())
      return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
    // 其他情况, 这个时候也是往左边插入, 如果存在重复的键值,
    // 那么 --__j 能定位到此重复的键的节点.
    else
      --__j;

  // HACKS: 这里比较的是 __j 和 __v, 如果存在键值, 那么 __j == __v,
  // 会跳过 if 语句. 否则执行插入. 也就是说如果存在重复的键, 那么 __j
  // 的值肯定是等于 __v
  if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))
    return pair<iterator,bool>(_M_insert(__x, __y, __v), true);

  // 此时 __y.value = __v, 不允许插入, 返回键值所在位置
  return pair<iterator,bool>(__j, false);
}

// _M_insert() 是真正执行插入的地方.
// 策略概述: 插入策略已经在上篇中详述, 可以根据上篇文章的描述,
// 和下面代码的注释, 加深对红黑树插入算法里理解
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v)
{
  _Link_type __x = (_Link_type) __x_; // 新节点插入的位置.
  // 关于 __x 的疑问:
  // 1. 它被放到下面的, 第一个 if 语句中, 我觉得是没有必要的,
  // 因为从调用 _M_insert() 的函数来看, __x 总是为空.
  // 2. 既然 __x 是新节点插入的位置, 那么为什么不直接在 __x 上创建节点,
  // 还要在下面通过比较来决定新节点是左孩子还是右孩子;
  // 不如直接用指针的指针或者指针的引用来完成, 省去了下面的判断.

  _Link_type __y = (_Link_type) __y_; // 新节点的父节点
  _Link_type __z; // 新节点的位置

  if (__y == _M_header || __x != 0 ||
      _M_key_compare(_KeyOfValue()(__v), _S_key(__y))) {
  // 新节点应该为左孩子
    __z = _M_create_node(__v);
    _S_left(__y) = __z;               // also makes _M_leftmost() = __z
                                      //    when __y == _M_header
    if (__y == _M_header) {
      _M_root() = __z;
      _M_rightmost() = __z;
    }
    else if (__y == _M_leftmost())
      _M_leftmost() = __z;   // maintain _M_leftmost() pointing to min node
  }
  // 新节点应该为右孩子
  else {
    __z = _M_create_node(__v);
    _S_right(__y) = __z;
    if (__y == _M_rightmost())
      _M_rightmost() = __z;  // maintain _M_rightmost() pointing to max node
  }
  _S_parent(__z) = __y;
  _S_left(__z) = 0;
  _S_right(__z) = 0;

  // 重新调整
  _Rb_tree_rebalance(__z, _M_header->_M_parent);

  // 更新红黑树节点数
  ++_M_node_count;

  // 返回迭代器类型
  return iterator(__z);
}

// 插入新节点后, 可能会破坏红黑树性质, _Rb_tree_rebalance() 负责维持性质.
// 其中:
// __x 新插入的节点
// __root 根节点
// 策略概述: 红黑树插入重新调整的策略已经在上篇中讲述,
// 可以结合上篇文章和这里的代码注释,
// 理解红黑树的插入算法.
inline void
_Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  // 将新插入的节点染成红色
  __x->_M_color = _S_rb_tree_red;

  while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {
    // __x 的父节点也是红色的情况. 提示: 如果是黑色节点, 不会破坏红黑树性质.

    if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {
      // 叔父节点
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;

      if (__y && __y->_M_color == _S_rb_tree_red) {
        // 第 1 种情况, N,P,U 都红(G 肯定黑).
        // 策略: G->红, N,P->黑. 此时, G 红, 如果 G 的父亲也是红, 性质又被破坏了,
        // HACK: 可以将 GPUN 看成一个新的红色 N 节点, 如此递归调整下去;
        // 特俗的, 如果碰巧将根节点染成了红色, 可以在算法的最后强制 root->红.
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {

        if (__x == __x->_M_parent->_M_right) {
        // 第 2 种情况, P 为红, N 为 P 右孩子, U 为黑或缺少.
        // 策略: 旋转变换, 从而进入下一种情况:
          __x = __x->_M_parent;
          _Rb_tree_rotate_left(__x, __root);
        }
        // 第 3 种情况, 可能由第二种变化而来, 但不是一定: P 为红, N 为红.
        // 策略: 旋转, 交换 P,G 颜色, 调整后, 因为 P 为黑色, 所以不怕
        // P 的父节点是红色的情况. over
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
      }
    }
    else { // 下面的代码是镜像得出的, 脑补吧.
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
      if (__y && __y->_M_color == _S_rb_tree_red) {
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_left) {
          __x = __x->_M_parent;
          _Rb_tree_rotate_right(__x, __root);
        }
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
      }
    }
  }
  __root->_M_color = _S_rb_tree_black;
}

// 删除算法, 直接调用底层的删除实现 _Rb_tree_rebalance_for_erase().
template <class _Key, class _Value, class _KeyOfValue,
          class _Compare, class _Alloc>
inline void _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::erase(iterator __position)
{
  _Link_type __y =
    (_Link_type) _Rb_tree_rebalance_for_erase(__position._M_node,
                                              _M_header->_M_parent,
                                              _M_header->_M_left,
                                              _M_header->_M_right);
  destroy_node(__y);
  --_M_node_count;
}

// 删除节点底层实现, 删除可能会破坏红黑树性质,
// _Rb_tree_rebalance()
// 负责维持性质. 其中:
// __z 需要删除的节点
// __root 根节点
// __leftmost 红黑树内部数据, 即最左子树
// __rightmost 红黑树内部数据, 即最右子树
// 策略概述: _Rb_tree_rebalance_for_erase() 会根据
// 删除节点的位置在红黑树中找到顶替删除节点的节点,
// 即无非是删除节点左子树的最大节点或右子树中的最小节点,
// 此处用的是有一种策略. 接着, 会调整红黑树以维持性质.
// 调整的算法已经在上篇文章中详述, 可以根据上篇文章的描述
// 和此篇的代码注释, 加深对红黑树删除算法的理解.
inline _Rb_tree_node_base*
_Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z,
                             _Rb_tree_node_base*& __root,
                             _Rb_tree_node_base*& __leftmost,
                             _Rb_tree_node_base*& __rightmost)
{
  // __z 是要删除的节点

  // __y 最终会指向要删除的节点
  _Rb_tree_node_base* __y = __z;
  // N 节点
  _Rb_tree_node_base* __x = 0;
  // 记录 N 节点的父节点
  _Rb_tree_node_base* __x_parent = 0;

  // 只有一个孩子或者没有孩子的情况
  if (__y->_M_left == 0)     // __z has at most one non-null child. y == z.
    __x = __y->_M_right;     // __x might be null.
  else
    if (__y->_M_right == 0)  // __z has exactly one non-null child. y == z.
      __x = __y->_M_left;    // __x is not null.

    // 有两个非空孩子
    else {                   // __z has two non-null children.  Set __y to
      __y = __y->_M_right;   //   __z's successor.  __x might be null.

      // __y 取右孩子中的最小节点, __x 记录他的右孩子(可能存在右孩子)
      while (__y->_M_left != 0)
        __y = __y->_M_left;
      __x = __y->_M_right;
    }

  // __y != __z 说明有两个非空孩子的情况,
  // 此时的删除策略就和文中提到的普通二叉搜索树删除策略一样:
  // __y 记录了 __z 右子树中最小的节点
  // __x 记录了 __y 的右孩子
  // 用 __y 顶替 __z 的位置, __x 顶替 __y 的位置, 最后用 __y 指向 __z,
  // 从而 __y 指向了要删除的节点
  if (__y != __z) {          // relink y in place of z.  y is z's successor

    // 将 __z 的记录转移至 __y 节点
    __z->_M_left->_M_parent = __y;
    __y->_M_left = __z->_M_left;

    // 如果 __y 不是 __z 的右孩子, __z->_M_right 有左孩子
    if (__y != __z->_M_right) {

      __x_parent = __y->_M_parent;

      // 如果 __y 有右孩子 __x, 必须有那个 __x 替换 __y 的位置
      if (__x)
        // 替换 __y 的位置
        __x->_M_parent = __y->_M_parent;

      __y->_M_parent->_M_left = __x;      // __y must be a child of _M_left
      __y->_M_right = __z->_M_right;
      __z->_M_right->_M_parent = __y;
    }
    // __y == __z->_M_right
    else
      __x_parent = __y;

    // 如果 __z 是根节点
    if (__root == __z)
      __root = __y;

    // __z 是左孩子
    else if (__z->_M_parent->_M_left == __z)
      __z->_M_parent->_M_left = __y;

    // __z 是右孩子
    else
      __z->_M_parent->_M_right = __y;

    __y->_M_parent = __z->_M_parent;
    // 交换需要删除节点 __z 和 替换节点 __y 的颜色
    __STD::swap(__y->_M_color, __z->_M_color);
    __y = __z;
    // __y now points to node to be actually deleted
  }
  // __y == __z 说明至多一个孩子
  else {                        // __y == __z
    __x_parent = __y->_M_parent;
    if (__x) __x->_M_parent = __y->_M_parent;

    // 将 __z 的父亲指向 __x
    if (__root == __z)
      __root = __x;
    else
      if (__z->_M_parent->_M_left == __z)
        __z->_M_parent->_M_left = __x;
      else
        __z->_M_parent->_M_right = __x;

    // __leftmost 和 __rightmost 是红黑树的内部数据, 因为 __z 可能是
    // __leftmost 或者 __rightmost, 因此需要更新.
    if (__leftmost == __z)
      if (__z->_M_right == 0)        // __z->_M_left must be null also
        // __z 左右孩子都为空, 没有孩子
        __leftmost = __z->_M_parent;
    // makes __leftmost == _M_header if __z == __root
      else
        __leftmost = _Rb_tree_node_base::_S_minimum(__x);

    if (__rightmost == __z)
      if (__z->_M_left == 0)         // __z->_M_right must be null also
        __rightmost = __z->_M_parent;
    // makes __rightmost == _M_header if __z == __root
      else                      // __x == __z->_M_left
        __rightmost = _Rb_tree_node_base::_S_maximum(__x);

    // __y 同样已经指向要删除的节点
  }

  // __y 指向要删除的节点
  // __x 即为 N 节点
  // __x_parent 指向 __x 的父亲, 即 N 节点的父亲
  if (__y->_M_color != _S_rb_tree_red) {
    // __y 的颜色为黑色的时候, 会破坏红黑树性质

    while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black))
      // __x 不为红色, 即为空或者为黑. 提示: 如果 __x 是红色, 直接将 __x 替换成黑色

      if (__x == __x_parent->_M_left) { // 如果 __x 是左孩子

        _Rb_tree_node_base* __w = __x_parent->_M_right; // 兄弟节点

        if (__w->_M_color == _S_rb_tree_red) {
          //第 2 情况, S 红, 根据红黑树性质P,SL,SR 一定黑.
          // 策略: 旋转, 交换 P,S 颜色.

          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red; // 交换颜色
          _Rb_tree_rotate_left(__x_parent, __root); // 旋转
          __w = __x_parent->_M_right; // 调整关系
        }

        if ((__w->_M_left == 0 ||
             __w->_M_left->_M_color == _S_rb_tree_black) &&
            (__w->_M_right == 0 ||
             __w->_M_right->_M_color == _S_rb_tree_black)) {
          // 提示: 这是 第 1 情况和第 2.1 情况的合并, 因为处理的过程是一样的.
          // 但他们的情况还是要分门别类的. 已经在文章中详细支出,
          // 似乎大多数的博文中没有提到这一点.

          // 第 1 情况, N,P,S,SR,SL 都黑.
          // 策略: S->红. 通过 PN,PS 的黑色节点数量相同了, 但会比其他路径多一个,
          // 解决的方法是在 P 上从情况 0 开始继续调整.
          // 为什么要这样呢? HACKS: 因为既然 PN,PS
          // 路径上的黑节点数量相同而且比其他路径会少一个黑节点,
          // 那何不将其整体看成了一个 N 节点! 这是递归原理.

          // 第 2.1 情况, S,SL,SR 都黑.
          // 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点,
          // 通过 S 的黑节点个数不变, 所以维持了性质 5. over

          // 可能大家会有疑问, 不对啊, 2.1 的情况,
          // 策略是交换父节点和兄弟节点的颜色, 此时怎么没有对父节点的颜色赋值呢?
          // HACKS: 这就是合并情况的好处, 因为就算此时父节点是红色,
          // 而且也将兄弟节点颜色改为红色, 你也可以将 PS,PN 看成一个红色的 N 节点,
          // 这样在下一个循环当中, 这个 N 节点也会变成黑色. 因为此函数最后有一句话:
          // if (__x) __x->_M_color = _S_rb_tree_black;
          // 合并情况, 节省代码量

          // 当然是可以分开写的

          // 兄弟节点染成红色
          __w->_M_color = _S_rb_tree_red;

          // 调整关系
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_right == 0 ||
              __w->_M_right->_M_color == _S_rb_tree_black) {
            // 第 2.2.1 情况, S,SR 黑, SL 红.
            // 策略: 旋转, 变换 SL,S 颜色.

            if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_right(__w, __root);

            // 调整关系
            __w = __x_parent->_M_right;
          }

          // 第 2.2.2 情况, S 黑, SR 红.
          // 策略: 旋转, 交换 S,P 颜色, SR->黑色, 重新获得平衡.
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_left(__x_parent, __root);
          break;
        }                        // 下面的代码是镜像得出的, 脑补吧.
      } else {                  // same as above, with _M_right <-> _M_left.
        _Rb_tree_node_base* __w = __x_parent->_M_left;
        if (__w->_M_color == _S_rb_tree_red) {
          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red;
          _Rb_tree_rotate_right(__x_parent, __root);
          __w = __x_parent->_M_left;
        }
        if ((__w->_M_right == 0 ||
             __w->_M_right->_M_color == _S_rb_tree_black) &&
            (__w->_M_left == 0 ||
             __w->_M_left->_M_color == _S_rb_tree_black)) {
          __w->_M_color = _S_rb_tree_red;
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_left == 0 ||
              __w->_M_left->_M_color == _S_rb_tree_black) {
            if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_left(__w, __root);
            __w = __x_parent->_M_left;
          }
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_right(__x_parent, __root);
          break;
        }
      }
    if (__x) __x->_M_color = _S_rb_tree_black;
  }
  return __y;
}

捣乱 2013-9-29

http://daoluan.net

《编程珠玑,字字珠玑》读书笔记完结篇——AVL树

2012-04-26 14:34 by 捣乱小子, 7523 阅读, 7 评论, 收藏编辑

写在最前面的

手贱翻开了《珠玑》的最后几章,所以这一篇更多是关于13、14、15章的内容。这篇文章的主要内容是“AVL树”,即平衡树,比红黑树低一个等次。捣乱真惹不起红黑树,情况很复杂;而AVL思路比较清晰。《编程珠玑,字字珠玑》910读书笔记——代码优化更新了,做了点关于“哨兵”的笔记。在这篇文章的末尾,笔者还加了对引用调用的“大彻大悟”。

4篇读书笔记:全在这里

AVL树

学习数据结构的时候,有过一次实验课, 题意大概:英文单词出现次数统计。当时选了哈希表,映射(map),AVL树(平衡树)三种方法来做,是冲着“完成实验老师请吃饭”《AVL树、红黑树》去做的。哈希表键值用“除留余数法”,处理冲突用了最简单的开哈希表的“链地址法”。 映射(map)没有深入,只是简单的应用。 比较痛心的是AVL树。

AVL树的旋转

树的旋转分四种:左单旋,右单旋,左右旋转,右左旋转。规定,右子树的高度减去左子树的高度得到此节点的平衡数(也叫平衡因子,balance factor,bf),用bf(node)表示node节点的平衡数。小剖一下这四种情况:

当bf(node)==2的时候,即右子树高度比左子树高,需要将树在node节点左单旋。在作旋转之后,左子树bf+1,右子树bf-1,node节点平衡数归零。 
《AVL树、红黑树》 
节点的调整过程很清晰。

 

再来当bf(node)==-2时候,即右子树比左子树低。需要将树在node节点右单旋。在作选择之后,左子树bf-1,右子树+1,node节点平衡树归零。 
《AVL树、红黑树》  
细心的发现,左单旋和右单旋是一样的,只是反过来罢了。

 

下面的情况复杂了点,但是他们是从上面两种情况延伸过来的,但是这种变化导致它们平衡化的方法也有小小不同。 下面两种情况从子树的内侧插入,导致子树(bf(kid))和其父亲(bf(parent))的bf正负相反,先来左右旋转,看图:

《AVL树、红黑树》 
解决之道:kid节点作简单的左单旋,然后parent作简单的右单旋。在过程中需要非常注意节点bf的调整,要分情况进行讨论(把这个槛跨过去,离成功就不远了)。

  • 如果从左kid的右子树(grandkid)的左侧插入, 
    对bf(kid)调整:那么bf(grandkid)<0,在kid作了左单旋之后,grandkid的左侧树被调整为kid的右子树,结果bf(kid)=0; 
    对bf(parent)调整:在对parent作了右单旋之后,grandkid右子树被调整为parent的左子树,因此如果bf(grandkid)<0,那么bf(parent)=1;  
  • 如果从左kid的右子树(grandkid)的右侧插入, 
    对bf(kid)调整:那么bf(grandkid)>0,在kid作了左单旋之后,grandkid的左侧树被调整为kid的右子树,结果bf(kid)=-1; 
    对bf(parent)调整:在对parent作了右单旋之后,grandkid右子树被调整为parent的左子树,因此如果bf(grandkid)<0,那么bf(parent)=0;
  • 对bf(grandkid)调整:最后,grandkid被调整为新树的根节点,bf(grandkid)=0。

(作一个填空题吧) 结合下面的图来做,属于右左旋转:

 
如果从右kid的左子树(grandkid)的左侧插入,
对bf(kid)调整:那么bf(grandkid)    
0,在kid作了左单旋之后,grandkid的左侧树被调整为kid的右子树,结果bf(kid)=    

对bf(parent)调整:
在对parent作了右单旋之后,grandkid右子树被调整为parent的左子树,因此如果bf(grandkid)    
0,那么bf(parent)=    
 
如果从右kid的左子树(grandkid)的
右侧插入,
对bf(kid)调整:那么bf(grandkid)    0,在kid作了左单旋之后,grandkid的左侧树被调整为kid的右子树,结果bf(kid)=    
对bf(parent)调整:在对parent作了右单旋之后,grandkid右子树被调整为parent的左子树,因此如果bf(grandkid)    0,那么bf(parent)=    

对bf(grandkid)调整:最后,grandkid被调整为新树的根节点,bf(grandkid)=    

答案:<,1,<,0;>,0,>,-1。

 

可以看出三个节点在调整过程中需要更改bf。最后一种旋转就是右左旋转。不需要太多的分析,跟上面的是一样的,做一个简单的反转。捣乱上图:

《AVL树、红黑树》

构造一个平衡树,即不断将一个新的节点在原树中找到合适的位置,然后调整。那么在“找”的过程中,所经历的节点bf都改变了(+1或者-1)。插入一个节点的做法是: 用栈存储所走过的节点,在找到插入位置后,从插入位置的父节点开始调整,如果此父节点是平衡的,那么从栈中取出父节点,继续调整。

从上面的分析中,只要旋转后,结果旋转的节点都会得到bf(node)=0结果,所以只要旋转后,我们的目的就达到了——树平衡了!所以bf(node)==0d的节点会越来越多,而且是堆积在树的顶层。

《AVL树、红黑树》

因此,不需要每次都调整到树的根节点root,只要调整的节点bf=0,就可以结束了,上面的节点或者兄弟节点已经bf=0。这我在刚接触AVL的时候也很迷惑的地方。

最后我把insert节点的代码给出:

expand source

另外,旋转的代码我放在附件里面(如果都贴出来显得很臃肿),再者,附件里有一个“单词统计”的实验报告,有兴趣的同学可以下载看看。当时做实验的时候,AVL统计单词还是挺给力的:

《AVL树、红黑树》

 

漫谈引用调用

注意:ANSI C里不支持引用调用,而C++提供了引用调用的实现。 
正如《effective c++》条款1提及的,指针和引用有应用上的区别。指针所指的对象可以随意更改,而且它的指向可以为null,非常灵活;但引用必须代表一个对象,不能为null,而且它被赋予某个对象后,它将始终代表那个对象知道被销毁为止。例如: 
 

expand source

a成为了b的引用,a将不能再引用其他数据。另外,引用变量是否占有内存听说唯有定义http://topic.csdn.net/u/20100622/15/728477fe-92ab-4e83-8572-0923d37186f1.html),笔者认为可行的方法是程序只在在变量的符号表中添加a,而并没有为a分配任何的内存。

在函数传参的过程中,有值传递,指针传递(都属于c)和引用传递方式(c++)。指针所能做到的,引用也可以做得到。但引用更安全(不至于让它为null),操作起来更方便,同时拥有和指针优点——“节能减排”。来看看:

expand source

在function返回后,a依旧为原来的NULL,并没有改变。因为你想,function函数栈内,只保存了指针a的原值NULL,即使a = new TYPE能为a赋予新址,但此a非彼a,在function退栈后,此a将被销毁,而彼a仍旧为NULL。因此如果想更改a指针的内容,必须使用指针的指针或者指针的引用,指针的引用会比较方便。

expand source

这时,指针a的值才有所改变。AVL树的程序里有较多的引用调用,读者要注意。捣乱纳闷,这笔记,这大彻大悟,应早在大一就应该写下,羞愧于心,贻笑大方呐。

关于珠玑的总结

珠玑我到底还是把它当作休闲读物了,对于算法或者数据结构的初学者,这一本是力荐的。

红黑树—java实现



概要

前面分别介绍红黑树的理论知识、红黑树的C语言和C++的实现。本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习 红黑树的理论知识 ,再来学习本章。还是那句老话,红黑树的C/C++/Java实现,原理一样,择其一了解即可。

目录 
1.红黑树的介绍 
2. 红黑树的Java实现(代码说明) 
3. 红黑树的Java实现(完整源码) 
4.红黑树的Java测试程序

转载请注明出处:

更多内容: 数据结构与算法系列 目录

(01)  红黑树(一)之 原理和算法详细介绍 
(02)  红黑树(二)之 C语言的实现 
(03)  红黑树(三)之 Linux内核中红黑树的经典实现 
(04)  红黑树(四)之 C++的实现  
(05)  红黑树(五)之 Java的实现

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。 
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。 
除了具备该特性之外,红黑树还包括许多额外的信息。

红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。 
红黑树的特性: 
(1) 每个节点或者是黑色,或者是红色。 
(2) 根节点是黑色。 
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!] 
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。 
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

关于它的特性,需要注意的是: 
第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。 
第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

《AVL树、红黑树》

红黑树的Java实现(代码说明)

红黑树的基本操作是 添加 、 删除 和 旋转 。在对红黑树进行添加或删除后,会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。 
旋转包括两种: 左旋 和 右旋 。下面分别对红黑树的基本操作进行介绍。

1. 基本定义

public class RBTree<T extends Comparable<T>> {  private RBTNode<T> mRoot;	// 根结点

 private static final boolean RED   = false;  private static final boolean BLACK = true;  public class RBTNode<T extends Comparable<T>> {   boolean color;		// 颜色
  T key;				// 关键字(键值)
  RBTNode<T> left;	// 左孩子
  RBTNode<T> right;	// 右孩子
  RBTNode<T> parent;	// 父结点

  public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {    this.key = key;    this.color = color;    this.parent = parent;    this.left = left;    this.right = right;   }  }  ... }

RBTree是红黑树对应的类,RBTNode是红黑树的节点类。在RBTree中包含了根节点mRoot和红黑树的相关API。 
注意:在实现红黑树API的过程中,我重载了许多函数。重载的原因,一是因为有的API是内部接口,有的是外部接口;二是为了让结构更加清晰。

2. 左旋

《AVL树、红黑树》

对x进行左旋,意味着”将x变成一个左节点”。

左旋的实现代码(Java语言)

/* * 对红黑树的节点(x)进行左旋转 * * 左旋示意图(对节点x进行左旋): * px px * / / * x y * / \ --(左旋)-. / \ # * lx y x ry * / \ / \ * ly ry lx ly * * */
private void leftRotate(RBTNode<T> x) {  // 设置x的右孩子为y
 RBTNode<T> y = x.right;  // 将 “y的左孩子” 设为 “x的右孩子”;  // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
 x.right = y.left;  if (y.left != null)   y.left.parent = x;  // 将 “x的父亲” 设为 “y的父亲”
 y.parent = x.parent;  if (x.parent == null) {   this.mRoot = y;			// 如果 “x的父亲” 是空节点,则将y设为根节点
 } else {   if (x.parent.left == x)    x.parent.left = y;	// 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
  else    x.parent.right = y;	// 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
 }    // 将 “x” 设为 “y的左孩子”
 y.left = x;  // 将 “x的父节点” 设为 “y”
 x.parent = y; }

3. 右旋

《AVL树、红黑树》

对y进行左旋,意味着”将y变成一个右节点”。

右旋的实现代码(Java语言)

/* * 对红黑树的节点(y)进行右旋转 * * 右旋示意图(对节点y进行左旋): * py py * / / * y x * / \ --(右旋)-. / \ # * x ry lx y * / \ / \ # * lx rx rx ry * */
private void rightRotate(RBTNode<T> y) {  // 设置x是当前节点的左孩子。
 RBTNode<T> x = y.left;  // 将 “x的右孩子” 设为 “y的左孩子”;  // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
 y.left = x.right;  if (x.right != null)   x.right.parent = y;  // 将 “y的父亲” 设为 “x的父亲”
 x.parent = y.parent;  if (y.parent == null) {   this.mRoot = x;			// 如果 “y的父亲” 是空节点,则将x设为根节点
 } else {   if (y == y.parent.right)    y.parent.right = x;	// 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
  else    y.parent.left = x;	// (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
 }  // 将 “y” 设为 “x的右孩子”
 x.right = y;  // 将 “y的父节点” 设为 “x”
 y.parent = x; }

4. 添加

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过”旋转和重新着色”等一系列操作来修正该树,使之重新成为一颗红黑树。详细描述如下: 
第一步: 将红黑树当作一颗二叉查找树,将节点插入。 
       红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。 
好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

第二步:将插入的节点着色为”红色”。 
       为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性: 
(1) 每个节点或者是黑色,或者是红色。 
(2) 根节点是黑色。 
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!] 
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。 
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 
      将插入的节点着色为红色,不会违背”特性(5)”!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o…哈哈

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。 
       第二步中,将插入节点着色为”红色”之后,不会违背”特性(5)”。那它到底会违背哪些特性呢? 
       对于”特性(1)”,显然不会违背了。因为我们已经将它涂成红色了。 
       对于”特性(2)”,显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。 
       对于”特性(3)”,显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。 
       对于”特性(4)”,是有可能违背的! 
       那接下来,想办法使之”满足特性(4)”,就可以将树重新构造成红黑树了。

添加操作的实现代码(Java语言)

/* * 将结点插入到红黑树中 * * 参数说明: * node 插入的结点 // 对应《算法导论》中的node */
private void insert(RBTNode<T> node) {
 int cmp;
 RBTNode<T> y = null;
 RBTNode<T> x = this.mRoot;

 // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
  while (x != null) {
  y = x;
  cmp = node.key.compareTo(x.key);
  if (cmp < 0)
   x = x.left;
  else
   x = x.right;
 }

 node.parent = y;
 if (y!=null) {
  cmp = node.key.compareTo(y.key);
  if (cmp < 0)
   y.left = node;
  else
   y.right = node;
 } else {
  this.mRoot = node;
 }

 // 2. 设置节点的颜色为红色
  node.color = RED;

 // 3. 将它重新修正为一颗二叉查找树
 insertFixUp(node);
}

/* * 新建结点(key),并将其插入到红黑树中 * * 参数说明: * key 插入结点的键值 */
public void insert(T key) {
 RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

 // 如果新建结点失败,则返回。
  if (node != null)
  insert(node);
}

内部接口 — insert(node)的作用是将”node”节点插入到红黑树中。 
外部接口 — insert(key)的作用是将”key”添加到红黑树中。

添加修正操作的实现代码(Java语言)

/* * 红黑树插入修正函数 * * 在向红黑树中插入节点之后(失去平衡),再调用该函数; * 目的是将它重新塑造成一颗红黑树。 * * 参数说明: * node 插入的结点 // 对应《算法导论》中的z */
private void insertFixUp(RBTNode<T> node) {  RBTNode<T> parent, gparent;  // 若“父节点存在,并且父节点的颜色是红色”
 while (((parent = parentOf(node))!=null) && isRed(parent)) {   gparent = parentOf(parent);   //若“父节点”是“祖父节点的左孩子”
  if (parent == gparent.left) {    // Case 1条件:叔叔节点是红色
   RBTNode<T> uncle = gparent.right;    if ((uncle!=null) && isRed(uncle)) {     setBlack(uncle);     setBlack(parent);     setRed(gparent);     node = gparent;     continue;    }    // Case 2条件:叔叔是黑色,且当前节点是右孩子
   if (parent.right == node) {     RBTNode<T> tmp;     leftRotate(parent);     tmp = parent;     parent = node;     node = tmp;    }    // Case 3条件:叔叔是黑色,且当前节点是左孩子。
   setBlack(parent);    setRed(gparent);    rightRotate(gparent);   } else {	//若“z的父节点”是“z的祖父节点的右孩子”    // Case 1条件:叔叔节点是红色
   RBTNode<T> uncle = gparent.left;    if ((uncle!=null) && isRed(uncle)) {     setBlack(uncle);     setBlack(parent);     setRed(gparent);     node = gparent;     continue;    }    // Case 2条件:叔叔是黑色,且当前节点是左孩子
   if (parent.left == node) {     RBTNode<T> tmp;     rightRotate(parent);     tmp = parent;     parent = node;     node = tmp;    }    // Case 3条件:叔叔是黑色,且当前节点是右孩子。
   setBlack(parent);    setRed(gparent);    leftRotate(gparent);   }  }  // 将根节点设为黑色
 setBlack(this.mRoot); }

insertFixUp(node)的作用是对应”上面所讲的第三步”。它是一个内部接口。

5. 删除操作

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下: 
第一步:将红黑树当作一颗二叉查找树,将节点删除。 
       这和”删除常规二叉查找树中删除节点的方法是一样的”。分3种情况: 
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。 
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。 
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给”被删除节点”之后,再将后继节点删除。这样就巧妙的将问题转换为”删除后继节点”的情况了,下面就考虑后继节点。 在”被删除节点”有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然”的后继节点”不可能双子都非空,就意味着”该节点的后继节点”要么没有儿子,要么只有一个儿子。若没有儿子,则按”情况① “进行处理;若只有一个儿子,则按”情况② “进行处理。

第二步:通过”旋转和重新着色”等一系列来修正该树,使之重新成为一棵红黑树。 
        因为”第一步”中删除节点之后,可能会违背红黑树的特性。所以需要通过”旋转和重新着色”来修正该树,使之重新成为一棵红黑树。

删除操作的实现代码(Java语言)

/* * 删除结点(node),并返回被删除的结点 * * 参数说明: * node 删除的结点 */
private void remove(RBTNode<T> node) {  RBTNode<T> child, parent;  boolean color;  // 被删除节点的"左右孩子都不为空"的情况。
 if ( (node.left!=null) && (node.right!=null) ) {   // 被删节点的后继节点。(称为"取代节点")   // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
  RBTNode<T> replace = node;   // 获取后继节点
  replace = replace.right;   while (replace.left != null)    replace = replace.left;   // "node节点"不是根节点(只有根节点不存在父节点)
  if (parentOf(node)!=null) {    if (parentOf(node).left == node)     parentOf(node).left = replace;    else     parentOf(node).right = replace;   } else {    // "node节点"是根节点,更新根节点。
   this.mRoot = replace;   }   // child是"取代节点"的右孩子,也是需要"调整的节点"。   // "取代节点"肯定不存在左孩子!因为它是一个后继节点。
  child = replace.right;   parent = parentOf(replace);   // 保存"取代节点"的颜色
  color = colorOf(replace);   // "被删除节点"是"它的后继节点的父节点"
  if (parent == node) {    parent = replace;   } else {    // child不为空
   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)    removeFixUp(child, parent);   node = null;   return ;  }  if (node.left !=null) {   child = node.left;  } else {   child = node.right;  }  parent = node.parent;  // 保存"取代节点"的颜色
 color = node.color;  if (child!=null)   child.parent = parent;  // "node节点"不是根节点
 if (parent!=null) {   if (parent.left == node)    parent.left = child;   else    parent.right = child;  } else {   this.mRoot = child;  }  if (color == BLACK)   removeFixUp(child, parent);  node = null; } /* * 删除结点(z),并返回被删除的结点 * * 参数说明: * tree 红黑树的根结点 * z 删除的结点 */
public void remove(T key) {  RBTNode<T> node;  if ((node = search(mRoot, key)) != null)   remove(node); }

内部接口 — remove(node)的作用是将”node”节点插入到红黑树中。 
外部接口 — remove(key)删除红黑树中键值为key的节点。

删除修正操作的实现代码(Java语言)

/* * 红黑树删除修正函数 * * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数; * 目的是将它重新塑造成一颗红黑树。 * * 参数说明: * node 待修正的节点 */
private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) { RBTNode<T> other; while ((node==null || isBlack(node)) && (node != this.mRoot)) { if (parent.left == node) { other = parent.right; if (isRed(other)) {  // Case 1: x的兄弟w是红色的 
 setBlack(other);  setRed(parent);  leftRotate(parent);  other = parent.right; } if ((other.left==null || isBlack(other.left)) &&  (other.right==null || isBlack(other.right))) {  // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 
 setRed(other);  node = parent;  parent = parentOf(node); } else {  if (other.right==null || isBlack(other.right)) {   // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 
  setBlack(other.left);   setRed(other);   rightRotate(other);   other = parent.right;  }  // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
 setColor(other, colorOf(parent));  setBlack(parent);  setBlack(other.right);  leftRotate(parent);  node = this.mRoot;  break; } } else { other = parent.left; if (isRed(other)) {  // Case 1: x的兄弟w是红色的 
 setBlack(other);  setRed(parent);  rightRotate(parent);  other = parent.left; } if ((other.left==null || isBlack(other.left)) &&  (other.right==null || isBlack(other.right))) {  // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 
 setRed(other);  node = parent;  parent = parentOf(node); } else {  if (other.left==null || isBlack(other.left)) {   // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 
  setBlack(other.right);   setRed(other);   leftRotate(other);   other = parent.left;  }  // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
 setColor(other, colorOf(parent));  setBlack(parent);  setBlack(other.left);  rightRotate(parent);  node = this.mRoot;  break; } } } if (node!=null) setBlack(node); }

removeFixup(node, parent)是对应”上面所讲的第三步”。它是一个内部接口。

红黑树的Java实现(完整源码)

下面是红黑树实现的完整代码和相应的测试程序。 
(1) 除了上面所说的”左旋”、”右旋”、”添加”、”删除”等基本操作之后,还实现了”遍历”、”查找”、”打印”、”最小值”、”最大值”、”创建”、”销毁”等接口。 
(2) 函数接口大多分为内部接口和外部接口。内部接口是private函数,外部接口则是public函数。 
(3) 测试代码中提供了”插入”和”删除”动作的检测开关。默认是关闭的,打开方法可以参考”代码中的说明”。建议在打开开关后,在草稿上自己动手绘制一下红黑树。

红黑树的实现文件(RBTree.java)

《AVL树、红黑树》

 1 /**  2  * Java 语言: 红黑树  3  *  4  * @author skywang  5  * @date 2013/11/07  6 */
 7 
 8 public class RBTree<T extends Comparable<T>> {  9 
 10     private RBTNode<T> mRoot;    // 根结点
 11 
 12     private static final boolean RED   = false;  13     private static final boolean BLACK = true;  14 
 15     public class RBTNode<T extends Comparable<T>> {  16         boolean color;        // 颜色
 17         T key;                // 关键字(键值)
 18         RBTNode<T> left;    // 左孩子
 19         RBTNode<T> right;    // 右孩子
 20         RBTNode<T> parent;    // 父结点
 21 
 22         public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {  23             this.key = key;  24             this.color = color;  25             this.parent = parent;  26             this.left = left;  27             this.right = right;  28  }  29 
 30         public T getKey() {  31             return key;  32  }  33 
 34         public String toString() {  35             return ""+key+(this.color==RED?"(R)":"B");  36  }  37  }  38 
 39     public RBTree() {  40         mRoot=null;  41  }  42 
 43     private RBTNode<T> parentOf(RBTNode<T> node) {  44         return node!=null ? node.parent : null;  45  }  46     private boolean colorOf(RBTNode<T> node) {  47         return node!=null ? node.color : BLACK;  48  }  49     private boolean isRed(RBTNode<T> node) {  50         return ((node!=null)&&(node.color==RED)) ? true : false;  51  }  52     private boolean isBlack(RBTNode<T> node) {  53         return !isRed(node);  54  }  55     private void setBlack(RBTNode<T> node) {  56         if (node!=null)  57             node.color = BLACK;  58  }  59     private void setRed(RBTNode<T> node) {  60         if (node!=null)  61             node.color = RED;  62  }  63     private void setParent(RBTNode<T> node, RBTNode<T> parent) {  64         if (node!=null)  65             node.parent = parent;  66  }  67     private void setColor(RBTNode<T> node, boolean color) {  68         if (node!=null)  69             node.color = color;  70  }  71 
 72     /*  73  * 前序遍历"红黑树"  74 */
 75     private void preOrder(RBTNode<T> tree) {  76         if(tree != null) {  77             System.out.print(tree.key+" ");  78  preOrder(tree.left);  79  preOrder(tree.right);  80  }  81  }  82 
 83     public void preOrder() {  84  preOrder(mRoot);  85  }  86 
 87     /*  88  * 中序遍历"红黑树"  89 */
 90     private void inOrder(RBTNode<T> tree) {  91         if(tree != null) {  92  inOrder(tree.left);  93             System.out.print(tree.key+" ");  94  inOrder(tree.right);  95  }  96  }  97 
 98     public void inOrder() {  99  inOrder(mRoot); 100  } 101 
102 
103     /* 104  * 后序遍历"红黑树" 105 */
106     private void postOrder(RBTNode<T> tree) { 107         if(tree != null) 108  { 109  postOrder(tree.left); 110  postOrder(tree.right); 111             System.out.print(tree.key+" "); 112  } 113  } 114 
115     public void postOrder() { 116  postOrder(mRoot); 117  } 118 
119 
120     /* 121  * (递归实现)查找"红黑树x"中键值为key的节点 122 */
123     private RBTNode<T> search(RBTNode<T> x, T key) { 124         if (x==null) 125             return x; 126 
127         int cmp = key.compareTo(x.key); 128         if (cmp < 0) 129             return search(x.left, key); 130         else if (cmp > 0) 131             return search(x.right, key); 132         else
133             return x; 134  } 135 
136     public RBTNode<T> search(T key) { 137         return search(mRoot, key); 138  } 139 
140     /* 141  * (非递归实现)查找"红黑树x"中键值为key的节点 142 */
143     private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) { 144         while (x!=null) { 145             int cmp = key.compareTo(x.key); 146 
147             if (cmp < 0) 148                 x = x.left; 149             else if (cmp > 0) 150                 x = x.right; 151             else
152                 return x; 153  } 154 
155         return x; 156  } 157 
158     public RBTNode<T> iterativeSearch(T key) { 159         return iterativeSearch(mRoot, key); 160  } 161 
162     /* 163  * 查找最小结点:返回tree为根结点的红黑树的最小结点。 164 */
165     private RBTNode<T> minimum(RBTNode<T> tree) { 166         if (tree == null) 167             return null; 168 
169         while(tree.left != null) 170             tree = tree.left; 171         return tree; 172  } 173 
174     public T minimum() { 175         RBTNode<T> p = minimum(mRoot); 176         if (p != null) 177             return p.key; 178 
179         return null; 180  } 181      
182     /* 183  * 查找最大结点:返回tree为根结点的红黑树的最大结点。 184 */
185     private RBTNode<T> maximum(RBTNode<T> tree) { 186         if (tree == null) 187             return null; 188 
189         while(tree.right != null) 190             tree = tree.right; 191         return tree; 192  } 193 
194     public T maximum() { 195         RBTNode<T> p = maximum(mRoot); 196         if (p != null) 197             return p.key; 198 
199         return null; 200  } 201 
202     /* 203  * 找结点(x)的后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"。 204 */
205     public RBTNode<T> successor(RBTNode<T> x) { 206         // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
207         if (x.right != null) 208             return minimum(x.right); 209 
210         // 如果x没有右孩子。则x有以下两种可能: 211         // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。 212         // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
213         RBTNode<T> y = x.parent; 214         while ((y!=null) && (x==y.right)) { 215             x = y; 216             y = y.parent; 217  } 218 
219         return y; 220  } 221      
222     /* 223  * 找结点(x)的前驱结点。即,查找"红黑树中数据值小于该结点"的"最大结点"。 224 */
225     public RBTNode<T> predecessor(RBTNode<T> x) { 226         // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
227         if (x.left != null) 228             return maximum(x.left); 229 
230         // 如果x没有左孩子。则x有以下两种可能: 231         // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。 232         // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
233         RBTNode<T> y = x.parent; 234         while ((y!=null) && (x==y.left)) { 235             x = y; 236             y = y.parent; 237  } 238 
239         return y; 240  } 241 
242     /* 243  * 对红黑树的节点(x)进行左旋转 244  * 245  * 左旋示意图(对节点x进行左旋): 246  * px px 247  * / / 248  * x y 249  * / \ --(左旋)-. / \ # 250  * lx y x ry 251  * / \ / \ 252  * ly ry lx ly 253  * 254  * 255 */
256     private void leftRotate(RBTNode<T> x) { 257         // 设置x的右孩子为y
258         RBTNode<T> y = x.right; 259 
260         // 将 “y的左孩子” 设为 “x的右孩子”; 261         // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
262         x.right = y.left; 263         if (y.left != null) 264             y.left.parent = x; 265 
266         // 将 “x的父亲” 设为 “y的父亲”
267         y.parent = x.parent; 268 
269         if (x.parent == null) { 270             this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
271         } else { 272             if (x.parent.left == x) 273                 x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
274             else
275                 x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
276  } 277         
278         // 将 “x” 设为 “y的左孩子”
279         y.left = x; 280         // 将 “x的父节点” 设为 “y”
281         x.parent = y; 282  } 283 
284     /* 285  * 对红黑树的节点(y)进行右旋转 286  * 287  * 右旋示意图(对节点y进行左旋): 288  * py py 289  * / / 290  * y x 291  * / \ --(右旋)-. / \ # 292  * x ry lx y 293  * / \ / \ # 294  * lx rx rx ry 295  * 296 */
297     private void rightRotate(RBTNode<T> y) { 298         // 设置x是当前节点的左孩子。
299         RBTNode<T> x = y.left; 300 
301         // 将 “x的右孩子” 设为 “y的左孩子”; 302         // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
303         y.left = x.right; 304         if (x.right != null) 305             x.right.parent = y; 306 
307         // 将 “y的父亲” 设为 “x的父亲”
308         x.parent = y.parent; 309 
310         if (y.parent == null) { 311             this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
312         } else { 313             if (y == y.parent.right) 314                 y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
315             else
316                 y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
317  } 318 
319         // 将 “y” 设为 “x的右孩子”
320         x.right = y; 321 
322         // 将 “y的父节点” 设为 “x”
323         y.parent = x; 324  } 325 
326     /* 327  * 红黑树插入修正函数 328  * 329  * 在向红黑树中插入节点之后(失去平衡),再调用该函数; 330  * 目的是将它重新塑造成一颗红黑树。 331  * 332  * 参数说明: 333  * node 插入的结点 // 对应《算法导论》中的z 334 */
335     private void insertFixUp(RBTNode<T> node) { 336         RBTNode<T> parent, gparent; 337 
338         // 若“父节点存在,并且父节点的颜色是红色”
339         while (((parent = parentOf(node))!=null) && isRed(parent)) { 340             gparent = parentOf(parent); 341 
342             //若“父节点”是“祖父节点的左孩子”
343             if (parent == gparent.left) { 344                 // Case 1条件:叔叔节点是红色
345                 RBTNode<T> uncle = gparent.right; 346                 if ((uncle!=null) && isRed(uncle)) { 347  setBlack(uncle); 348  setBlack(parent); 349  setRed(gparent); 350                     node = gparent; 351                     continue; 352  } 353 
354                 // Case 2条件:叔叔是黑色,且当前节点是右孩子
355                 if (parent.right == node) { 356                     RBTNode<T> tmp; 357  leftRotate(parent); 358                     tmp = parent; 359                     parent = node; 360                     node = tmp; 361  } 362 
363                 // Case 3条件:叔叔是黑色,且当前节点是左孩子。
364  setBlack(parent); 365  setRed(gparent); 366  rightRotate(gparent); 367             } else {    //若“z的父节点”是“z的祖父节点的右孩子” 368                 // Case 1条件:叔叔节点是红色
369                 RBTNode<T> uncle = gparent.left; 370                 if ((uncle!=null) && isRed(uncle)) { 371  setBlack(uncle); 372  setBlack(parent); 373  setRed(gparent); 374                     node = gparent; 375                     continue; 376  } 377 
378                 // Case 2条件:叔叔是黑色,且当前节点是左孩子
379                 if (parent.left == node) { 380                     RBTNode<T> tmp; 381  rightRotate(parent); 382                     tmp = parent; 383                     parent = node; 384                     node = tmp; 385  } 386 
387                 // Case 3条件:叔叔是黑色,且当前节点是右孩子。
388  setBlack(parent); 389  setRed(gparent); 390  leftRotate(gparent); 391  } 392  } 393 
394         // 将根节点设为黑色
395         setBlack(this.mRoot); 396  } 397 
398     /* 399  * 将结点插入到红黑树中 400  * 401  * 参数说明: 402  * node 插入的结点 // 对应《算法导论》中的node 403 */
404     private void insert(RBTNode<T> node) { 405         int cmp; 406         RBTNode<T> y = null; 407         RBTNode<T> x = this.mRoot; 408 
409         // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
410         while (x != null) { 411             y = x; 412             cmp = node.key.compareTo(x.key); 413             if (cmp < 0) 414                 x = x.left; 415             else
416                 x = x.right; 417  } 418 
419         node.parent = y; 420         if (y!=null) { 421             cmp = node.key.compareTo(y.key); 422             if (cmp < 0) 423                 y.left = node; 424             else
425                 y.right = node; 426         } else { 427             this.mRoot = node; 428  } 429 
430         // 2. 设置节点的颜色为红色
431         node.color = RED; 432 
433         // 3. 将它重新修正为一颗二叉查找树
434  insertFixUp(node); 435  } 436 
437     /* 438  * 新建结点(key),并将其插入到红黑树中 439  * 440  * 参数说明: 441  * key 插入结点的键值 442 */
443     public void insert(T key) { 444         RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null); 445 
446         // 如果新建结点失败,则返回。
447         if (node != null) 448  insert(node); 449  } 450 
451 
452     /* 453  * 红黑树删除修正函数 454  * 455  * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数; 456  * 目的是将它重新塑造成一颗红黑树。 457  * 458  * 参数说明: 459  * node 待修正的节点 460 */
461     private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) { 462         RBTNode<T> other; 463 
464         while ((node==null || isBlack(node)) && (node != this.mRoot)) { 465             if (parent.left == node) { 466                 other = parent.right; 467                 if (isRed(other)) { 468                     // Case 1: x的兄弟w是红色的 
469  setBlack(other); 470  setRed(parent); 471  leftRotate(parent); 472                     other = parent.right; 473  } 474 
475                 if ((other.left==null || isBlack(other.left)) &&
476                     (other.right==null || isBlack(other.right))) { 477                     // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 
478  setRed(other); 479                     node = parent; 480                     parent = parentOf(node); 481                 } else { 482 
483                     if (other.right==null || isBlack(other.right)) { 484                         // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 
485  setBlack(other.left); 486  setRed(other); 487  rightRotate(other); 488                         other = parent.right; 489  } 490                     // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
491  setColor(other, colorOf(parent)); 492  setBlack(parent); 493  setBlack(other.right); 494  leftRotate(parent); 495                     node = this.mRoot; 496                     break; 497  } 498             } else { 499 
500                 other = parent.left; 501                 if (isRed(other)) { 502                     // Case 1: x的兄弟w是红色的 
503  setBlack(other); 504  setRed(parent); 505  rightRotate(parent); 506                     other = parent.left; 507  } 508 
509                 if ((other.left==null || isBlack(other.left)) &&
510                     (other.right==null || isBlack(other.right))) { 511                     // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 
512  setRed(other); 513                     node = parent; 514                     parent = parentOf(node); 515                 } else { 516 
517                     if (other.left==null || isBlack(other.left)) { 518                         // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 
519  setBlack(other.right); 520  setRed(other); 521  leftRotate(other); 522                         other = parent.left; 523  } 524 
525                     // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
526  setColor(other, colorOf(parent)); 527  setBlack(parent); 528  setBlack(other.left); 529  rightRotate(parent); 530                     node = this.mRoot; 531                     break; 532  } 533  } 534  } 535 
536         if (node!=null) 537  setBlack(node); 538  } 539 
540     /* 541  * 删除结点(node),并返回被删除的结点 542  * 543  * 参数说明: 544  * node 删除的结点 545 */
546     private void remove(RBTNode<T> node) { 547         RBTNode<T> child, parent; 548         boolean color; 549 
550         // 被删除节点的"左右孩子都不为空"的情况。
551         if ( (node.left!=null) && (node.right!=null) ) { 552             // 被删节点的后继节点。(称为"取代节点") 553             // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
554             RBTNode<T> replace = node; 555 
556             // 获取后继节点
557             replace = replace.right; 558             while (replace.left != null) 559                 replace = replace.left; 560 
561             // "node节点"不是根节点(只有根节点不存在父节点)
562             if (parentOf(node)!=null) { 563                 if (parentOf(node).left == node) 564                     parentOf(node).left = replace; 565                 else
566                     parentOf(node).right = replace; 567             } else { 568                 // "node节点"是根节点,更新根节点。
569                 this.mRoot = replace; 570  } 571 
572             // child是"取代节点"的右孩子,也是需要"调整的节点"。 573             // "取代节点"肯定不存在左孩子!因为它是一个后继节点。
574             child = replace.right; 575             parent = parentOf(replace); 576             // 保存"取代节点"的颜色
577             color = colorOf(replace); 578 
579             // "被删除节点"是"它的后继节点的父节点"
580             if (parent == node) { 581                 parent = replace; 582             } else { 583                 // child不为空
584                 if (child!=null) 585  setParent(child, parent); 586                 parent.left = child; 587 
588                 replace.right = node.right; 589  setParent(node.right, replace); 590  } 591 
592             replace.parent = node.parent; 593             replace.color = node.color; 594             replace.left = node.left; 595             node.left.parent = replace; 596 
597             if (color == BLACK) 598  removeFixUp(child, parent); 599 
600             node = null; 601             return ; 602  } 603 
604         if (node.left !=null) { 605             child = node.left; 606         } else { 607             child = node.right; 608  } 609 
610         parent = node.parent; 611         // 保存"取代节点"的颜色
612         color = node.color; 613 
614         if (child!=null) 615             child.parent = parent; 616 
617         // "node节点"不是根节点
618         if (parent!=null) { 619             if (parent.left == node) 620                 parent.left = child; 621             else
622                 parent.right = child; 623         } else { 624             this.mRoot = child; 625  } 626 
627         if (color == BLACK) 628  removeFixUp(child, parent); 629         node = null; 630  } 631 
632     /* 633  * 删除结点(z),并返回被删除的结点 634  * 635  * 参数说明: 636  * tree 红黑树的根结点 637  * z 删除的结点 638 */
639     public void remove(T key) { 640         RBTNode<T> node; 641 
642         if ((node = search(mRoot, key)) != null) 643  remove(node); 644  } 645 
646     /* 647  * 销毁红黑树 648 */
649     private void destroy(RBTNode<T> tree) { 650         if (tree==null) 651             return ; 652 
653         if (tree.left != null) 654  destroy(tree.left); 655         if (tree.right != null) 656  destroy(tree.right); 657 
658         tree=null; 659  } 660 
661     public void clear() { 662  destroy(mRoot); 663         mRoot = null; 664  } 665 
666     /* 667  * 打印"红黑树" 668  * 669  * key -- 节点的键值 670  * direction -- 0,表示该节点是根节点; 671  * -1,表示该节点是它的父结点的左孩子; 672  * 1,表示该节点是它的父结点的右孩子。 673 */
674     private void print(RBTNode<T> tree, T key, int direction) { 675 
676         if(tree != null) { 677 
678             if(direction==0)    // tree是根节点
679                 System.out.printf("%2d(B) is root\n", tree.key); 680             else                // tree是分支节点
681                 System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"right" : "left"); 682 
683             print(tree.left, tree.key, -1); 684             print(tree.right,tree.key,  1); 685  } 686  } 687 
688     public void print() { 689         if (mRoot != null) 690             print(mRoot, mRoot.key, 0); 691  } 692 }

View Code

红黑树的测试文件(RBTreeTest.java)

《AVL树、红黑树》

 1 /**  2  * Java 语言: 二叉查找树  3  *  4  * @author skywang  5  * @date 2013/11/07  6 */
 7 public class RBTreeTest {  8 
 9     private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80}; 10     private static final boolean mDebugInsert = false;    // "插入"动作的检测开关(false,关闭;true,打开)
11     private static final boolean mDebugDelete = false;    // "删除"动作的检测开关(false,关闭;true,打开)
12 
13     public static void main(String[] args) { 14         int i, ilen = a.length; 15         RBTree<Integer> tree=new RBTree<Integer>(); 16 
17         System.out.printf("== 原始数据: "); 18         for(i=0; i<ilen; i++) 19             System.out.printf("%d ", a[i]); 20         System.out.printf("\n"); 21 
22         for(i=0; i<ilen; i++) { 23  tree.insert(a[i]); 24             // 设置mDebugInsert=true,测试"添加函数"
25             if (mDebugInsert) { 26                 System.out.printf("== 添加节点: %d\n", a[i]); 27                 System.out.printf("== 树的详细信息: \n"); 28  tree.print(); 29                 System.out.printf("\n"); 30  } 31  } 32 
33         System.out.printf("== 前序遍历: "); 34  tree.preOrder(); 35 
36         System.out.printf("\n== 中序遍历: "); 37  tree.inOrder(); 38 
39         System.out.printf("\n== 后序遍历: "); 40  tree.postOrder(); 41         System.out.printf("\n"); 42 
43         System.out.printf("== 最小值: %s\n", tree.minimum()); 44         System.out.printf("== 最大值: %s\n", tree.maximum()); 45         System.out.printf("== 树的详细信息: \n"); 46  tree.print(); 47         System.out.printf("\n"); 48 
49         // 设置mDebugDelete=true,测试"删除函数"
50         if (mDebugDelete) { 51             for(i=0; i<ilen; i++) 52  { 53  tree.remove(a[i]); 54 
55                 System.out.printf("== 删除节点: %d\n", a[i]); 56                 System.out.printf("== 树的详细信息: \n"); 57  tree.print(); 58                 System.out.printf("\n"); 59  } 60  } 61 
62         // 销毁二叉树
63  tree.clear(); 64  } 65 }

View Code

红黑树的Java测试程序

前面已经给出了红黑树的测试代码(RBTreeTest.java),这里就不再重复说明。下面是测试程序的运行结果:

== 原始数据: 10 40 30 60 90 70 20 50 80 
== 前序遍历: 30 10 20 60 40 50 80 70 90 
== 中序遍历: 10 20 30 40 50 60 70 80 90 
== 后序遍历: 20 10 50 40 70 90 80 60 30 
== 最小值: 10
== 最大值: 90
== 树的详细信息: 
30(B) is root
10(B) is 30's   left child
20(R) is 10's  right child
60(R) is 30's  right child
40(B) is 60's   left child
50(R) is 40's  right child
80(B) is 60's  right child
70(R) is 80's   left child
90(R) is 80's  right child


    原文作者:AVL树
    原文地址: https://blog.csdn.net/basycia/article/details/51853735
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞