1. 程式人生 > >AVL-平衡二叉樹的原理和實現

AVL-平衡二叉樹的原理和實現

一、簡介

  本文將通過圖解和程式碼詳細講解AVL平衡二叉樹的性質及失衡和再平衡的內容。在看本文之前希望大傢俱備二分搜尋樹的相關知識。或移步《二分搜尋樹》瞭解二分搜尋樹。

 

二、平衡二叉樹

  前面關於二分搜尋樹的文章,最後分析了在極端情況下,二分搜尋樹會退化為一個連結串列,那為了避免這種情況的發生,AVL平衡二叉樹應運而生。

  平衡二叉樹的定義:

1 平衡二叉樹是一顆二分搜尋樹,及平衡二叉樹滿足二分搜尋樹的所有性質
2 平衡二叉樹要求任意一個節點的左右子樹的高度差不能超過1

  對於第一點應該挺容易理解的,對於第二點,我們要做一點解釋。對於高度差,有一個專有名詞平衡因子。

  平衡因子:左子樹的高度減去右子樹的高度,及B = B左 - B右。由平衡二叉樹的定義可知,平衡因子的取值只可能為0,1,-1。0:左右子樹等高。1:左子樹比較高。-1:右子樹比較高。如下圖

  高度:一般的我們取葉子節點的高度值為1,任意一個節點的高度值取左右子樹比較高的那個孩子節點的高度值然後加1。比如上圖1中20這個節點的高度值,顯然左子樹比較高,所以H20 = H10 + 1;依次類推,H10 = H6(或者H14) + 1 = 2;所以H20 = 3;

  上圖1的樹的各個節點的高度,如下圖1所示。各個節點的平衡因子如下圖2紅色數字所示。所以根據定義,各個節點的左右子樹的高度差不能超過1,及任意一個節點的平衡因子應該為 -1, 0, 1;所以下面的這棵樹是一棵平衡二叉樹。

 

三、AVL的失衡及再平衡--旋轉

 

3.1、AVL的失衡情況

 

  前面我們介紹了AVL的高度和平衡因子問題,接下來我們來看看有幾種情況會導致AVL的失衡,也就是什麼情況下我們需要調整AVL樹,使其經過調整後能繼續維持平衡狀態。

  如上圖1所示,當我們已經插入了20,10元素,當我們再插入6這個元素的時候,很顯然20節點的平衡因子為2,及B20 = 2 > 1此時該樹已經不平衡了。

  如上圖2所示,當我們已經插入了20,10元素,當我們再插入14這個元素的時候,很顯然20節點的平衡因子為2,及B20 = 2 > 1此時該樹已經不平衡了。

  如上圖3所示,當我們已經插入了20,29元素,當我們再插入33這個元素的時候,很顯然20節點的平衡因子為-2,及B20 = -2 < -1此時該樹已經不平衡了。

  如上圖4所示,當我們已經插入了20,29元素,當我們再插入25這個元素的時候,很顯然20節點的平衡因子為-2,及B20 = -2 < -1此時該樹已經不平衡了。

  對於AVL,需要進行再平衡操作的情況正如以上4個圖所示。那接下來我們需要討論的問題就是如何調整了,及旋轉。

 

3.2、AVL的再平衡--旋轉

  如下圖所示,對於上一章我們說的四種失衡狀態的調整。然後我們加下來對照著下面的四張圖片進行逐一介紹旋轉的過程。

  

