平衡二叉樹
定義
-
動機:二叉查詢樹的操作實踐複雜度由樹高度決定,所以希望控制樹高,左右子樹儘可能平衡。
-
平衡二叉樹(AVL樹):稱一棵二叉查詢樹為高度平衡樹,當且僅當或由單一外結點組成,或由兩個子樹形 Ta 和 Tb 組成,並且滿足:
- |h(Ta) - h(Tb)| <= 1,其中 h(T) 表示樹 T 的高度
- Ta 和 Tb 都是高度平衡樹
即:每個結點的左子樹和右子樹的高度最多差 1 的 二叉查詢樹。
-
設 T 為高度平衡樹中結點 q 的平衡係數為 q 的右子樹高度減去左子樹高度
-
高度平衡樹所以結點的平衡係數只可能為:-1, 0, 1
結點結構
1️⃣ key
:關鍵字的值
2️⃣ value
:關鍵字的儲存資訊
3️⃣ height
:樹的高度(只有一個結點的樹的高度為 1
)
4️⃣ left
:左子樹根結點的的引用
5️⃣ right
:右子樹根結點的引用
class AVLNode<K extends Comparable<K>, V> {
public K key;
public V value;
public int height;
public AVLNode<K, V> left;
public AVLNode<K, V> right;
public AVLNode(K key, V value, int height) {
this.key = key;
this.value = value;
this.height = height;
}
}
查詢演算法
同二叉查詢樹的查詢演算法:【資料結構與演算法】手撕二叉查詢樹
插入演算法
AVL 樹是一種二叉查詢樹,故可以使用二叉查詢樹的插入方法插入結點,但插入一個新結點時,有可能破壞 AVL 樹的平衡性。
如果發生這種情況,就需要在插入結點後對平衡樹進行調整,恢復平衡的性質。實現這種調整的操作稱為“旋轉”。
在插入一個新結點 X 後,應調整失去平衡的最小子樹,即從插入點到根的路徑向上找第一個不平衡結點 A。
平衡因子:該結點的左子樹高度和右子樹高度的差值。如果差值的絕對值小於等於 1
,則說明該結點平衡,如果差值的絕對值為 2
(不會出現其他情況),則說明該結點不平衡,需要做平衡處理。
造成結點 A 不平衡的的原因以及調整方式有以下幾種情況。
LL 型
A 結點的平衡因子為 2
,說明該結點是最小不平衡結點,需要對 A 結點進行調整。問題發生在 A 結點左子結點的左子結點,所以為 LL 型。
扁擔原理:右旋
-
將 A 的左孩子 B 提升為新的根結點;
-
將原來的根結點 A 降為 B 的右孩子;
-
各子樹按大小關係連線(BL 和 AR 不變,BR 調整為 A 的左子樹)。
-
高度調整:由於調整後 B 的高度依賴於 A 的高度,所以先更新 A 的高度,再更新 B 的高度。
private AVLNode<K, V> rightRotate(AVLNode<K, V> a) {
AVLNode<K, V> b = a.left;
a.left = b.right;
b.right = a;
a.height = Math.max(getHeight(a.left), getHeight(a.right)) + 1;
b.height = Math.max(getHeight(b.left), getHeight(b.left)) + 1;
return b;
}
RR 型
A 結點的平衡因子為 2
,說明該結點是最小不平衡結點,需要對 A 結點進行調整。問題發生在 A 結點右子結點的右子結點,所以為 RR 型。
扁擔原理:左旋
-
將 A 的右孩子 B 提升為新的根結點;
-
將原來的根結點 A 降為 B 的左孩子;
-
各子樹按大小關係連線(AL 和 BR 不變,BL 調整為 A 的右子樹)。
-
高度調整:由於調整後 B 的高度依賴於 A 的高度,所以先更新 A 的高度,再更新 B 的高度。
private AVLNode<K, V> leftRotate(AVLNode<K, V> a) {
AVLNode<K, V> b = a.right;
a.right = b.left;
b.left = a;
a.height = Math.max(getHeight(a.left), getHeight(a.right)) + 1;
b.height = Math.max(getHeight(b.left), getHeight(b.left)) + 1;
return b;
}
LR 型
A 結點的平衡因子為 2
,說明該結點是最小不平衡結點,需要對 A 結點進行調整。問題發生在 A 結點左子結點的右子結點,所以為 LR 型。
-
從旋轉的角度:對 B 左旋,然後對 A 右旋
-
將 B 的左孩子 C 提升為新的根結點;
-
將原來的根結點 A 降為 C 的右孩子;
-
各子樹按大小關係連線(BL 和 AR 不變,CL 和 CR 分別調整為 B 的右子樹和 A 的左子樹)。
private AVLNode<K, V> leftRightRotate(AVLNode<K, V> a) {
a.left = leftRotate(a.left); // 對 B 左旋
return rightRotate(a); // 對 A 右旋
}
RL 型
A 結點的平衡因子為 2
,說明該結點是最小不平衡結點,需要對 A 結點進行調整。問題發生在 A 結點右子結點的左子結點,所以為 RL 型。
-
從旋轉的角度:對 B 右旋,然後對 A 左旋
-
將 B 的左孩子 C 提升為新的根結點;
-
將原來的根結點 A 降為 C 的左孩子;
-
各子樹按大小關係連線(AL 和 BR 不變,CL 和 CR 分別調整為 A 的右子樹和 B 的左子樹)。
private AVLNode<K, V> rightLeftRotate(AVLNode<K, V> a) {
a.right = rightRotate(a.right);
return leftRotate(a);
}
插入方法
-
根結點預設高度為
1
-
某結點的左右子樹高度差的絕對值為
2
,則需要進行平衡處理-
左子樹高
-
key
小於root.left.key
:LL型,進行右旋
-
key
大於root.left.key
:LR型,進行左右旋
-
-
右子樹高
-
key
大於root.right.key
:RR型,進行左旋
-
key
小於root.right.key
:RR型,進行右左旋
-
-
public void insert(K key, V value) {
root = insert(root, key, value);
}
private AVLNode<K, V> insert(AVLNode<K, V> t, K key, V value) {
if (t == null) {
return new AVLNode<>(key, value, 1);
} else if (key.compareTo(t.key) < 0) {
t.left = insert(t.left, key, value);
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
// 平衡因子判斷
if (getHeight(t.left) - getHeight(t.right) == 2) {
if (key.compareTo(root.left.key) < 0) // 左左:右旋
t = rightRotate(t);
else // 左右:先左旋,再右旋
t = leftRightRotate(t);
}
} else if (key.compareTo(t.key) > 0) {
t.right = insert(t.right, key, value);
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
// 平衡因子判斷
if (getHeight(t.left) - getHeight(t.right) == -2) {
if (key.compareTo(root.right.key) > 0) // 右右:左旋
t = leftRotate(t);
else // 右左:先右旋,再左旋
t = rightLeftRotate(t);
}
} else {
t.value = value;
}
return t;
}
刪除演算法
概述
-
可採用二叉查詢樹的刪除演算法進行刪除。
【資料結構與演算法】手撕二叉查詢樹 -
刪除某結點 X 後,沿從 X 到根節點的路徑上考察沿途結點的平衡係數,若第一個不平衡點為 A,平衡以 A 為根的子樹。
-
平衡後,可能使子樹 A 高度變小。這樣可能導致 A 的父節點不滿足平衡性。
-
所以要繼續向上考察結點的平衡性,最遠可能至根結點,即最多需要做
O(logn)
次旋轉。 -
對比“插入”操作:平衡 A 後,子樹高度不變,A 子樹以外的結點不受影響,即插入最多涉及
O(1)
次旋轉。
例項分析
? 下面舉個刪除的例子:
刪除以下平衡二叉樹中的 16 結點
1️⃣ 16 為葉子,將其刪除即可,如下圖。
2️⃣ 指標 g 指向實際被刪除節點 16 之父 25,檢查是否失衡,25 節點失衡,用 g 、u 、v 記錄失衡三代節點(從失衡節點沿著高度大的子樹向下找三代),判斷為 RL 型,進行 RL 旋轉調整平衡,如下圖所示。
3️⃣ 繼續向上檢查,指標 g 指向 g 的雙親 69,檢查是否失衡,69 節點失衡,用 g 、u 、v 記錄失衡三代節點,判斷為 RR 型,進行 RR 旋轉調整平衡,如下圖所示。
程式碼
程式碼描述:
-
若當前結點為空, 則返回該節點
-
若關鍵值小於當前結點的關鍵值,則遞迴處理該結點的左子樹
-
若關鍵值大於當前結點的關鍵值,則遞迴處理該結點的右子樹
-
若關鍵值等於當前結點的關鍵值
-
若當前結點的左子樹為空,則返回該結點的右子樹根節點
-
若當前結點的右子樹為空,則返回該結點的左子樹根節點
-
若當前結點左右子樹都不為空,則找到該結點的中序前驅結點(該結點左子樹的最右結點)或中序後繼結點(該結點右子樹的最左結點),將其值賦予該結點,然後遞迴刪除中序前驅或後繼結點。
-
-
更新結點高度
-
若該結點左子樹高度更高,且處於不平衡狀態
-
若為 LL 型,進行右旋
-
若為 LR 型,先左旋,再右旋
-
-
若該結點右子樹高度更高,且處於不平衡狀態
-
若為 RL 型,先右旋,再左旋
-
若為 RR 型,進行左旋
-
-
返回該結點
public void remove(K key) {
this.root = delete(root, key);
}
public AVLNode<K, V> delete(AVLNode<K, V> t, K key) {
if (t == null) return t;
if (key.compareTo(t.key) < 0) {
t.left = delete(t.left, key);
}
else if (key.compareTo(t.key) > 0) {
t.right = delete(t.right, key);
}
else {
if(t.left == null) return t.right;
else if(t.right == null) return t.left;
else { // t.left != null && t.right != null
AVLNode<K, V> pre = t.left;
while (pre.right != null) {
pre = pre.right;
}
t.key = pre.key;
t.value = pre.value;
t.left = delete(t.left, t.key);
}
}
if (t == null) return t;
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
if(getHeight(t.left) - getHeight(t.right) >= 2) {
if(getHeight(t.left.left) > getHeight(t.left.right)) {
return rightRotate(t);
} else {
return leftRightRotate(t);
}
}
else if(getHeight(t.left) - getHeight(t.right) <= -2) {
if(getHeight(t.right.left) > getHeight(t.right.right)) {
return rightLeftRotate(t);
}
else {
return leftRotate(t);
}
}
return t;
}
完整程式碼
class AVLNode<K extends Comparable<K>, V> {
public K key;
public V value;
public int height;
public AVLNode<K, V> left;
public AVLNode<K, V> right;
public AVLNode(K key, V value, int height) {
this.key = key;
this.value = value;
this.height = height;
}
}
class AVLTree<K extends Comparable<K>, V> {
public AVLNode<K, V> root;
public int getHeight(AVLNode<K, V> t) {
return t == null ? 0 : t.height;
}
public void insert(K key, V value) {
root = insert(root, key, value);
}
public void remove(K key) {
this.root = delete(root, key);
}
public AVLNode<K, V> delete(AVLNode<K, V> t, K key) {
if (t == null) return t;
if (key.compareTo(t.key) < 0) {
t.left = delete(t.left, key);
}
else if (key.compareTo(t.key) > 0) {
t.right = delete(t.right, key);
}
else {
if(t.left == null) return t.right;
else if(t.right == null) return t.left;
else { // t.left != null && t.right != null
AVLNode<K, V> pre = t.left;
while (pre.right != null) {
pre = pre.right;
}
t.key = pre.key;
t.value = pre.value;
t.left = delete(t.left, t.key);
}
}
if (t == null) return t;
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
if(getHeight(t.left) - getHeight(t.right) >= 2) {
if(getHeight(t.left.left) > getHeight(t.left.right)) {
return rightRotate(t);
} else {
return leftRightRotate(t);
}
}
else if(getHeight(t.left) - getHeight(t.right) <= -2) {
if(getHeight(t.right.left) > getHeight(t.right.right)) {
return rightLeftRotate(t);
}
else {
return leftRotate(t);
}
}
return t;
}
private AVLNode<K, V> insert(AVLNode<K, V> t, K key, V value) {
if (t == null) {
return new AVLNode<>(key, value, 1);
}
if (key.compareTo(t.key) < 0) {
t.left = insert(t.left, key, value);
// 平衡因子判斷
if (getHeight(t.left) - getHeight(t.right) == 2) {
if (key.compareTo(t.left.key) < 0) // 左左:右旋
t = rightRotate(t);
else // 左右:先左旋,再右旋
t = leftRightRotate(t);
}
} else if (key.compareTo(t.key) > 0) {
t.right = insert(t.right, key, value);
// 平衡因子判斷
if (getHeight(t.left) - getHeight(t.right) == -2) {
if (key.compareTo(t.right.key) > 0) // 右右:左旋
t = leftRotate(t);
else // 右左:先右旋,再左旋
t = rightLeftRotate(t);
}
} else {
t.value = value;
}
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
return t;
}
private AVLNode<K, V> rightLeftRotate(AVLNode<K, V> a) {
a.right = rightRotate(a.right);
return leftRotate(a);
}
private AVLNode<K, V> leftRightRotate(AVLNode<K, V> a) {
a.left = leftRotate(a.left);
return rightRotate(a);
}
private AVLNode<K, V> leftRotate(AVLNode<K, V> a) {
AVLNode<K, V> b = a.right;
a.right = b.left;
b.left = a;
a.height = Math.max(getHeight(a.left), getHeight(a.right)) + 1;
b.height = Math.max(getHeight(b.left), getHeight(b.right)) + 1;
return b;
}
private AVLNode<K, V> rightRotate(AVLNode<K, V> a) {
AVLNode<K, V> b = a.left;
a.left = b.right;
b.right = a;
a.height = Math.max(getHeight(a.left), getHeight(a.right)) + 1;
b.height = Math.max(getHeight(b.left), getHeight(b.right)) + 1;
return b;
}
private void inorder(AVLNode<K, V> root) {
if (root != null) {
inorder(root.left);
System.out.print("(key: " + root.key + " , value: " + root.value + " , height: " + root.height + ") ");
inorder(root.right);
}
}
private void preorder(AVLNode<K, V> root) {
if (root != null) {
System.out.print("(key: " + root.key + " , value: " + root.value + " , height: " + root.height + ") ");
preorder(root.left);
preorder(root.right);
}
}
private void postorder(AVLNode<K, V> root) {
if (root != null) {
postorder(root.left);
postorder(root.right);
System.out.print("(key: " + root.key + " , value: " + root.value + " , height: " + root.height + ") ");
}
}
public void postorderTraverse() {
System.out.print("後序遍歷:");
postorder(root);
System.out.println();
}
public void preorderTraverse() {
System.out.print("先序遍歷:");
preorder(root);
System.out.println();
}
public void inorderTraverse() {
System.out.print("中序遍歷:");
inorder(root);
System.out.println();
}
}
? 方法測試
public static void main(String[] args) {
AVLTree<Integer, Integer> tree = new AVLTree<>();
tree.insert(69, 1);
tree.insert(25, 1);
tree.insert(80, 1);
tree.insert(16, 1);
tree.insert(56, 1);
tree.insert(75, 1);
tree.insert(90, 1);
tree.insert(30, 1);
tree.insert(78, 1);
tree.insert(85, 1);
tree.insert(98, 1);
tree.insert(82, 1);
tree.remove(16);
tree.preorderTraverse();
tree.inorderTraverse();
tree.postorderTraverse();
}
輸出
先序遍歷:(key: 80 , value: 1 , height: 4) (key: 69 , value: 1 , height: 3) (key: 30 , value: 1 , height: 2) (key: 25 , value: 1 , height: 1) (key: 56 , value: 1 , height: 1) (key: 75 , value: 1 , height: 2) (key: 78 , value: 1 , height: 1) (key: 90 , value: 1 , height: 3) (key: 85 , value: 1 , height: 2) (key: 82 , value: 1 , height: 1) (key: 98 , value: 1 , height: 1)
中序遍歷:(key: 25 , value: 1 , height: 1) (key: 30 , value: 1 , height: 2) (key: 56 , value: 1 , height: 1) (key: 69 , value: 1 , height: 3) (key: 75 , value: 1 , height: 2) (key: 78 , value: 1 , height: 1) (key: 80 , value: 1 , height: 4) (key: 82 , value: 1 , height: 1) (key: 85 , value: 1 , height: 2) (key: 90 , value: 1 , height: 3) (key: 98 , value: 1 , height: 1)
後序遍歷:(key: 25 , value: 1 , height: 1) (key: 56 , value: 1 , height: 1) (key: 30 , value: 1 , height: 2) (key: 78 , value: 1 , height: 1) (key: 75 , value: 1 , height: 2) (key: 69 , value: 1 , height: 3) (key: 82 , value: 1 , height: 1) (key: 85 , value: 1 , height: 2) (key: 98 , value: 1 , height: 1) (key: 90 , value: 1 , height: 3) (key: 80 , value: 1 , height: 4)