個人部落格傳送門
一、須知須會
-
: 二叉樹的 左子樹 - 右子樹 = 高度的內插補點,在平衡樹中可能的值(-1 ,0 ,1)平衡因子
-
:平衡
的絕對值小于 2 (下圖第一張為平衡樹, 第二張為不平衡樹)平衡因子
- 平衡樹且平衡因子==0

- 非平衡樹且平衡因子==-2
-
: 參考維基百科 樹的旋轉樹的旋轉
資料結構(二), AVL平衡二叉樹一、須知須會二、簡介三、基本特征四、算法描述五、完整代碼 資料結構(二), AVL平衡二叉樹一、須知須會二、簡介三、基本特征四、算法描述五、完整代碼
轉軸的移動方向來決定它是左旋還是右旋, 本文中稱轉軸右移為右旋反之則是左旋
-
: (Q為樹的根節點, P為轉軸, 轉軸最終被右移) 右旋時,右旋
轉軸的右孩子 = 樹的根節點;
根節點的左孩子 = 轉軸的右子樹根節點;
樹的根節點 = 轉軸;
-
(P為樹的根節點, Q為轉軸, 轉軸最終被左移) 左旋時,左旋
轉軸的左孩子 = 樹的根節點;
根節點的右孩子 = 轉軸的左子樹根節點;
樹的根節點 = 轉軸;
-
旋轉結束後二叉樹的中序始終不變,中序不變
A<P<B<Q<C
二、簡介
AVL樹(Adelson-Velsky and Landis Tree)得名于它的發明者G. M. Adelson-Velsky和Evgenii Landis,他們在1962年的論文《An algorithm for the organization of information》中公開了這一資料結構。其實AVL就是在BST的基礎上增加了一個平衡(Balance)的屬性, 為的是穩固算法複雜度到 O(logn), BST算法複雜度受到樹結構的影響會遊離于 O(logn)~O(n) 之間, 而加入了平衡屬性之後則會降低到恒定為 O(logn)
三、基本特征
- 首先是 BST 的一種( BST 有的它都有)
- 是平衡二叉樹(根節點的平衡因子絕對值不大于1)
- 左右子樹也是平衡二叉樹
四、算法描述
插入, 删除之後
是否需要平衡
這個樹?
如何平衡
?
高度
為了判斷一個樹是否平衡? 引入了高度這個概念, 記錄從樹的最後一層到目前層數的距離
是以我們的節點(Node)結構定義如下
public static class Node {
private int data;
private int height;
private Node left;
private Node right;
}
旋轉
為了解決樹的平衡問題引入了樹的旋轉
A, B, C, D代表
子節點
,
子樹
或者
null
-
左節點的左節點/子樹導緻的不平衡, 需要左左
右旋
private Node rotateRight(Node root) {
Node t = root.left;
root.left = t.right;
t.right = root;
root.height = Math.max(height(root.left), height(root.right)) + 1;
t.height = Math.max(height(t.left), height(t.right)) + 1;
return t;
}
右右
右節點的右節點/子樹導緻的不平衡, 需要
左旋
private Node rotateLeft(Node root) {
Node t = root.right;
root.right = t.left;
t.left = root;
root.height = Math.max(height(root.left), height(root.right)) + 1;
t.height = Math.max(height(t.left), height(t.right)) + 1;
return t;
}
左右
左節點的右節點/子樹導緻的不平衡, 需要
左旋
,
右旋
private Node rotateLeftRight(Node root) {
root.left = rotateLeft(root.left);
return rotateRight(root);
}
右左
右節點的左節點/子樹導緻的不平衡, 需要
右旋
,
左旋
private Node rotateRightLeft(Node root) {
root.right = rotateRight(root.right);
return rotateLeft(root);
}
查找
和 BST 寫法一緻
- 先查找根節點,
-
, 則找左子樹;< 根
-
, 則找右子樹;> 根
-
, 則找到傳回;= 根
public Node search(int num) {
return doSearch(root, num);
}
private Node doSearch(Node root, int num) {
if (root == null) {
return null;
}
if (root.data == num) {
return root;
} else if (root.data > num) {
return doSearch(root.left, num);
} else if (root.data < num) {
return doSearch(root.right, num);
}
return null;
}
算法時間複雜度 對于 n 個節點的樹
f(n) = 需要查找的次數 = 二叉樹的層數 ~= O(logn)
插入
- 比對根節點, 小于就往左節點比對, 大于就往右節點比對
- 直到需要比對的節點為空, 而這個空就是你需要插入的位置
- 判斷樹是否平衡, 否, 則需要判斷旋轉類型并進行旋轉變換
public void insert(int num) {
root = doInsert(root, num);
}
private Node doInsert(Node parent, int num) {
if (parent == null) {
parent = new Node(num);
} else if (num > parent.data) {
parent.right = doInsert(parent.right, num);
// 判斷樹是否失衡
if (height(parent.right) - height(parent.left) >= 2) {
// '右右'
if (num > parent.right.data) {
parent = rotateLeft(parent);
}
// '右左'
else {
parent = rotateRightLeft(parent);
}
}
} else if (num < parent.data) {
parent.left = doInsert(parent.left, num);
// 判斷樹是否失衡
if (height(parent.left) - height(parent.right) >= 2) {
// '左左'
if (num < parent.left.data) {
parent = rotateRight(parent);
}
// '左右'
else {
parent = rotateLeftRight(parent);
}
}
}
// 重新計算旋轉之後的高度
parent.height = Math.max(height(parent.left), height(parent.right)) + 1;
return parent;
}
算法時間複雜度:
f(n) = 查找插入點的比對次數logn + 一次旋轉(單旋或者雙旋) + 判斷是否平衡 ~= O(logn)
旋轉的時間複雜度 O(1) 最多需要單旋或者雙旋, 另外, 判斷是否平衡的時間複雜度也是 O(1) ( 主要得益于 Node 使用了 height 記錄高度, 典型的空間換時間), 這樣總得算法複雜度還是 比對的次數![]()
資料結構(二), AVL平衡二叉樹一、須知須會二、簡介三、基本特征四、算法描述五、完整代碼
删除
- 先查找到目标節點
- 若: 目标左子樹為空, 則, 用目标右子樹根節點替換目标
- 若: 目标右子樹為空, 則, 用目标左子樹根節點替換目标
- 若: 都不為空, 則, 選取
或者左子樹值最大節點
替換目标, 并, 遞歸删除替換目标的節點右子樹最小節點
- 判斷樹是否平衡, 否, 則需要判斷旋轉類型并進行旋轉變換
public void remove(int num) {
root = doRemove(root, num);
}
private Node doRemove(Node parent, int num) {
if (parent == null) {
return null;
}
if (num > parent.data) {
parent.right = doRemove(parent.right, num);
// 判斷樹是否平衡
if (height(parent.left) - height(parent.right) >= 2) {
Node t = parent.right;
// `右左`
if (t == null || height(t.right) < height(t.left)) {
parent = rotateRightLeft(parent);
}
// `右右`
else {
parent = rotateLeft(parent);
}
}
} else if (num < parent.data) {
parent.left = doRemove(parent.left, num);
// 判斷樹是否平衡
if (height(parent.right) - height(parent.left) >= 2) {
Node t = parent.left;
// `左右`
if (t == null || height(t.left) < height(t.right)) {
parent = rotateLeftRight(parent);
}
// `左左`
else {
parent = rotateRight(parent);
}
}
}
// 找出左子樹最大的值或者右子樹最小的值替換, 這裡選擇前者來實作
else if (parent.left != null && parent.right != null) {
// 找到左子樹最大值替換
parent.data = findMax(parent.left).data;
// 删除左子樹中用于替換的節點
parent.left = doRemove(parent.left, parent.data);
}
// 左子樹為空, 直接用右子樹根節點替換被删除的節點
else if (parent.left == null) {
parent = parent.right;
}
// 右子樹為空, 直接用左子樹根節點替換被删除的節點
else if (parent.right == null) {
parent = parent.left;
}
return parent;
}
private Node findMax(Node node) {
if (node == null) {
return node;
}
while (node.right != null) {
node = node.right;
}
return node;
}
算法複雜度:
f(n) = 需要比對的次數 = 查找到目标比對次數logn + 旋轉次數logn = O(2logn)
最多需要 logn 次旋轉, 來確定平衡, 是以最終的時間複雜度是 O(2logn)
五、完整代碼
就一個AVLTree.java檔案搞定, 裡面還附有main()函數測試功能, 可直接運作github傳送門