3.2.1、LL旋轉

  LL,及對於上圖1的待旋轉的樹(中間那棵),造成這棵樹失衡的節點6在20節點的左孩子的左孩子處,left,left簡稱為LL。這個時候我們需要進行的旋轉一般稱之為LL旋轉。對於這種情況,我們考慮如何旋轉,始終要考慮如何通過旋轉既達到再平衡的目的,又能維持平衡二叉樹的性質不變,即左孩子 < 父節點 < 右孩子。觀察圖一中插入節點6以後,一個很顯然的結果就是不管我們怎麼旋轉只有當10節點在中間的時候我們才能保證這棵樹是平衡的。我們知道了結果,再看看這個旋轉的過程,為了保證平衡二叉樹的性質,根據左孩子 < 父節點 < 右孩子的性質,我們看20 > 10,也就是說,我們可以將20節點下移,放到10節點的右孩子處,即得到圖1中的結果。

  為了更好地描述這個過程,我們使用幾個虛擬的節點。

    ///////////////////////////////////////////////////
    // LL T1<Z<T2< X <T3<Y<T4                        //
    //        y                              x       //
    //       / \                           /   \     //
    //      x   T4     向右旋轉 (y)        z     y    //
    //     / \       - - - - - - - ->    / \   / \   //
    //    z   T3                        T1 T2 T3 T4  //
    //   / \                                         //
    // T1   T2                                       //
    ///////////////////////////////////////////////////

 

   如上所示,我們真實的三個節點為Y > X > Z。然後我們為了方便描述,增加幾個虛擬的節點,節點間的大小關係:T1<Z<T2< X <T3<Y<T4

  對於LL,我們要右旋才能達到再平衡,根據之前描述,我們需要將Y節點頂替T3的位置,問題來了,T3放哪呢?根據大小關係 X < T3 < Y。我們可以將T3放到Y的左孩子節點的位置,這樣進行旋轉後得到的結果如上所示。我們發現這棵樹不但達到了再平衡的目的,節點間的大小關係,依然維持了:T1<Z<T2< X <T3<Y<T4的關係。

  程式碼實現一下這個過程,先假設我們的節點為Node。傳入的引數應該是Y節點

 1     private Node rightRotate(Node y) {
 2         Node x = y.left;
 3         Node T3 = x.right;
 4 
 5         // 向右旋轉過程
 6         x.right = y;
 7         y.left = T3;
 8 
 9         return x;
10     }

 

  對於以上程式碼,結合上面我們分析的過程,大家應該很容易就能理解。

 

3.2.2、RR旋轉

  RR對應上面的圖3,RR及造成AVL失衡的節點6在20節點的右側的右側,即RR。對於RR我們要進行左旋轉才能實現再平衡。同樣的,我們如果想通過旋轉達到再平衡,AVL樹的性質依然是我們實現這個操作的根本。如上圖3所示,如果我們將20節點移到29元素的左孩子節點處,便可實現再平衡。而且也能維持AVL樹的基本性質。

  同分析LL一樣,我們增加一些虛擬節點來描述這個過程。

    ////////////////////////////////////////////////
    // RR T1<Y<T2< X <T3<Z<T4                     //
    //    y                             x         //
    //  /  \                          /   \       //
    // T1   x      向左旋轉 (y)       y     z      //
    //     / \   - - - - - - - ->   / \   / \     //
    //    T2  z                    T1 T2 T3 T4    //
    //       / \                                  //
    //      T3 T4                                 //
    ////////////////////////////////////////////////

 

  節點間的大小關係:T1<Y<T2< X <T3<Z<T4。對於RR我們對Y節點進行左旋轉。即讓Y節點頂替T2,然後根據大小關係:Y < X < T2可知,我們可以將T2放到Y的右孩子節點處即可。對Y節點左旋完了如上圖所示的結果。通過比較,節點間的大小關係,依然為:T1<Y<T2< X <T3<Z<T4。通過對Y節點的左旋轉,達到了AVL的再平衡,並維持了AVL的性質不變。

  程式碼實現就不解釋了

 1     private Node leftRotate(Node y) {
 2         Node x = y.right;
 3         Node T2 = x.left;
 4 
 5         // 向左旋轉過程
 6         x.left = y;
 7         y.right = T2;
 8 
 9         return x;
10     }

 

 

