一、簡介
本文將通過圖解和代碼詳細講解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