資料結構 - AVL 樹

程式設計師翔仔發表於2022-04-18

簡介

基本概念

AVL 樹是最早被發明的自平衡的二叉查詢樹,在 AVL 樹中,任意結點的兩個子樹的高度最大差別為 1,所以它也被稱為高度平衡樹,其本質仍然是一顆二叉查詢樹。

結合二叉查詢樹,AVL 樹具有以下特性:

  • 若任意結點的左子樹不為空,則左子樹上所有結點的值均小於它的根結點的值
  • 若任意結點的右子樹不為空,則右子樹上所有結點的值均大於或等於它的根結點的值
  • 任意結點的左、右子樹也分別為二叉查詢樹
  • 任意結點的子樹的高度差都小於等於 1

上述的前三項都是二叉查詢樹的特性,第四個是 AVL 樹自平衡的特性。

實現原理

為了保證二叉樹的平衡,AVL 樹引入了監督機制,就是在樹的某一部分的不平衡度超過一個閾值後觸發相應的平衡操作,保證樹的平衡度在可以接受的範圍內。

既然引入了監督機制,則必然需要一個監督指標,以此來判斷是否需要進行平衡操作。這個監督指標被稱為平衡因子(Balance Factor)。定義如下:

某個結點的右子樹的高度減去左子樹的高度得到的差值。

基於平衡因子,就可以這樣定義 AVL 樹:

所有結點的平衡因子的絕對值都不超過 1 的二叉查詢樹。

為了計算平衡因子,自然需要在結點中引入高度這一屬性。結點的高度為以下定義:

左右子樹的高度的最大值。

class AVLNode {
    AVLNode left;       // 左子樹
    AVLNode right;      // 右子樹
    int height;         // 當前結點的高度
    int value;          // 當前結點的值
}

自平衡

自平衡是指在對平衡二叉樹執行插入或刪除結點操作後,可能會導致樹中某個結點的平衡因子絕對值超過 1,即平衡二叉樹變得“不平衡”,為了恢復該結點左右子樹的平衡,此時需要對結點執行旋轉操作。

二叉樹的平衡化有兩大基礎操作: 左旋和右旋。左旋,即是逆時針旋轉;右旋,即是順時針旋轉。

這兩種操作都是從失去平衡的最小子樹根結點開始的(即離插入結點最近且平衡因子超過 1 的祖結點)。

左旋

AVL 樹左旋

所謂左旋操作,就是把上圖中的 B 結點和 A 結點進行所謂“父子交換”。在僅有這三個結點時候,是十分簡單的。但是當 B 結點處存在左孩子時,事情就變得有點複雜了。

通常的操作是:結點 B 拋棄左孩子,將之與旋轉後的結點 A 相連,成為結點 A 的右孩子。

右旋

AVL 樹右旋

所謂右旋操作,就是把上圖中的 B 結點和 C 結點進行所謂“父子交換”。在僅有這三個結點時候,也是是十分簡單的。但是當 B 結點處存在右孩子時,事情就變得有點複雜了。

這時通常的操作是:結點 B 拋棄右孩子,將之和旋轉後的結點 C 相連,成為結點 C 的左孩子。

單次旋轉 - LL

AVL 樹 LL 原型

LL 型又被稱為“左左”,從上圖中可以看得出,A 結點的平衡因子絕對值達到了 2,需要進行修復才能重新成為一棵平衡二叉樹。F 結點為新插入的結點,優先會經過 A 結點的左孩子 B 結點,最終落到 B 結點的左子樹上,這即是“左左”的來由。

可以使用平衡因子來定義 LL 情況:A 結點的平衡因子為 -2,左孩子 B 結點的平衡因子為 -1。

這時候僅需要對 A 結點做一次右旋的操作即可達到平衡狀態:

AVL 樹 LL 右旋結果

單次旋轉 - RR

RR 型又被稱為“右右”,與上面的 LL 型 具有對稱性,展示的情況如下:

AVL 樹 RR 原型

也可以使用平衡因子來定義:A 結點的平衡因子為 2,右孩子 C 結點的平衡因子為 1。

這裡則是僅需要對 A 結點做一次左旋的操作即可達到平衡狀態:

AVL 樹 RR 左旋結果

雙次旋轉 - LR

使用平衡因子定義 LR 型為:A 結點的平衡因子為 -2,左孩子 B 結點的平衡因子為 1。

下面有一個例子:

AVL 樹 LR 原型

第一步:對 A 結點的左子結點 —— B 結點執行左旋操作,得到一個 LL 型的結構:

AVL 樹 LR 左旋結果

第二步:對 A 結點執行右旋操作:

AVL 樹 LR 右旋結果

雙次旋轉 - RL

RL 型和上面的 LR 型對稱,A 結點的平衡因子為 2,右孩子 C 結點的平衡因子為 -1。

AVL 樹 RL 原型

第一步:對 A 結點的右子結點 —— C 結點執行右旋操作,得到一個 RR 型的結構:

AVL 樹 RL 右旋結果

第二步:對 A 結點執行左旋操作:

AVL 樹 RL 左旋結果

相關文章