3.2.3、LR

  LR對應上圖2,即造成AVL失衡的節點14在節點20的左側的右側,即LR。這種情況有點複雜,而且有個很想當然的坑,就是將根節點直接換成10不就完事了?可是如果我們這麼做,發現,10的左節點為14,不滿足:左孩子 < 父節點 < 右孩子的大小關係了。這種情況呢,正確的做法是先將10節點左旋,然後再將14節點右旋。大家通過之前對LL和RR的分析,在腦子中能不能想象到這個畫面呢?

  為了方便描述,我們依然增加一些虛假的節點來描述這個過程。

    //////////////////////////////////////////////////////////////////////////////////////////
    //  LR  T1<X<T2< Z <T3<Y<T4                                                             //
    //         y                                y                              z            //
    //        / \                              / \                           /   \          //
    //       x  t4    向左旋轉(x)             z   T4      向右旋轉(y)      x     y         //
    //      / \     --------------->         / \        --------------->   / \   / \        //
    //     T1  z                            x   T3                        T1  T2 T3 T4      //
    //        / \                          / \                                              //
    //       T2  T3                      T1   T2                                            //
    //////////////////////////////////////////////////////////////////////////////////////////

 

  對於原始的這棵樹呢,大小關係:T1<X<T2< Z <T3<Y<T4。如果我們先不看Y節點,看X,Z,T3節點,是不是可以發現,這正是我們上面描述的RR的情況啊。對RR,我們上面已經進行了詳細的分析,通過貴X節點進行左旋,得到中間那棵樹。這時又一個神奇的事情發生了,這棵樹的形狀又變成了前面我們說的,LL的情況。那大家就清楚了,對Y節點進行右旋轉即可。最終的結果如上第三棵樹,達到了AVL的再平衡並依然滿足:T1<X<T2< Z <T3<Y<T4。

  我們發現經過我們的分析,將這種複雜的情況進行一步步的拆解即分解成了比較簡單的情況。不得不感嘆一下:計算機的世界太神奇了。

 

3.2.4、RL

  呃呵,自己看吧,不解釋,不接受反駁。皮一下,很開心。

    //////////////////////////////////////////////////////////////////////////////////////////
    // RL: T1<Y<T2< Z <T3<X<T4                                                              //
    //      y                           y                                       z           //
    //     / \                         / \                                    /   \         //
    //    T1  x       向右旋轉(x)    T1  z         向左旋轉(y)              y     x        //
    //       / \    - - - - - - ->       / \      - - - - - - - - ->        / \   / \       //
    //      z  T4                       T2  x                              T1 T2 T3 T4      //
    //     / \                             / \                                              //
    //    T2  T3                          T3  T4                                            //
    //////////////////////////////////////////////////////////////////////////////////////////

  相信大家看到這裡,被面試官虐千百遍的問題,原來不過如此。其實一切高大上的問題,只要我們耐心的看下去就能有收穫。

 

3.3、再平衡的時機

 

  首先需要明白,AVL的失衡是由於節點的變動引起的,也就是增和刪操作才會導致節點的變動。下面我們結合平衡因子和插入或者刪除的過程,分析AVL再平衡的時機。

  增加操作再平衡時機:

  對於3.2章節中的過程,希望大家可以清楚,我們是通過眼睛觀察來判斷AVL是不是失衡了,但是計算機還沒有達到這種能力。所以我們想想前面介紹的平衡因子,正是判斷AVL是不是平衡的重要依據。假如現在向一棵空的AVL樹依次插入[20,10,6];三個節點。當插入6節點後,如下圖所示,各個節點的高度。之前說過:B = H左 - H右。我們看一下20這個節點的平衡因子,B20 = 2 - 0 = 2 > 1;所以,這時20就是不平衡的節點,需要對20這個節點進行旋轉才能再平衡。但是從元素插入操作看一下,很顯然當插入6這個元素的時候,並不知道20這個節點的平衡因子已經不滿足要求了。需要沿著新增的元素向上回溯,沿著該節點到根節點的路徑,一步步的重新計算其父節點,爺爺節點,祖父節點...當我們發現其父節點,爺爺節點...等平衡因子不滿足要求的時候,就對該節點進行旋轉。

  刪除操作再平衡的時機:

  刪除操作進行再平衡的時機類似增加操作,需要在刪除節點後沿著其父節點,爺爺節點...一直向上計算各個節點的平衡因子是否滿足AVL的性質。當發現某個節點的平衡因子不在[-1, 1]之間的時候,然後判斷其形狀對應的進行左旋轉或者右旋轉使其完成再平衡。

 

四、程式碼實現一棵AVL

  

  在前面的章節詳細介紹了AVL的定義,失衡,再平衡即LL,RR,LR,RL等旋轉。接下來我們通過程式碼實現一棵AVL樹。對於我們要實現的AVL平衡二叉樹,我們期待具備的功能如下:

