AA树 - 红黑树的变种
2015-08-11 16:20
309 查看
AA树 - 红黑树的变种
作者:ljs 2011-06-15
AA树是Arne Andersson教授在他的论文"Balanced search trees made simple"中介绍的一个红黑树变种,设计的目的是减少RB树考虑的cases。AA树是一颗红黑树,但是规定红色结点不能作为任何结点的左孩子,也就是说红色结点只能作为右孩子。这样本质上跟2-3树类似(虽然后者属于B树)。另外AA树为实现方便,不再使用红黑两种颜色,而是用level标记结点。level实际上就相当于RB树中的black height,叶子结点的level等于1(反过来,level等于1的不一定是叶子结点,因为等于1的结点可能有一个红色的右孩子),红色结点使用它的父结点的level,黑色结点比它的父结点的level小1。另外,下面两种情况是禁止出现的:
1)连续两个水平方向链(horizontal link),所谓horizontal link是指一个结点跟它的右孩子结点的level相同(左孩子结点永远比它的父结点level小1)。这个规定其实相当于RB树中不能出现两个连续的红色结点。
2)向左的水平方向链(left horizontal link),也就是说一个结点最多只能出现一次向右的水平方向链。这是因为left horizontal link相当于左孩子能为红色结点,这在AA树的定义中是不允许的。
在插入和删除操作中,可能会出现上面两个禁止发生的情况,这时候就需要通过树的旋转操作来纠正。AA树中只有两个基本操作:skew和split。前者用于纠正出现向左的水平方向链,后者用于纠正出现连续两个水平方向链的情况。skew就是一个右旋转,split是一个左旋转,但两者不是互逆的。skew操作之后可能引起1)的发生(当skew之前已经有一个右孩子的level跟当前结点的level相同),这时需要配合使用split操作。split操作的特点是新的子树的根节点level增加1, 从而会在它的父结点中出现1)(当它作为父结点的左孩子)或者在它的父结点中出现2)(当它作为父结点的右孩子而且父结点跟祖父结点的level相同),这时需要通过skew和split操作纠正这两种情况。
由于split引起的新问题发生在parent一级局部结点,而skew引起的新问题只发生在当前局部结点,所以在实现时需要先skew,再split。
在下面的插入操作中使用递归,删除操作没有使用递归。新插入的结点level等于1。
因为AA树也是平衡BST,它的时间复杂度跟RB树一样,即O(logn),但是旋转次数相对多一些(RB树插入操作最多旋转两次,而且旋转完毕即结束rebalancing;删除操作最多旋转三次,也是旋转完毕即结束rebalancing)。
实现: (性能测试代码和结果在本段代码后面)
[java] view
plaincopy
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
/**
*
* @author ljs
* 2011-06-15
* 版权说明:可以复制,但须标明出处
*
*/
public class AATree<T extends Comparable<T>> {
private AANode<T> nil;
private AANode<T> root;
public static class AANode<E extends Comparable<E>>{
E key;
AANode<E> left;
AANode<E> right;
int level;
public AANode(E key,AANode<E> left,AANode<E> right){
this.key = key;
this.left = left;
this.right = right;
this.level = 1;
}
public AANode(){
}
public String toString(){
return String.valueOf(key + ":" + level);
}
public E getKey(){
return this.key;
}
public E getLeftChild(){
return this.left.key;
}
public E getRightChild(){
return this.right.key;
}
public AANode<E> getLeft() {
return left;
}
public AANode<E> getRight() {
return right;
}
}
public AATree(){
//sentinel node
nil = new AANode<T>();
nil.left = nil;
nil.right = nil;
nil.level = 0;
root = nil;
}
public AANode<T> getRoot(){
return root;
}
private AANode<T> rotateLeftChild(AANode<T> t){
AANode<T> x = t.left;
t.left = x.right;
x.right = t;
return x;
}
private AANode<T> rotateRightChild(AANode<T> t){
AANode<T> x = t.right;
t.right = x.left;
x.left = t;
return x;
}
private AANode<T> skew(AANode<T> t){
if(t != nil && t.left.level == t.level)
t=rotateLeftChild(t);
return t;
}
private AANode<T> split(AANode<T> t){
if(t != nil && t.right.right.level == t.level){
t = rotateRightChild(t);
t.level++;
}
return t;
}
//single insert
public void insert(T x) throws Exception{
root = insert(x,root);
}
//batch insert
public void insert(T[] keys) throws Exception{
for(T x:keys){
insert(x);
}
}
//x: the AANode to insert
//t: the root of the subtree
//return: the new root
private AANode<T> insert(T x,AANode<T> t) throws Exception{
if(t==nil){
t = new AANode<T>(x,nil,nil);
}else if(x.compareTo(t.key)<0){
//search the left subtree
AANode<T> left = insert(x,t.left);
t.left = left;
}else if(x.compareTo(t.key)>0){
//search the right subtree
AANode<T> right = insert(x,t.right);
t.right = right;
}else{
//duplicate key found!
throw new Exception("Duplicate key is found!");
}
t = skew(t);
t = split(t);
return t;
}
//single delete
public void delete(T x) throws Exception{
delete(x,this.root);
}
//batch delete
public void delete(T[] keys) throws Exception{
for(T x:keys){
delete(x);
}
}
//x: the AANode to delete
//t: the root of the subtree
//return: the new root
private void delete(T x,AANode<T> t) throws Exception{
//delete using BST method
AANode<T> deleteNode = null;
Stack<AANode<T>> nodes = new Stack<AANode<T>>();
nodes.push(nil); //sentinel
while(t != nil){
nodes.push(t);
if(x.compareTo(t.key)==0){
deleteNode = t;
t = t.left; //find predecessor
}else if(x.compareTo(t.key)<0){
//search the left subtree
t = t.left;
}else {
//search the right subtree
t = t.right;
}
}
if(deleteNode == null){
throw new Exception("No key is found!");
}
//the top element in the stack is the predecessor of the deleted node or the deleted node itself
t = nodes.pop();
if(deleteNode != t){
//t is the predecessor of the deleteNode
deleteNode.key = t.key; //normal BST operation
}//otherwise: t is a single black leaf node (level=1)
//remove the node
if(t.right != nil){
//there is a red right child (which has same level with t)
AANode<T> parent = nodes.pop();
if(parent == nil){
this.root = t.right;
}else{
if(parent.left == t)
parent.left = t.right;
else
parent.right = t.right;
}
//return this.root;
}else{
AANode<T> parent = nodes.pop();
if(parent == nil){
//now return an empty tree
//return nil;
this.root = nil;
}else{
if(parent.left == t){
parent.left = nil;
}else{
parent.right = nil;
}
t=parent;
AANode<T> rootNode = null;
//rebalance
boolean deleteFromRight = false;
while(!nodes.isEmpty() && t != nil){
parent = nodes.peek();
boolean doSkewOrSplit = false;
int currLevel = t.level;
if(t.left.level < currLevel - 1){
if(parent != nil && parent.right == t) {
deleteFromRight = true;
}
t.level--;
if(t.right.level == currLevel) {
t.right.level--;
}
doSkewOrSplit = true;
}else if(t.right.level<currLevel - 1
|| (deleteFromRight && currLevel-1==t.right.level)){
if(parent != nil && parent.right == t) {
deleteFromRight = true;
}
t.level--;
doSkewOrSplit = true;
}
if(doSkewOrSplit){
t = skew(t);
t.right = skew(t.right);
t.right.right = skew(t.right.right);
t = split(t);
t.right = split(t.right);
//set the parent
if(parent != nil) {
if(deleteFromRight)
parent.right = t;
else
parent.left = t;
}
}else{ //no rotation
rootNode = this.root;
break;
}
rootNode = t;
t = nodes.pop();
}
//return rootNode;
this.root = rootNode;
}
}
}
public static void testCases() throws Exception{
AATree<Integer> aaT = new AATree<Integer>();
try{
aaT.delete(0); //throw exception
System.out.println("***Exception Test WRONG");
}catch(Exception e){
if(e.getMessage().startsWith("No key is found!")){
System.out.println("Exception Test OK");
}else{
System.out.println("***Exception Test WRONG");
}
}
System.out.println("*************************");
aaT = new AATree<Integer>();
Integer[] A = new Integer[]{0,1,2,3,4,5,6};
aaT.insert(A);
System.out.println("After Insert:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{6,5,4,3,2};
aaT.insert(A);
System.out.println("After Insert:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(1); //become an empty tree
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.isEmptyTree()){
System.out.println("Empty Tree Test OK");
}else{
System.out.println("***Empty Tree Test WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
aaT.insert(2);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(2); //delete right node (red)
System.out.println("After Delete 2:");
AATree.print(aaT);
if(aaT.getRoot().getKey() == 1){
System.out.println("Delete Red Leaf OK");
}else{
System.out.println("***Delete Red Leaf WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
aaT.insert(2);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(1); //delete 1
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().getKey() == 2){
System.out.println("Delete Root OK");
}else{
System.out.println("***Delete Root WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(5);
System.out.println("After Delete 5:");
AATree.print(aaT);
AANode<Integer> node = aaT.findNode(4);
if(aaT.isLeaf(node)){
System.out.println("Leaf Test OK");
}else{
System.out.println("***Leaf Test WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(3);
System.out.println("After Delete 3:");
AATree.print(aaT);
node = aaT.findNode(0);
if(node.getRightChild()==1){
System.out.println("Right Child OK");
}else{
System.out.println("***Right Child WRONG");
}
if(aaT.getRoot().getKey()==2){
System.out.println("Root Key OK");
}else{
System.out.println("***Root Key WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(4);
System.out.println("After Delete 4:");
AATree.print(aaT);
if(aaT.findNode(5).level==1){
System.out.println("Leaf Level OK");
}else{
System.out.println("***Leaf Level WRONG");
}
if(aaT.findNode(6).level==2){
System.out.println("Subtree's Root Level OK");
}else{
System.out.println("***Subtree's Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{10,85,15,70,20,60,30,50,65,80,90,40,5,55,35};
aaT.insert(A);
System.out.println("Before Insert 45:");
AATree.print(aaT);
aaT.insert(45);
System.out.println("After Insert 45:");
AATree.print(aaT);
if(aaT.getRoot().key==50 && aaT.getRoot().level == 4){
System.out.println("Root(50) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,5,6};
aaT.insert(A);
System.out.println("Before Delete:");
AATree.print(aaT);
/*
3,3
/ /
1,2 5,2
/ / / /
0,1 2,1 4,1 6,1
*/
Integer[] DEL = new Integer[]{0,3};
aaT.delete(DEL); //until now nothing special
System.out.println("After Delete 0 and 3:");
AATree.print(aaT);
aaT.delete(1);
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().key==4 && aaT.getRoot().level == 2){
System.out.println("Root(4) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
//make a tree
aaT = new AATree<Integer>();
buildTestTree1(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(1);
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().key==3 && aaT.getRoot().level == 2){
System.out.println("Root(3) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree1(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
aaT.delete(6);
System.out.println("After Delete 6:");
AATree.print(aaT);
if(aaT.getRoot().key==2 && aaT.getRoot().level == 2){
System.out.println("Root(2) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree2(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree3(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
}
private static void buildTestTree1(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(2);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node5);
AANode<Integer> node3 = aaT.makeNode(3);
node3.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 1;
AANode<Integer> node4 = aaT.makeNode(4);
node4.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AATree.attachAsLeftChild(node5, node3);
AATree.attachAsRightChild(node5, node6);
AATree.attachAsRightChild(node3, node4);
AATree.attachAsRightChild(node6, node7);
aaT.setRoot(root);
}
private static void buildTestTree2(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(3);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node6);
AANode<Integer> node2 = aaT.makeNode(2);
node2.level = 1;
AANode<Integer> node4 = aaT.makeNode(4);
node4.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 1;
AATree.attachAsLeftChild(node6, node4);
AATree.attachAsRightChild(node6, node7);
AATree.attachAsRightChild(node4, node5);
AATree.attachAsRightChild(node1, node2);
aaT.setRoot(root);
}
private static void buildTestTree3(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(3);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node6);
AANode<Integer> node2 = aaT.makeNode(2);
node2.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 1;
AATree.attachAsLeftChild(node6, node5);
AATree.attachAsRightChild(node6, node7);
AATree.attachAsRightChild(node1, node2);
aaT.setRoot(root);
}
//utility method for test purpose
@SuppressWarnings("rawtypes")
public static void print(AATree aaTree){
AANode root = aaTree.getRoot();
System.out.format("%nin-order BST:%n");
NODES=0;
AATree.recursiveInOrderTraverse(root);
System.out.format("%n%n%nThe tree is:");
AATree.displayBinaryTree(root,NODES);
}
private static int NODES=0;
//utility method for test purpose
@SuppressWarnings("rawtypes")
private static void recursiveInOrderTraverse(AANode root){
if(root.key == null)return;
recursiveInOrderTraverse(root.left);
System.out.format(" (%d,%d)", root.key,root.level);
NODES++;
recursiveInOrderTraverse(root.right);
}
//utility method for test purpose
//n: the nodes number of the tree
@SuppressWarnings("rawtypes")
private static void displayBinaryTree(AANode root,int n){
if(root.key == null) return;
LinkedList<AANode> queue = new LinkedList<AANode>();
//all AANodes in each level
List<List<AANode>> nodesList = new ArrayList<List<AANode>>();
//the positions in a displayable tree for each level's nodes
List<List<Integer>> nextPosList = new ArrayList<List<Integer>>();
queue.add(root);
//int level=0;
int levelNodes = 1;
int nextLevelNodes = 0;
List<AANode> levelNodesList = new ArrayList<AANode>();
List<Integer> nextLevelNodesPosList = new ArrayList<Integer>();
int pos = 0; //the position of the current node
List<Integer> levelNodesPosList = new ArrayList<Integer>();
levelNodesPosList.add(0); //root position
nextPosList.add(levelNodesPosList);
int levelNodesTotal = 1;
while(!queue.isEmpty()) {
AANode node = queue.remove();
if(levelNodes==0){
nodesList.add(levelNodesList);
nextPosList.add(nextLevelNodesPosList);
levelNodesPosList = nextLevelNodesPosList;
levelNodesList = new ArrayList<AANode>();
nextLevelNodesPosList = new ArrayList<Integer>();
//level++;
levelNodes = nextLevelNodes;
levelNodesTotal = nextLevelNodes;
nextLevelNodes = 0;
}
levelNodesList.add(node);
pos = levelNodesPosList.get(levelNodesTotal - levelNodes);
if(node.left.key != null){
queue.add(node.left);
nextLevelNodes++;
nextLevelNodesPosList.add(2*pos);
}
if(node.right.key != null) {
queue.add(node.right);
nextLevelNodes++;
nextLevelNodesPosList.add(2*pos+1);
}
levelNodes--;
}
//save the last level's nodes list
nodesList.add(levelNodesList);
int maxLevel = nodesList.size()-1; //==level
//use both nodesList and nextPosList to set the positions for each node
//Note: expected max columns: 2^(level+1) - 1
int cols = 1;
for(int i=0;i<=maxLevel;i++){
cols <<= 1;
}
cols--;
AANode[][] tree = new AANode[maxLevel+1][cols];
//load the tree into an array for later display
for(int currLevel=0;currLevel<=maxLevel;currLevel++){
levelNodesList = nodesList.get(currLevel);
levelNodesPosList = nextPosList.get(currLevel);
//Note: the column for this level's j-th element: 2^(maxLevel-level)*(2*j+1) - 1
int tmp = maxLevel-currLevel;
int coeff = 1;
for(int i=0;i<tmp;i++){
coeff <<= 1;
}
for(int k=0;k<levelNodesList.size();k++){
int j = levelNodesPosList.get(k);
int col = coeff*(2*j + 1) - 1;
tree[currLevel][col] = levelNodesList.get(k);
}
}
//display the binary search tree
System.out.format("%n");
for(int i=0;i<=maxLevel;i++){
for(int j=0;j<cols;j++){
AANode node = tree[i][j];
if(node== null || node.key == null)
System.out.format(" ");
else
System.out.format("%2d,%d",node.key,node.level);
}
System.out.format("%n");
}
}
public void setRoot(AANode<T> root) {
this.root = root;
}
public boolean isEmptyTree(){
return this.root == nil;
}
public AANode<T> makeNode(T key){
return new AANode<T>(key,nil,nil);
}
public static <T extends Comparable<T>> void attachAsLeftChild(AANode<T> parent,AANode<T> node){
parent.left = node;
}
public static <T extends Comparable<T>> void attachAsRightChild(AANode<T> parent,AANode<T> node){
parent.right = node;
}
public boolean isLeaf(AANode<T> node){
return node.getLeft()==nil && node.getRight() == nil;
}
public AANode<T> findNode(T key) throws Exception{
AANode<T> node = this.root;
while(node != nil){
if(key.compareTo(node.key)==0){
break;
}else if(key.compareTo(node.key)<0){
node = node.left;
}else if(key.compareTo(node.key)>0){
node = node.right;
}
}
if(node == nil){
throw new Exception("No such key is found!");
}
return node;
}
public static void main(String[] args) throws Exception {
testCases();
}
}
性能测试:从磁盘读入一百万条数据到内存,然后依次查找、插入和删除各50万次。
先是性能测试代码:
其中prepareTestData(dataFilename,N)语句只需执行一次,用于写入一百万个不重复的整数到磁盘文件中:
[java] view
plaincopy
public static void main(String[] args) throws Exception {
String dataFilename = "C://tmp//aatree.txt";
int N = 1000000; //the number of keys
//******Prepare Data(only execute once for multiple tests)******
prepareTestData(dataFilename,N);
//******Begin Test******
AATree<Integer> aaT = new AATree<Integer>();
DataInputStream in= new DataInputStream(new FileInputStream(dataFilename));
//long startTime = System.currentTimeMillis();
int key = -1;
int keyCount = 0;
while(true){
try{
key = in.readInt();
aaT.insert(key);
keyCount++;
}catch(EOFException e){
break;
}
}
in.close();
//long endTime = System.currentTimeMillis();
//System.out.format("file io and insert time spent: %d(ms)%n",(endTime-startTime));
System.out.format("Total number of keys to begin with: %d%n",keyCount);
long t1=0,t2=0;
//do some operations in memory
long startTime = System.currentTimeMillis();
t1 = startTime;
int totalSuccess = 0;
int totalFailed = 0;
System.out.format("%n***Search Operations***%n");
//try to find a number of keys
int rangeStart = 1100000;
int rangeEnd = 1600000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
AANode<Integer> node = aaT.findNode(i);
//System.out.println("found key: " + node.key);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//not found
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to find: %d%n",rangeEnd-rangeStart);
System.out.format("The number of keys found: %d%n",totalSuccess);
System.out.format("The number of keys not found: %d%n",totalFailed);
//insert a number of new keys
System.out.format("%n***Insertion Operations***%n");
t1 = System.currentTimeMillis();
totalSuccess = 0;
totalFailed = 0;
rangeStart = 1400000;
rangeEnd = 1900000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
aaT.insert(i);
//System.out.println("inserted key: " + i);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//duplicate insert
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to insert: %d%n",rangeEnd - rangeStart);
System.out.format("The number of keys inserted: %d%n",totalSuccess);
System.out.format("The number of keys with insertion failed due to duplicate: %d%n",totalFailed);
//delete a number of new keys
System.out.format("%n***Deletion Operations***%n");
t1 = System.currentTimeMillis();
totalSuccess = 0;
totalFailed = 0;
rangeStart = 400000;
rangeEnd = 900000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
aaT.delete(i);
//System.out.println("deleted key: " + i);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//not found
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to delete: %d%n",rangeEnd-rangeStart);
System.out.format("The number of keys deleted: %d%n",totalSuccess);
System.out.format("The number of keys with deletion failed due to non-existence: %d%n",totalFailed);
long endTime = t2;
System.out.format("%nThe above operations(search,insert,delete) total spent: %d(ms)%n",(endTime-startTime));
}
//write non-duplicate keys into external file for later test
private static void prepareTestData(String dataFilename,int totalKeys) throws IOException{
AATree<Integer> aaT = new AATree<Integer>();
//multiply by a larger factor(here it is 10) to eliminate the duplicate insert
int KEYRANGE = totalKeys*10;
int i=0;
while(i<totalKeys){
int rand = (int)(Math.random()*KEYRANGE);
try{
aaT.insert(rand);
i++;
//System.out.format("insert: %d%n",rand);
}catch(Exception e){
//for duplicate insert: retry
//System.out.println(e.getMessage());
}
}
DataOutputStream out= new DataOutputStream(new FileOutputStream(dataFilename));
persistKeys(aaT.getRoot(),out);
out.close();
}
private static void persistKeys(AANode<Integer> root,DataOutputStream out) throws IOException{
if(root.key == null)return;
persistKeys(root.left,out);
out.writeInt(root.key);
persistKeys(root.right,out);
}
性能测试报告(Intel Core2 T5500 1.66GHz, 2GB RAM):
Total number of keys to begin with: 1000000
***Search Operations***
Time spent: 2172(ms)
The number of keys we try to find: 500000
The number of keys found: 49955
The number of keys not found: 450045
***Insertion Operations***
Time spent: 1984(ms)
The number of keys we try to insert: 500000
The number of keys inserted: 450090
The number of keys with insertion failed due to duplicate: 49910
***Deletion Operations***
Time spent: 3172(ms)
The number of keys we try to delete: 500000
The number of keys deleted: 7
The number of keys with deletion failed due to non-existence: 499993
The above operations(search,insert,delete) total spent: 7328(ms)
作者:ljs 2011-06-15
AA树是Arne Andersson教授在他的论文"Balanced search trees made simple"中介绍的一个红黑树变种,设计的目的是减少RB树考虑的cases。AA树是一颗红黑树,但是规定红色结点不能作为任何结点的左孩子,也就是说红色结点只能作为右孩子。这样本质上跟2-3树类似(虽然后者属于B树)。另外AA树为实现方便,不再使用红黑两种颜色,而是用level标记结点。level实际上就相当于RB树中的black height,叶子结点的level等于1(反过来,level等于1的不一定是叶子结点,因为等于1的结点可能有一个红色的右孩子),红色结点使用它的父结点的level,黑色结点比它的父结点的level小1。另外,下面两种情况是禁止出现的:
1)连续两个水平方向链(horizontal link),所谓horizontal link是指一个结点跟它的右孩子结点的level相同(左孩子结点永远比它的父结点level小1)。这个规定其实相当于RB树中不能出现两个连续的红色结点。
2)向左的水平方向链(left horizontal link),也就是说一个结点最多只能出现一次向右的水平方向链。这是因为left horizontal link相当于左孩子能为红色结点,这在AA树的定义中是不允许的。
在插入和删除操作中,可能会出现上面两个禁止发生的情况,这时候就需要通过树的旋转操作来纠正。AA树中只有两个基本操作:skew和split。前者用于纠正出现向左的水平方向链,后者用于纠正出现连续两个水平方向链的情况。skew就是一个右旋转,split是一个左旋转,但两者不是互逆的。skew操作之后可能引起1)的发生(当skew之前已经有一个右孩子的level跟当前结点的level相同),这时需要配合使用split操作。split操作的特点是新的子树的根节点level增加1, 从而会在它的父结点中出现1)(当它作为父结点的左孩子)或者在它的父结点中出现2)(当它作为父结点的右孩子而且父结点跟祖父结点的level相同),这时需要通过skew和split操作纠正这两种情况。
由于split引起的新问题发生在parent一级局部结点,而skew引起的新问题只发生在当前局部结点,所以在实现时需要先skew,再split。
在下面的插入操作中使用递归,删除操作没有使用递归。新插入的结点level等于1。
因为AA树也是平衡BST,它的时间复杂度跟RB树一样,即O(logn),但是旋转次数相对多一些(RB树插入操作最多旋转两次,而且旋转完毕即结束rebalancing;删除操作最多旋转三次,也是旋转完毕即结束rebalancing)。
实现: (性能测试代码和结果在本段代码后面)
[java] view
plaincopy
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
/**
*
* @author ljs
* 2011-06-15
* 版权说明:可以复制,但须标明出处
*
*/
public class AATree<T extends Comparable<T>> {
private AANode<T> nil;
private AANode<T> root;
public static class AANode<E extends Comparable<E>>{
E key;
AANode<E> left;
AANode<E> right;
int level;
public AANode(E key,AANode<E> left,AANode<E> right){
this.key = key;
this.left = left;
this.right = right;
this.level = 1;
}
public AANode(){
}
public String toString(){
return String.valueOf(key + ":" + level);
}
public E getKey(){
return this.key;
}
public E getLeftChild(){
return this.left.key;
}
public E getRightChild(){
return this.right.key;
}
public AANode<E> getLeft() {
return left;
}
public AANode<E> getRight() {
return right;
}
}
public AATree(){
//sentinel node
nil = new AANode<T>();
nil.left = nil;
nil.right = nil;
nil.level = 0;
root = nil;
}
public AANode<T> getRoot(){
return root;
}
private AANode<T> rotateLeftChild(AANode<T> t){
AANode<T> x = t.left;
t.left = x.right;
x.right = t;
return x;
}
private AANode<T> rotateRightChild(AANode<T> t){
AANode<T> x = t.right;
t.right = x.left;
x.left = t;
return x;
}
private AANode<T> skew(AANode<T> t){
if(t != nil && t.left.level == t.level)
t=rotateLeftChild(t);
return t;
}
private AANode<T> split(AANode<T> t){
if(t != nil && t.right.right.level == t.level){
t = rotateRightChild(t);
t.level++;
}
return t;
}
//single insert
public void insert(T x) throws Exception{
root = insert(x,root);
}
//batch insert
public void insert(T[] keys) throws Exception{
for(T x:keys){
insert(x);
}
}
//x: the AANode to insert
//t: the root of the subtree
//return: the new root
private AANode<T> insert(T x,AANode<T> t) throws Exception{
if(t==nil){
t = new AANode<T>(x,nil,nil);
}else if(x.compareTo(t.key)<0){
//search the left subtree
AANode<T> left = insert(x,t.left);
t.left = left;
}else if(x.compareTo(t.key)>0){
//search the right subtree
AANode<T> right = insert(x,t.right);
t.right = right;
}else{
//duplicate key found!
throw new Exception("Duplicate key is found!");
}
t = skew(t);
t = split(t);
return t;
}
//single delete
public void delete(T x) throws Exception{
delete(x,this.root);
}
//batch delete
public void delete(T[] keys) throws Exception{
for(T x:keys){
delete(x);
}
}
//x: the AANode to delete
//t: the root of the subtree
//return: the new root
private void delete(T x,AANode<T> t) throws Exception{
//delete using BST method
AANode<T> deleteNode = null;
Stack<AANode<T>> nodes = new Stack<AANode<T>>();
nodes.push(nil); //sentinel
while(t != nil){
nodes.push(t);
if(x.compareTo(t.key)==0){
deleteNode = t;
t = t.left; //find predecessor
}else if(x.compareTo(t.key)<0){
//search the left subtree
t = t.left;
}else {
//search the right subtree
t = t.right;
}
}
if(deleteNode == null){
throw new Exception("No key is found!");
}
//the top element in the stack is the predecessor of the deleted node or the deleted node itself
t = nodes.pop();
if(deleteNode != t){
//t is the predecessor of the deleteNode
deleteNode.key = t.key; //normal BST operation
}//otherwise: t is a single black leaf node (level=1)
//remove the node
if(t.right != nil){
//there is a red right child (which has same level with t)
AANode<T> parent = nodes.pop();
if(parent == nil){
this.root = t.right;
}else{
if(parent.left == t)
parent.left = t.right;
else
parent.right = t.right;
}
//return this.root;
}else{
AANode<T> parent = nodes.pop();
if(parent == nil){
//now return an empty tree
//return nil;
this.root = nil;
}else{
if(parent.left == t){
parent.left = nil;
}else{
parent.right = nil;
}
t=parent;
AANode<T> rootNode = null;
//rebalance
boolean deleteFromRight = false;
while(!nodes.isEmpty() && t != nil){
parent = nodes.peek();
boolean doSkewOrSplit = false;
int currLevel = t.level;
if(t.left.level < currLevel - 1){
if(parent != nil && parent.right == t) {
deleteFromRight = true;
}
t.level--;
if(t.right.level == currLevel) {
t.right.level--;
}
doSkewOrSplit = true;
}else if(t.right.level<currLevel - 1
|| (deleteFromRight && currLevel-1==t.right.level)){
if(parent != nil && parent.right == t) {
deleteFromRight = true;
}
t.level--;
doSkewOrSplit = true;
}
if(doSkewOrSplit){
t = skew(t);
t.right = skew(t.right);
t.right.right = skew(t.right.right);
t = split(t);
t.right = split(t.right);
//set the parent
if(parent != nil) {
if(deleteFromRight)
parent.right = t;
else
parent.left = t;
}
}else{ //no rotation
rootNode = this.root;
break;
}
rootNode = t;
t = nodes.pop();
}
//return rootNode;
this.root = rootNode;
}
}
}
public static void testCases() throws Exception{
AATree<Integer> aaT = new AATree<Integer>();
try{
aaT.delete(0); //throw exception
System.out.println("***Exception Test WRONG");
}catch(Exception e){
if(e.getMessage().startsWith("No key is found!")){
System.out.println("Exception Test OK");
}else{
System.out.println("***Exception Test WRONG");
}
}
System.out.println("*************************");
aaT = new AATree<Integer>();
Integer[] A = new Integer[]{0,1,2,3,4,5,6};
aaT.insert(A);
System.out.println("After Insert:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{6,5,4,3,2};
aaT.insert(A);
System.out.println("After Insert:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(1); //become an empty tree
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.isEmptyTree()){
System.out.println("Empty Tree Test OK");
}else{
System.out.println("***Empty Tree Test WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
aaT.insert(2);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(2); //delete right node (red)
System.out.println("After Delete 2:");
AATree.print(aaT);
if(aaT.getRoot().getKey() == 1){
System.out.println("Delete Red Leaf OK");
}else{
System.out.println("***Delete Red Leaf WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
aaT.insert(1);
aaT.insert(2);
System.out.println("Before Delete:");
AATree.print(aaT);
aaT.delete(1); //delete 1
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().getKey() == 2){
System.out.println("Delete Root OK");
}else{
System.out.println("***Delete Root WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(5);
System.out.println("After Delete 5:");
AATree.print(aaT);
AANode<Integer> node = aaT.findNode(4);
if(aaT.isLeaf(node)){
System.out.println("Leaf Test OK");
}else{
System.out.println("***Leaf Test WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(3);
System.out.println("After Delete 3:");
AATree.print(aaT);
node = aaT.findNode(0);
if(node.getRightChild()==1){
System.out.println("Right Child OK");
}else{
System.out.println("***Right Child WRONG");
}
if(aaT.getRoot().getKey()==2){
System.out.println("Root Key OK");
}else{
System.out.println("***Root Key WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,6,7};
aaT.insert(A);
aaT.insert(5);
System.out.println("Before Delete:");
AATree.print(aaT);
//tree:
/*
3,3
/ /
1,2 6,2
/ / / /
0,1 2,1 4,1 7,1
/
5,1
*/
aaT.delete(4);
System.out.println("After Delete 4:");
AATree.print(aaT);
if(aaT.findNode(5).level==1){
System.out.println("Leaf Level OK");
}else{
System.out.println("***Leaf Level WRONG");
}
if(aaT.findNode(6).level==2){
System.out.println("Subtree's Root Level OK");
}else{
System.out.println("***Subtree's Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{10,85,15,70,20,60,30,50,65,80,90,40,5,55,35};
aaT.insert(A);
System.out.println("Before Insert 45:");
AATree.print(aaT);
aaT.insert(45);
System.out.println("After Insert 45:");
AATree.print(aaT);
if(aaT.getRoot().key==50 && aaT.getRoot().level == 4){
System.out.println("Root(50) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
A = new Integer[]{0,1,2,3,4,5,6};
aaT.insert(A);
System.out.println("Before Delete:");
AATree.print(aaT);
/*
3,3
/ /
1,2 5,2
/ / / /
0,1 2,1 4,1 6,1
*/
Integer[] DEL = new Integer[]{0,3};
aaT.delete(DEL); //until now nothing special
System.out.println("After Delete 0 and 3:");
AATree.print(aaT);
aaT.delete(1);
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().key==4 && aaT.getRoot().level == 2){
System.out.println("Root(4) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
//make a tree
aaT = new AATree<Integer>();
buildTestTree1(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(1);
System.out.println("After Delete 1:");
AATree.print(aaT);
if(aaT.getRoot().key==3 && aaT.getRoot().level == 2){
System.out.println("Root(3) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree1(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
aaT.delete(6);
System.out.println("After Delete 6:");
AATree.print(aaT);
if(aaT.getRoot().key==2 && aaT.getRoot().level == 2){
System.out.println("Root(2) Level OK");
}else{
System.out.println("***Root Level WRONG");
}
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree2(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
System.out.println("*************************");
aaT = new AATree<Integer>();
buildTestTree3(aaT);
System.out.println("Before Delete:");
AATree.print(aaT);
//test the tree
aaT.delete(7);
System.out.println("After Delete 7:");
AATree.print(aaT);
}
private static void buildTestTree1(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(2);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node5);
AANode<Integer> node3 = aaT.makeNode(3);
node3.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 1;
AANode<Integer> node4 = aaT.makeNode(4);
node4.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AATree.attachAsLeftChild(node5, node3);
AATree.attachAsRightChild(node5, node6);
AATree.attachAsRightChild(node3, node4);
AATree.attachAsRightChild(node6, node7);
aaT.setRoot(root);
}
private static void buildTestTree2(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(3);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node6);
AANode<Integer> node2 = aaT.makeNode(2);
node2.level = 1;
AANode<Integer> node4 = aaT.makeNode(4);
node4.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 1;
AATree.attachAsLeftChild(node6, node4);
AATree.attachAsRightChild(node6, node7);
AATree.attachAsRightChild(node4, node5);
AATree.attachAsRightChild(node1, node2);
aaT.setRoot(root);
}
private static void buildTestTree3(AATree<Integer> aaT){
AANode<Integer> root = aaT.makeNode(3);
root.level = 2;
AANode<Integer> node1 = aaT.makeNode(1);
node1.level = 1;
AANode<Integer> node6 = aaT.makeNode(6);
node6.level = 2;
AATree.attachAsLeftChild(root, node1);
AATree.attachAsRightChild(root, node6);
AANode<Integer> node2 = aaT.makeNode(2);
node2.level = 1;
AANode<Integer> node7 = aaT.makeNode(7);
node7.level = 1;
AANode<Integer> node5 = aaT.makeNode(5);
node5.level = 1;
AATree.attachAsLeftChild(node6, node5);
AATree.attachAsRightChild(node6, node7);
AATree.attachAsRightChild(node1, node2);
aaT.setRoot(root);
}
//utility method for test purpose
@SuppressWarnings("rawtypes")
public static void print(AATree aaTree){
AANode root = aaTree.getRoot();
System.out.format("%nin-order BST:%n");
NODES=0;
AATree.recursiveInOrderTraverse(root);
System.out.format("%n%n%nThe tree is:");
AATree.displayBinaryTree(root,NODES);
}
private static int NODES=0;
//utility method for test purpose
@SuppressWarnings("rawtypes")
private static void recursiveInOrderTraverse(AANode root){
if(root.key == null)return;
recursiveInOrderTraverse(root.left);
System.out.format(" (%d,%d)", root.key,root.level);
NODES++;
recursiveInOrderTraverse(root.right);
}
//utility method for test purpose
//n: the nodes number of the tree
@SuppressWarnings("rawtypes")
private static void displayBinaryTree(AANode root,int n){
if(root.key == null) return;
LinkedList<AANode> queue = new LinkedList<AANode>();
//all AANodes in each level
List<List<AANode>> nodesList = new ArrayList<List<AANode>>();
//the positions in a displayable tree for each level's nodes
List<List<Integer>> nextPosList = new ArrayList<List<Integer>>();
queue.add(root);
//int level=0;
int levelNodes = 1;
int nextLevelNodes = 0;
List<AANode> levelNodesList = new ArrayList<AANode>();
List<Integer> nextLevelNodesPosList = new ArrayList<Integer>();
int pos = 0; //the position of the current node
List<Integer> levelNodesPosList = new ArrayList<Integer>();
levelNodesPosList.add(0); //root position
nextPosList.add(levelNodesPosList);
int levelNodesTotal = 1;
while(!queue.isEmpty()) {
AANode node = queue.remove();
if(levelNodes==0){
nodesList.add(levelNodesList);
nextPosList.add(nextLevelNodesPosList);
levelNodesPosList = nextLevelNodesPosList;
levelNodesList = new ArrayList<AANode>();
nextLevelNodesPosList = new ArrayList<Integer>();
//level++;
levelNodes = nextLevelNodes;
levelNodesTotal = nextLevelNodes;
nextLevelNodes = 0;
}
levelNodesList.add(node);
pos = levelNodesPosList.get(levelNodesTotal - levelNodes);
if(node.left.key != null){
queue.add(node.left);
nextLevelNodes++;
nextLevelNodesPosList.add(2*pos);
}
if(node.right.key != null) {
queue.add(node.right);
nextLevelNodes++;
nextLevelNodesPosList.add(2*pos+1);
}
levelNodes--;
}
//save the last level's nodes list
nodesList.add(levelNodesList);
int maxLevel = nodesList.size()-1; //==level
//use both nodesList and nextPosList to set the positions for each node
//Note: expected max columns: 2^(level+1) - 1
int cols = 1;
for(int i=0;i<=maxLevel;i++){
cols <<= 1;
}
cols--;
AANode[][] tree = new AANode[maxLevel+1][cols];
//load the tree into an array for later display
for(int currLevel=0;currLevel<=maxLevel;currLevel++){
levelNodesList = nodesList.get(currLevel);
levelNodesPosList = nextPosList.get(currLevel);
//Note: the column for this level's j-th element: 2^(maxLevel-level)*(2*j+1) - 1
int tmp = maxLevel-currLevel;
int coeff = 1;
for(int i=0;i<tmp;i++){
coeff <<= 1;
}
for(int k=0;k<levelNodesList.size();k++){
int j = levelNodesPosList.get(k);
int col = coeff*(2*j + 1) - 1;
tree[currLevel][col] = levelNodesList.get(k);
}
}
//display the binary search tree
System.out.format("%n");
for(int i=0;i<=maxLevel;i++){
for(int j=0;j<cols;j++){
AANode node = tree[i][j];
if(node== null || node.key == null)
System.out.format(" ");
else
System.out.format("%2d,%d",node.key,node.level);
}
System.out.format("%n");
}
}
public void setRoot(AANode<T> root) {
this.root = root;
}
public boolean isEmptyTree(){
return this.root == nil;
}
public AANode<T> makeNode(T key){
return new AANode<T>(key,nil,nil);
}
public static <T extends Comparable<T>> void attachAsLeftChild(AANode<T> parent,AANode<T> node){
parent.left = node;
}
public static <T extends Comparable<T>> void attachAsRightChild(AANode<T> parent,AANode<T> node){
parent.right = node;
}
public boolean isLeaf(AANode<T> node){
return node.getLeft()==nil && node.getRight() == nil;
}
public AANode<T> findNode(T key) throws Exception{
AANode<T> node = this.root;
while(node != nil){
if(key.compareTo(node.key)==0){
break;
}else if(key.compareTo(node.key)<0){
node = node.left;
}else if(key.compareTo(node.key)>0){
node = node.right;
}
}
if(node == nil){
throw new Exception("No such key is found!");
}
return node;
}
public static void main(String[] args) throws Exception {
testCases();
}
}
性能测试:从磁盘读入一百万条数据到内存,然后依次查找、插入和删除各50万次。
先是性能测试代码:
其中prepareTestData(dataFilename,N)语句只需执行一次,用于写入一百万个不重复的整数到磁盘文件中:
[java] view
plaincopy
public static void main(String[] args) throws Exception {
String dataFilename = "C://tmp//aatree.txt";
int N = 1000000; //the number of keys
//******Prepare Data(only execute once for multiple tests)******
prepareTestData(dataFilename,N);
//******Begin Test******
AATree<Integer> aaT = new AATree<Integer>();
DataInputStream in= new DataInputStream(new FileInputStream(dataFilename));
//long startTime = System.currentTimeMillis();
int key = -1;
int keyCount = 0;
while(true){
try{
key = in.readInt();
aaT.insert(key);
keyCount++;
}catch(EOFException e){
break;
}
}
in.close();
//long endTime = System.currentTimeMillis();
//System.out.format("file io and insert time spent: %d(ms)%n",(endTime-startTime));
System.out.format("Total number of keys to begin with: %d%n",keyCount);
long t1=0,t2=0;
//do some operations in memory
long startTime = System.currentTimeMillis();
t1 = startTime;
int totalSuccess = 0;
int totalFailed = 0;
System.out.format("%n***Search Operations***%n");
//try to find a number of keys
int rangeStart = 1100000;
int rangeEnd = 1600000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
AANode<Integer> node = aaT.findNode(i);
//System.out.println("found key: " + node.key);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//not found
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to find: %d%n",rangeEnd-rangeStart);
System.out.format("The number of keys found: %d%n",totalSuccess);
System.out.format("The number of keys not found: %d%n",totalFailed);
//insert a number of new keys
System.out.format("%n***Insertion Operations***%n");
t1 = System.currentTimeMillis();
totalSuccess = 0;
totalFailed = 0;
rangeStart = 1400000;
rangeEnd = 1900000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
aaT.insert(i);
//System.out.println("inserted key: " + i);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//duplicate insert
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to insert: %d%n",rangeEnd - rangeStart);
System.out.format("The number of keys inserted: %d%n",totalSuccess);
System.out.format("The number of keys with insertion failed due to duplicate: %d%n",totalFailed);
//delete a number of new keys
System.out.format("%n***Deletion Operations***%n");
t1 = System.currentTimeMillis();
totalSuccess = 0;
totalFailed = 0;
rangeStart = 400000;
rangeEnd = 900000;
for(int i=rangeStart;i<rangeEnd;i++){
try{
aaT.delete(i);
//System.out.println("deleted key: " + i);
totalSuccess++;
}catch(Exception e){
totalFailed++;
//not found
//System.out.println(e.getMessage());
}
}
t2 = System.currentTimeMillis();
System.out.format("Time spent: %d(ms)%n",(t2-t1));
System.out.format("The number of keys we try to delete: %d%n",rangeEnd-rangeStart);
System.out.format("The number of keys deleted: %d%n",totalSuccess);
System.out.format("The number of keys with deletion failed due to non-existence: %d%n",totalFailed);
long endTime = t2;
System.out.format("%nThe above operations(search,insert,delete) total spent: %d(ms)%n",(endTime-startTime));
}
//write non-duplicate keys into external file for later test
private static void prepareTestData(String dataFilename,int totalKeys) throws IOException{
AATree<Integer> aaT = new AATree<Integer>();
//multiply by a larger factor(here it is 10) to eliminate the duplicate insert
int KEYRANGE = totalKeys*10;
int i=0;
while(i<totalKeys){
int rand = (int)(Math.random()*KEYRANGE);
try{
aaT.insert(rand);
i++;
//System.out.format("insert: %d%n",rand);
}catch(Exception e){
//for duplicate insert: retry
//System.out.println(e.getMessage());
}
}
DataOutputStream out= new DataOutputStream(new FileOutputStream(dataFilename));
persistKeys(aaT.getRoot(),out);
out.close();
}
private static void persistKeys(AANode<Integer> root,DataOutputStream out) throws IOException{
if(root.key == null)return;
persistKeys(root.left,out);
out.writeInt(root.key);
persistKeys(root.right,out);
}
性能测试报告(Intel Core2 T5500 1.66GHz, 2GB RAM):
Total number of keys to begin with: 1000000
***Search Operations***
Time spent: 2172(ms)
The number of keys we try to find: 500000
The number of keys found: 49955
The number of keys not found: 450045
***Insertion Operations***
Time spent: 1984(ms)
The number of keys we try to insert: 500000
The number of keys inserted: 450090
The number of keys with insertion failed due to duplicate: 49910
***Deletion Operations***
Time spent: 3172(ms)
The number of keys we try to delete: 500000
The number of keys deleted: 7
The number of keys with deletion failed due to non-existence: 499993
The above operations(search,insert,delete) total spent: 7328(ms)
相关文章推荐
- private ;protected
- 23-IO流-47-IO流(综合练习-文件清单列表)
- C#与C++数据类型比较及结构体转换(搜集整理二)
- Objective-C:动态绑定
- 正则表达式(一)
- c# 获取IP
- 密码管理器LastPass和KeePass对比评测
- windows 使用源代码包安装Yii2 高级模板
- Android—[INFO:CONSOLE(55)] "Uncaught ReferenceError: $ is not defined", source: file:///android_asse
- 为了面试,准备的知识点
- Vim配置:插件+常用设置(不定时更新)
- Music
- HDU 2049 不容易系列之(4)——考新郎 (错排公式+递归)
- 一种机器人感知空间与智主行走的建模识别方法
- UVA 156 Ananagrams
- 23-IO流-46-IO流(Properties集合的-练习)
- 浅谈 Javascript 事件处理程序的几种方式
- U 跳转中加入变量参数的写法
- bex5中的常用方法总结
- 实现div的背景图片在各个浏览器上自适应显示:即backgroun-size属性不支持低版本ie的解决方案