AVL樹-自平衡二叉查詢樹(Java實現)
在電腦科學中,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之間標實線)
2. RR型
平衡二叉樹某一節點的右孩子的右子樹上插入一個新的節點,使得該節點不再平衡。這時只需要把樹向左旋轉一次即可,如圖所示,原A右孩子B變為父結點,A變為其左孩子,而原B的左子樹Blh將變為A的右子樹。
3. LR型
平衡二叉樹某一節點的左孩子的右子樹上插入一個新的節點,使得該節點不再平衡。這時需要旋轉兩次,僅一次的旋轉是不能夠使二叉樹再次平衡。如圖所示,在B節點按照RR型向左旋轉一次之後,二叉樹在A節點仍然不能保持平衡,這時還需要再向右旋轉一次。
4. RL型
平衡二叉樹某一節點的右孩子的左子樹上插入一個新的節點,使得該節點不再平衡。同樣,這時需要旋轉兩次,旋轉方向剛好同LR型相反。
二、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);
}
}
/*註明:由於刪除操作考慮的情況甚多,程式碼中出現的列印資訊主要為方便排錯*/
測試用例如下:import static org.junit.Assert.*;
import java.util.Random;
import org.junit.Test;
public class AvlTreeTest {
private AvlTree<Integer> avlTree = new AvlTree<Integer>();
@Test
public void testInsert(){
avlTree.insert(100);
avlTree.insert(120);
avlTree.insert(300);
avlTree.insert(500);
avlTree.insert(111);
avlTree.insert(92);
avlTree.insert(77);
avlTree.insert(125);
System.out.println(avlTree.contains(120));
avlTree.remove(120);
avlTree.remove(125); //需要單旋轉
System.out.println(avlTree.contains(120));
avlTree.insert(78); //需要雙旋轉
System.out.println("Insert Success !");
}
@Test
public void testRotate(){
avlTree.insert(100);
avlTree.insert(90);
avlTree.insert(92);
avlTree.insert(78);
avlTree.insert(76);
System.out.println("Insert Success !");
}
/**
* 通過較大資料進行測試,暫時還沒有發現問題
*/
@Test
public void testAll(){
avlTree.makeEmpty();
Random random = new Random();
for(int i=1;i<=1000000;i++){
avlTree.insert(random.nextInt(1000000));
}
for(int i=2000000;i>=1000000;i--){
avlTree.insert(i);
}
/*for(int i=700000;i>=400000;i--){
avlTree.insert(i);
}
for(int i=100000;i<=200000;i++){
avlTree.insert(i);
}
for(int i=400000;i<=500000;i++){
avlTree.insert(random.nextInt(600000));
}*/
for(int i=200000;i<1400000;i++){
int target = random.nextInt(1500000);
if(avlTree.contains(target)){
avlTree.remove(target);
}
}
System.out.println("Insert Success !");
}
}