1 以Node作為連結串列的基礎儲存結構
2 使用泛型,並要求該泛型必須實現Comparable介面
3 基本操作:增刪改查

 

  

4.1、AVL的基礎程式碼

 1 /**
 2  * 描述:AVL 平衡二叉樹的實現
 3  *
 4  * @Author shf
 5  * @Date 2019/7/31 15:35
 6  * @Version V1.0
 7  **/
 8 public class AVL<K extends Comparable<K>, V> {
 9 
10     private class Node{
11         public K key;
12         public V value;
13         public Node left, right;
14         public int height;// 記錄節點的高度
15 
16         public Node(K key, V value){
17             this.key = key;
18             this.value = value;
19             left = null;
20             right = null;
21             height = 1;
22         }
23     }
24 
25     private Node root;
26     private int size;
27 
28     public AVL(){
29         root = null;
30         size = 0;
31     }
32 
33     public int getSize(){
34         return size;
35     }
36 
37     public boolean isEmpty(){
38         return size == 0;
39     }
40 }

  

  在實現增刪改查之前我們先設計兩個輔助方法,如下所示,getHeight方法獲取節點的高度值,getBalanceFactor方法獲取節點的平衡因子。

 1     /**
 2      * 獲得節點node的高度
 3      * @param node
 4      * @return
 5      */
 6     private int getHeight(Node node){
 7         if(node == null)
 8             return 0;
 9         return node.height;
10     }
11 
12     /**
13      * 獲得節點node的平衡因子
14      * @param node
15      * @return
16      */
17     private int getBalanceFactor(Node node){
18         if(node == null)
19             return 0;
20         return getHeight(node.left) - getHeight(node.right);
21     }

 

 

