AVL樹的JAVA實現及AVL樹的旋轉算法


1,AVL樹又稱平衡二叉樹,它首先是一顆二叉查找樹,但在二叉查找樹中,某個結點的左右子樹高度之差的絕對值可能會超過1,稱之為不平衡。而在平衡二叉樹中,任何結點的左右子樹高度之差的絕對值會小於等於 1。

2,為什么需要AVL樹呢?在二叉查找樹中最壞情況下查找某個元素的時間復雜度為O(n),而AVL樹能保證查找操作的時間復雜度總為O(logn)。

     對於一棵BST樹而言,不僅有查找操作,也有插入、刪除等改變樹的形態的操作。隨着不斷地插入、刪除,BST樹有可能會退化成鏈表的形式,使得查找的時間復雜度變成O(N),這種情形下,BST樹的結構非常不平衡了。為了保持樹的平衡,需要對樹的形態做一些限制,因此,引入了AVL樹,以保證樹的左右子樹高度之差的絕對值小於等於1。

3,AVL樹的JAVA代碼實現:

 AVLTree  繼承 BinarySearchTree 並改寫 添加節點的add方法,在add方法中判斷插入元素后是否導致樹不平衡,當不平衡時需要通過旋轉進行調整。何時進行旋轉調整是通過左右子樹的高度之差進行判斷的。這里通過rebalance方法使得某結點保持平衡:整個程序的完成代碼參考最后。

private BinaryNodeInterface<T> rebalance(BinaryNodeInterface<T> nodeN){
        int heightDifference = getHeightDifference(nodeN);
        if(heightDifference > 1){//左子樹比右子樹高
            if(getHeightDifference(nodeN.getLeftChild()) > 0)
                nodeN = rotateRight(nodeN);//插入在左子樹的左孩子中
            else
                nodeN = rotateLeftRight(nodeN);
        }
        else if(heightDifference < -1){//右子樹比左子樹高
            if(getHeightDifference(nodeN.getRightChild()) < 0)
                nodeN = rotateLeft(nodeN);//插入在右子樹的右孩子中
            else
                nodeN = rotateRightLeft(nodeN);
        }
        return nodeN;
    }

 

4,AVL樹的平衡保證算法

當向AVL樹中插入或者刪除元素時,可能打破樹的平衡。這時,需要通過旋轉來調整使之重新變成一顆AVL樹。(這里討論插入元素時的調整)

設 N 表示最接近新葉子的不平衡結點,由於插入元素之前樹是平衡的,則插入之后不會有比 N 更高的不平衡結點。(樹根的高度為 1 )

一共有 4 種原因導致結點 N 不平衡:

①在 結點 N 的左孩子的左子樹中發生了插入操作

進行右旋轉調整:即對結點 N 進行右旋轉,算法如下:

算法 rotateRight(nodeN)
nodeL = nodeN 的左孩子
將nodeN 的左孩子置為 nodeL 的右孩子
將 nodeL 的右孩子置為 nodeN
return nodeL

 

②在 結點N 的右孩子的右子樹中發生了插入操作

進行左旋轉調整:即對結點N進行左旋轉,算法如下:

算法 rotateLeft(nodeN)
nodeR = nodeN 的右孩子
將 nodeN 的右孩子置為 nodeR 的左孩子
將 nodeR 的左孩子置為 nodeN
return nodeR

 

③在 結點N 的右孩子的左子樹中發生了插入操作

進行右-左旋轉,即先對結點N 的孫子(孩子的孩子,"位於新插入元素的那個方向")進行右旋轉;再對結點N 的新孩子進行左旋轉,算法如下:

算法 rotateRightLeft(nodeN)
nodeR = nodeN 的右孩子
將 nodeN 的右孩子置為由 rotateRight(nodeN 的孩子的孩子)返回的結點
return rotateLeft(nodeN 的新的右孩子)

 

④在 結點N 的左孩子的右子樹中發生了插入操作

