二叉排序樹很好的平衡了插入與查詢的效率,但不平衡的二叉排序樹效率大打折扣。今天介紹的AVL樹就是一種解決此問題的方案。
定義
平衡二叉樹(Self-Balancing Binary Search Tree 或Height-Balanced Binary Search Tree),是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1 。它是一種高度平衡的二叉排序樹。意思是說,要麼它是一棵空樹,要麼它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1 。我們將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF (Balance Factor),那麼平衡二叉樹上所有結點的平衡因子只可能是-1 、0 和1。
如下圖就不是一棵AVL樹,因為結點18的左子樹高度為2,右子樹高度為0,高度差大於1。

但通過一定的步驟調整之後,可以將其轉為一棵平衡二叉樹,如下圖:

實現原理
平衡二叉樹構建的基本思想就是在構建二叉排序樹的過程中,每當插入一個結點時,先檢查是否因插入而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保持二叉排序樹特性的前提下,調整最小不平衡子樹中各結點之間的連結關係,進行相應的旋轉,使之成為新的平衡子樹。最小不平衡子樹是指距離插入結點最近的,且平衡因子的絕對值大於1 的結點為根的子樹。
下面通過一個例項,瞭解平衡二叉樹的構建過程。
假如我們要將陣列int[] a = {3, 2, 1, 4, 5, 6, 7, 10, 9, 8}
構建成一棵二叉排序樹,如果直接按照二叉排序樹的定義,會得到下面的結果:

這樣的結果對查詢是十分不利的,樹的高度達到了8,而且大多數只有一個孩子。所以我們需要一些操作,將它變成一棵AVL樹。
首先,插入元素3和2時,沒有什麼影響,此時3的平衡因子為1,2的平衡因子為0,結果如下:

現在,要把1插入樹中,這時結果如下所示:

此時3的平衡因子為2了,不再符合平衡二叉樹的規則。此時,整棵樹就是最小不平衡子樹,我們將其右旋:

再插入4,也不會影響平衡,結果如下:

此時,插入元素5,以3為根結點的子樹成為了最小不平衡子樹,如下所示:

現在要對其進行左旋:

現在繼續插入元素6,此時以2為根結點的右子樹為最小不平衡子樹,結果如下:

這時再次需要對其進行左旋,這次旋轉後要將4的左孩子變為2的右孩子,以滿足二叉排序樹的定義,如下所示:

再插入7時,情況和之前有些類似了,結果如下:

左旋後結果如下:

現在,繼續插入10,此時無需調整,結果如下:

下一步,插入元素9,此時結果如下:

按照之前的經驗,這時我們應該進行左旋了,但是左旋之後9將變為10的右孩子,這會不符合二叉排序樹的定義。和之前不同的是,7和10的平衡因子符號相反,這是造成這一結果的原因。這種情況下,要先以10為根節點右旋,再進行左旋,結果如下所示:


最後插入元素8,如下所示:

此時情況和上述類似,6是最小不平衡子樹的根結點,9和6的平衡因子符號相反,所以先以9為根結點右旋:

然後再以6左旋:

可以看到,此樹的高度僅為4,與之前的8相差很多,效能自然也好很多。
平衡二叉樹的刪除操作與插入類似,這裡將不再介紹。大家可以自己思考如何最高效地刪除元素,可以分葉結點、僅有一個子結點和有兩個子結點三種情況考慮,這裡還用到了遞迴的思想。
接下來我們將介紹另一種實現方式,紅黑樹。
下一篇:Java集合原始碼分析之基礎(六):紅黑樹(RB Tree)
本文到此就結束了,如果您喜歡我的文章,可以關注我的微信公眾號: 大大紙飛機
或者掃描下方二維碼直接新增:

您也可以關注我的github:github.com/LtLei/artic…
程式設計之路,道阻且長。唯,路漫漫其修遠兮,吾將上下而求索。