4.2、增

  在《二分搜尋樹》介紹了二分搜尋樹,前面根據AVL的定義可知,AVL是完全滿足一個二分搜尋樹的所有性質的,如果大家想搞明白AVL,還是建議去先去看一下二分搜尋樹。對於二分搜尋樹的新增操作的程式碼實現,如下所示:

 1     /**
 2      * 新增元素
 3      * @param e
 4      */
 5     public void add(E e){
 6         root = add(root, e);
 7     }
 8 
 9     /**
10      * 新增元素 - 遞迴實現
11      * 時間複雜度 O(log n)
12      * @param node
13      * @param e
14      * @return 返回根節點
15      */
16     public Node add(Node node, E e){
17         if(node == null){// 如果當前節點為空,則將要新增的節點放到當前節點處
18             size ++;
19             return new Node(e);
20         }
21         if(e.compareTo(node.e) < 0){// 如果小於當前節點,遞迴左孩子
22             node.left = add(node.left, e);
23         } else if(e.compareTo(node.e) > 0){// 如果大於當前節點,遞迴右孩子
24             node.right = add(node.right, e);
25         }
26         return node;
27     }

 

  如果你還沒法理解上面的程式碼,請移步《二分搜尋樹》。對於AVL的新增操作,無非就是在AVL中需要考慮在二分搜尋樹失衡的時候,如何通過旋轉達到再平衡。根據我們前面的介紹,我們應該已經很明白旋轉的思路了,那我們直接上程式碼吧。

 

 1    /**
 2      * 向以node為根的二分搜尋樹中插入元素(key, value),遞迴演算法
 3      * 時間複雜度 O(log n)
 4      * @param node
 5      * @param key
 6      * @param value
 7      * @return 返回插入新節點後二分搜尋樹的根
 8      */
 9     private Node add(Node node, K key, V value){
10 
11         if(node == null){
12             size ++;
13             return new Node(key, value);
14         }
15 
16         if(key.compareTo(node.key) < 0)
17             node.left = add(node.left, key, value);
18         else if(key.compareTo(node.key) > 0)
19             node.right = add(node.right, key, value);
20         else // key.compareTo(node.key) == 0
21             node.value = value;
22 
23         // 更新height
24         node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
25 
26         // 計算平衡因子
27         int balanceFactor = getBalanceFactor(node);
28 
29         // 平衡維護
30         //////////////////////////////////////////////////////
31         // LL  T1<Z<T2< X <T3<Y<T4                          //
32         //        y                              x          //
33         //       / \                           /   \        //
34         //      x   T4     向右旋轉 (y)        z     y       //
35         //     / \       - - - - - - - ->    / \   / \      //
36         //    z   T3                        T1 T2 T3 T4     //
37         //   / \                                            //
38         // T1   T2                                          //
39         //////////////////////////////////////////////////////
40         if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
41             return rightRotate(node);
42         //////////////////////////////////////////////////////////////////////////////////////////
43         //  LR  T1<X<T2< Z <T3<Y<T4                                                             //
44         //         y                                y                              z            //
45         //        / \                              / \                           /   \          //
46         //       x  t4    向左旋轉(x)             z   T4      向右旋轉(y)       x     y         //
47         //      / \     --------------->         / \        --------------->   / \   / \        //
48         //     T1  z                            x   T3                        T1  T2 T3 T4      //
49         //        / \                          / \                                              //
50         //       T2  T3                      T1   T2                                            //
51         //////////////////////////////////////////////////////////////////////////////////////////
52         if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
53             node.left = leftRotate(node.left);
54             return rightRotate(node);
55         }
56         //////////////////////////////////////////////////
57         // RR: T1<Y<T2< X <T3<Z<T4                      //
58         //    y                              x          //
59         //  /  \                           /   \        //
60         // T1   x      向左旋轉 (y)        y     z       //
61         //     / \   - - - - - - - ->    / \   / \      //
62         //   T2   z                     T1 T2 T3 T4     //
63         //       / \                                    //
64         //      T3 T4                                   //
65         //////////////////////////////////////////////////
66         if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
67             return leftRotate(node);
68 
69         //////////////////////////////////////////////////////////////////////////////////////////
70         // RL: T1<Y<T2< Z <T3<X<T4                                                              //
71         //      y                           y                                       z           //
72         //     / \                         / \                                    /   \         //
73         //    T1  x       向右旋轉(x)     T1  z         向左旋轉(y)              y     x        //
74         //       / \    - - - - - - ->       / \      - - - - - - - - ->        / \   / \       //
75         //      z  T4                       T2  x                              T1 T2 T3 T4      //
76         //     / \                             / \                                              //
77         //    T2  T3                          T3  T4                                            //
78         //////////////////////////////////////////////////////////////////////////////////////////
79         if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
80             node.right = rightRotate(node.right);
81             return leftRotate(node);
82         }
83 
84         return node;
85     }

 

  看上面程式碼,輔之旋轉示意圖和平衡因子,相信大家能通過自己的分析,根據當前節點和左右孩子的平衡因子能判斷出來是LL,LR,RR,或者RL的情況。

 

4.3、左旋和右旋

  至於左右旋轉,我們前文給出了程式碼,但當我們對節點進行旋轉以後,我們需要重新維護一下各個節點的高度值。所以經過完善的左右旋轉的程式碼如下:

 1     /**
 2      * 對節點y進行向右旋轉操作,返回旋轉後新的根節點x
 3      * @param y
 4      * @return
 5      */
 6     ///////////////////////////////////////////////////
 7     // LL T1<Z<T2< X <T3<Y<T4                        //
 8     //        y                              x       //
 9     //       / \                           /   \     //
10     //      x   T4     向右旋轉 (y)        z     y    //
11     //     / \       - - - - - - - ->    / \   / \   //
12     //    z   T3                        T1 T2 T3 T4  //
13     //   / \                                         //
14     // T1   T2                                       //
15     ///////////////////////////////////////////////////
16     private Node rightRotate(Node y) {
17         Node x = y.left;
18         Node T3 = x.right;
19 
20         // 向右旋轉過程
21         x.right = y;
22         y.left = T3;
23 
24         // 更新height
25         y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
26         x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
27 
28         return x;
29     }
30 
31     /**
32      * 對節點y進行向左旋轉操作,返回旋轉後新的根節點x
33      * @param y
34      * @return
35      */
36     ////////////////////////////////////////////////
37     // RR T1<Y<T2< X <T3<Z<T4                     //
38     //    y                             x         //
39     //  /  \                          /   \       //
40     // T1   x      向左旋轉 (y)       y     z      //
41     //     / \   - - - - - - - ->   / \   / \     //
42     //    T2  z                    T1 T2 T3 T4    //
43     //       / \                                  //
44     //      T3 T4                                 //
45     ////////////////////////////////////////////////
46     private Node leftRotate(Node y) {
47         Node x = y.right;
48         Node T2 = x.left;
49 
50         // 向左旋轉過程
51         x.left = y;
52         y.right = T2;
53 
54         // 更新height
55         y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
56         x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
57 
58         return x;
59     }

  

4.4、刪

  對於刪除,稍微複雜一點,但是基本思路和增加是一樣的。但是關於我覺得還是有必要給大家惡補一下二分搜尋樹的刪除的思路,當我們刪除一個節點的時候,待刪除節點左右子樹有一個為空,我們只需要將其不為空的子樹的根節點提到待刪除元素的位置即可。如果其左右子樹都不為空,則將其右子樹最小的元素提到待刪除節點處。詳細的討論請參閱《二分搜尋樹》,在二分搜尋樹刪除操作的基礎上,我們只需要輔之再平衡操作即可。

  1     /**
  2      * 從二分搜尋樹中刪除鍵為key的節點
  3      * @param key
  4      * @return
  5      */
  6     public V remove(K key){
  7 
  8         Node node = getNode(root, key);
  9         if(node != null){
 10             root = remove(root, key);
 11             return node.value;
 12         }
 13         return null;
 14     }
 15 
 16     /**
 17      * 刪除指定的節點
 18      * @param node
 19      * @param key
 20      * @return
 21      */
 22     private Node remove(Node node, K key){
 23 
 24         if( node == null )
 25             return null;
 26 
 27         Node retNode;
 28         if( key.compareTo(node.key) < 0 ){
 29             node.left = remove(node.left , key);
 30             // return node;
 31             retNode = node;
 32         }
 33         else if(key.compareTo(node.key) > 0 ){
 34             node.right = remove(node.right, key);
 35             // return node;
 36             retNode = node;
 37         }
 38         else{   // key.compareTo(node.key) == 0 找到待刪除的節點 node
 39 
 40             // 待刪除節點左子樹為空,直接將右孩子替代當前節點
 41             if(node.left == null){
 42                 Node rightNode = node.right;
 43                 node.right = null;
 44                 size --;
 45                 // return rightNode;
 46                 retNode = rightNode;
 47             }
 48 
 49             // 待刪除節點右子樹為空,直接將左孩子替代當前節點
 50             else if(node.right == null){
 51                 Node leftNode = node.left;
 52                 node.left = null;
 53                 size --;
 54                 // return leftNode;
 55                 retNode = leftNode;
 56             }
 57 
 58             // 待刪除節點左右子樹均不為空的情況
 59             else{
 60                 // 待刪除節點左右子樹均不為空
 61                 // 找到右子樹最小的元素,替代待刪除節點
 62                 Node successor = minimum(node.right);
 63                 //successor.right = removeMin(node.right);
 64                 successor.right = remove(node.right, successor.key);
 65                 successor.left = node.left;
 66 
 67                 node.left = node.right = null;
 68 
 69                 // return successor;
 70                 retNode = successor;
 71             }
 72         }
 73 
 74         if(retNode == null)
 75             return null;
 76 
 77         // 更新height
 78         retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
 79 
 80         // 計算平衡因子
 81         int balanceFactor = getBalanceFactor(retNode);
 82 
 83         // 平衡維護
 84         // LL
 85         if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
 86             return rightRotate(retNode);
 87 
 88         // RR
 89         if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
 90             return leftRotate(retNode);
 91 
 92         // LR
 93         if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
 94             retNode.left = leftRotate(retNode.left);
 95             return rightRotate(retNode);
 96         }
 97 
 98         // RL
 99         if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