進行左-右旋轉,即先對結點N的孫子進行左旋轉,再對結點N的新孩子進行右旋轉,算法如下:

算法 rotateLeftRight(nodeN)
nodeL = nodeN 的左孩子
將 nodeN 的左孩子置為由 rotateLeft(nodeN 孩子的孩子)返回的結點
return rotateLeft(nodeN 新的左孩子)

 

5,關於樹的平衡性的進一步討論

除了AVL樹之外,還有2-3樹、2-4樹、紅黑樹等一系列帶有平衡性質的樹。其中2-3樹和2-4樹是完全平衡的,即所有的葉子位於同一層。2-3樹的結點至多有兩個數據元素,2-4樹的結點至多有三個數據元素,這樣使得在相同的結點數目下,一般,AVL樹比2-3樹要高,2-3樹比2-4樹要高,但是在查找過程中內部結點的比較次數2-4樹比2-3要多,2-3樹比AVL樹要多。因此,總體上看,AVL樹、2-3樹、2-4樹、紅黑樹的查找都是O(logn)操作。維護平衡性由難到易的排列: AVL樹 > 2-3樹 > 2-4樹 ·= 紅黑樹。其次,隨着對於結點含有3個以上的數據元素的查找樹的性能反而會降低,因此這也是為什么沒有2-5樹……的原因。。。

 

2018.12更新

6, 紅黑樹與AVL樹的對比

不管是紅黑樹還是AVL樹,不僅要有效地支持查找,還需要有效地支持插入和刪除,插入和刪除操作會改變樹的形態,如果不做一定的調整,樹的結構就會變得不平衡,因此不管是AVL樹還是紅黑樹都有“旋轉”操作。
但是,二者的“旋轉”操作的代價是不一樣的。在最壞的情況下,AVL樹的旋轉操作代價能達到O(logN),而紅黑樹的旋轉操作代價為O(1)。
由於插入、刪除操作會引發樹的旋轉,因此:紅黑樹比AVL樹更適合於插入、刪除元素更頻繁的情形,而AVL樹更適合於查詢非常多,插入、刪除很少的場景。
另外,紅黑樹在JDK集合框架中應用廣泛,比如HashMap就引入了紅黑樹,可以讓HashMap在存在大量的鍵沖突的情況下,仍然能夠保證快速地查詢。
在正常情況下,HashMap的get操作時間復雜度為O(1),在存在大量沖突時,get操作時間復雜度退化為O(logN)

 