100             retNode.right = rightRotate(retNode.right);
101             return leftRotate(retNode);
102         }
103 
104         return retNode;
105     }
106     /**
107      * 返回以node為根的二分搜尋樹的最小值所在的節點
108      * @param node
109      * @return
110      */
111     private Node minimum(Node node){
112         if(node.left == null)
113             return node;
114         return minimum(node.left);
115     }

  

 4.5、其他操作

 1     /**
 2      * 返回以node為根節點的二分搜尋樹中,key所在的節點
 3      * @param node
 4      * @param key
 5      * @return
 6      */
 7     private Node getNode(Node node, K key){
 8 
 9         if(node == null)
10             return null;
11 
12         if(key.equals(node.key))
13             return node;
14         else if(key.compareTo(node.key) < 0)
15             return getNode(node.left, key);
16         else // if(key.compareTo(node.key) > 0)
17             return getNode(node.right, key);
18     }
19 
20     /**
21      * 判斷是否包含 key
22      * @param key
23      * @return
24      */
25     public boolean contains(K key){
26         return getNode(root, key) != null;
27     }
28 
29     /**
30      * 獲取指定 key 的 value
31      * @param key
32      * @return
33      */
34     public V get(K key){
35 
36         Node node = getNode(root, key);
37         return node == null ? null : node.value;
38     }
39 
40     /**
41      * 設定 key 對應元素的值 value
42      * @param key
43      * @param newValue
44      */
45     public void set(K key, V newValue){
46         Node node = getNode(root, key);
47         if(node == null)
48             throw new IllegalArgumentException(key + " doesn't exist!");
49 
50         node.value = newValue;
51     }

 

 

 五、驗證AVL

  前面我們實現了一棵AVL樹,我們如何驗證這到底是不是一棵AVL樹呢?這個問題,我們依然是從其定義來思考,首先,AVL是一棵二分搜尋樹,其次每個節點的平衡因子能滿足在[-1, 1]之間,能滿足這兩點其實加之我們程式碼的邏輯即可判斷其是不是一棵AVL樹了。

  二分搜尋樹有一個延伸出來的性質不知道大家還記不記得,對於二分搜尋樹的中序遍歷,其實是對二分搜尋樹從小到大排序的過程。那我們判斷中序遍歷的結果滿不滿足從小到大即可判定其是不是一棵二分搜尋樹。

 1     /**
 2      * 測試方法 - 判斷該二叉樹是否是一棵二分搜尋樹
 3      * @return
 4      */
 5     public boolean isBST(){
 6 
 7         ArrayList<K> keys = new ArrayList<>();
 8         inOrder(root, keys);
 9         for(int i = 1 ; i < keys.size() ; i ++)
10             if(keys.get(i - 1).compareTo(keys.get(i)) > 0)
11                 return false;
12         return true;
13     }
14 
15     /**
16      * 中序遍歷
17      * @param node
18      * @param keys
19      */
20     private void inOrder(Node node, ArrayList<K> keys){
21 
22         if(node == null)
23             return;
24 
25         inOrder(node.left, keys);
26         keys.add(node.key);
27         inOrder(node.right, keys);
28     }
29 
30     /**
31      * 測試方法 - 判斷該二叉樹是否是一棵平衡二叉樹
32      * @return
33      */
34     public boolean isBalanced(){
35         return isBalanced(root);
36     }
37 
38     /**
39      * 判斷以Node為根的二叉樹是否是一棵平衡二叉樹,遞迴演算法
40      * @param node
41      * @return
42      */
43     private boolean isBalanced(Node node){
44 
45         if(node == null)
46             return true;
47 
48         int balanceFactor = getBalanceFactor(node);
49         if(Math.abs(balanceFactor) > 1)
50             return false;
51         return isBalanced(node.left) && isBalanced(node.right);
52     }

 

 

  我們寫如下測試程式碼:

public class Main {
    public static void main(String[] args) {
        AVL<Integer, Integer> avl = new AVL<>();
        for (int i=0; i< 10; i++){
            avl.add(i, i);
        }
        System.out.println(avl.isBST());
        System.out.println(avl.isBalanced());
        avl.remove(5);
        System.out.println(avl.isBST());
        System.out.println(avl.isBalanced());
    }
}
true
true
true
true

 

 

   到此,AVL所有的內容我們已經介紹完了。哎呦,凌晨三點了,心疼自己一秒鐘。我愛我的國。

 

 

 

 

  參考文獻:

  《玩轉資料結構-從入門到進階-劉宇波》

  《資料結構與演算法分析-Java語言描述》

 

 

  如有錯誤的地方還請留言指正。

  原創不易,轉載請註明原文地址:https://www.cnblogs.com/hello-shf/p/11352071.html 

&n