AVL樹的完整JAVA代碼實現:

  1 package searchtree;
  2 
  3 import tree.BinaryNode;
  4 import tree.BinaryNodeInterface;
  5 
  6 public class AVLTree<T extends Comparable<? super T>> extends BinarySearchTree<T> implements SearchTreeInterface<T>,java.io.Serializable {
  7     public AVLTree(){
  8         super();
  9     }
 10     
 11     public AVLTree(T rootEntry){
 12         super(rootEntry);
 13     }
 14 
 15     /*
 16      *@Task : 在AVL樹中添加元素
 17      */
 18     public T add(T newEntry){
 19         T result = null;
 20         if(isEmpty())
 21             setRootNode(new BinaryNode<T>(newEntry));
 22         else
 23         {
 24             BinaryNodeInterface<T> rootNode = getRootNode();
 25             result = addEntry(rootNode, newEntry);
 26             setRootNode(rebalance(rootNode));
 27         }
 28         return result;
 29     }
 30     
 31     private T addEntry(BinaryNodeInterface<T> rootNode, T newEntry){
 32         assert rootNode != null;
 33         T result = null;
 34         int comparison = newEntry.compareTo(rootNode.getData());//待添加元素與樹中已有元素比較以確定添加的位置
 35         if(comparison == 0){//待添加元素已存在於樹中
 36             result = rootNode.getData();
 37             rootNode.setData(newEntry);//將新元素替換舊元素
 38         }
 39         else if(comparison < 0){//添加到左子樹中
 40             if(rootNode.hasLeftChild()){//繼承遞歸比較
 41                 BinaryNodeInterface<T> leftChild = rootNode.getLeftChild();
 42                 result = addEntry(leftChild, newEntry);
 43                 rootNode.setLeftChild(rebalance(leftChild));
 44             }
 45             else
 46                 rootNode.setLeftChild(new BinaryNode<T>(newEntry));
 47         }
 48         else//添加到右子樹中
 49         {
 50             assert comparison > 0;
 51             if(rootNode.hasRightChild()){
 52                 BinaryNodeInterface<T> rightChild = rootNode.getRightChild();
 53                 result = addEntry(rightChild, newEntry);
 54                 rootNode.setRightChild(rebalance(rightChild));
 55             }
 56             else
 57                 rootNode.setRightChild(new BinaryNode<T>(newEntry));
 58         }
 59         return result;
 60     }
 61     
 62     public T remove(T newEntry){
 63         return null;//暫未實現刪除操作
 64     }
 65     
 66     /*
 67      * @Task: 在 nodeN 結點上進行右旋操作
 68      */
 69     private BinaryNodeInterface<T> rotateRight(BinaryNodeInterface<T> nodeN){
 70         BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();
 71         nodeN.setLeftChild(nodeL.getRightChild());
 72         nodeL.setRightChild(nodeN);
 73         return nodeL;
 74     }
 75     
 76     private BinaryNodeInterface<T> rotateLeft(BinaryNodeInterface<T> nodeN){
 77         BinaryNodeInterface<T> nodeR = nodeN.getRightChild();
 78         nodeN.setRightChild(nodeR.getLeftChild());
 79         nodeR.setLeftChild(nodeN);
 80         return nodeR;
 81     }
 82     
 83     private BinaryNodeInterface<T> rotateRightLeft(BinaryNodeInterface<T> nodeN){
 84         BinaryNodeInterface<T> nodeR = nodeN.getRightChild();
 85         nodeN.setRightChild(rotateRight(nodeR));
 86         return rotateLeft(nodeN);
 87     }
 88     
 89     private BinaryNodeInterface<T> rotateLeftRight(BinaryNodeInterface<T> nodeN){
 90         BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();
 91         nodeN.setLeftChild(rotateLeft(nodeL));
 92         return rotateRight(nodeN);
 93     }
 94     
 95     private BinaryNodeInterface<T> rebalance(BinaryNodeInterface<T> nodeN){
 96         int heightDifference = getHeightDifference(nodeN);
 97         if(heightDifference > 1){//左子樹比右子樹高
 98             if(getHeightDifference(nodeN.getLeftChild()) > 0)
 99                 nodeN = rotateRight(nodeN);//插入在左子樹的左孩子中
100             else
101                 nodeN = rotateLeftRight(nodeN);
102         }
103         else if(heightDifference < -1){//右子樹比左子樹高
104             if(getHeightDifference(nodeN.getRightChild()) < 0)
105                 nodeN = rotateLeft(nodeN);//插入在右子樹的右孩子中
106             else
107                 nodeN = rotateRightLeft(nodeN);
108         }
109         return nodeN;
110     }
111     
112     //獲得結點nodeN的左右子樹的高度之差
113     private int getHeightDifference(BinaryNodeInterface<T> nodeN){
114         int leftHeight = 0;
115         int rightHeight = 0;
116         if(nodeN.getLeftChild() != null){
117             leftHeight = nodeN.getLeftChild().getHeight();
118         }
119         if(nodeN.getRightChild() != null){
120             rightHeight = nodeN.getRightChild().getHeight();
121         }
122         return leftHeight - rightHeight;
123     }
124 }

 

整個實現的依賴代碼參考:https://github.com/hapjin/data-structures-and-abstraction-with-java 中的tree、searchtree、list 目錄下代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM