平衡二叉樹的插入與刪除
阿新 • • 發佈:2019-01-31
定義
AVL樹是帶有平衡條件的二叉查詢樹。它要求在AVL樹中任何節點的兩個子樹的高度(高度是指節點到一片樹葉的最長路徑的長) 最大差別為1,如下圖所示:
為什麼有AVL樹
大多數BST操作,例如查詢,找最大,最小值,插入,刪除等操作,基本上消耗O(h)時間,h是BST的高度。對於傾斜的二叉樹,這些操作的成本可能會變成O(n)。
如果我們在每次插入和刪除之後確保樹的高度保持O(logn),那麼我們就可以保證所有這些操作的O(logn)的上界。而AVL樹的高度總是O(logn),其中n是樹中節點的數量。
所以AVL樹很好的解決了二叉搜尋樹退化成連結串列的問題。把插入,查詢,刪除的時間複雜度的最好情況和最壞情況都維持在O(logn)
插入操作
當我們執行插入一個節點時,很可能會破壞AVL樹的平衡特性,所以我們需要調整AVL樹的結構,使其重新平衡,而調整的方式稱之為旋轉。
這裡我們針對父節點的位置分為左-左,左-右,右-右,右-左這4類情況分析,而對左-左,右-右情況進行單旋轉,也就是一次旋轉,對左-右,右-左情況進行雙旋轉,兩次旋轉,最終恢復其平衡特性。
1.左-左情況
2.左-右情況
3.右-右情況
4.右-左情況
程式碼實現
package com.pjmike.tree.AVL;
/**
* AVL樹
*
* @author pjmike
* @create 2018-08-09 17:57
*/
public class AVLTree {
private Node root;
/**
* 計算AVL節點的高度的方法
*
* @param node
* @return
*/
private int height(Node node) {
//如果為空,返回height為0
return node == null ? 0 : node.height;
}
/**
* 計算兩個的最大值
*
* @param a
* @param b
* @return
*/
private int max(int a, int b) {
return a > b ? a : b;
}
/**
* 右旋轉
*
* @param y
* @return
*/
Node rightRotate(Node y) {
Node x = y.left;
Node T1 = x.right;
x.right = y;
y.left = T1;
//更新高度
y.height = max(height(y.left), height(y.right)) + 1;
x.height = max(height(x.left), height(x.right)) + 1;
return x;
}
/**
* 左旋轉
*
* @param x
* @return
*/
Node leftRotate(Node x) {
Node y = x.right;
Node T2 = y.left;
y.left = x;
x.right = T2;
//更新高度
x.height = max(height(x.left), height(x.right)) + 1;
y.height = max(height(y.left), height(y.right)) + 1;
return y;
}
/**
* 獲取平衡因子
*
* @param node
* @return
*/
int getBalance(Node node) {
return node == null ? 0 : (height(node.left) - height(node.right));
}
/**
* 插入操作
*
* @param node
* @param val
* @return
*/
Node insert(Node node, int val) {
if (node == null) {
return new Node(val);
}
if (val < node.val) {
node.left = insert(node.left, val);
} else if (val > node.val) {
node.right = insert(node.right, val);
} else {
return node;
}
//更新節點高度
node.height = 1 + max(height(node.left), height(node.right));
//這是插入完畢後的
int balance = getBalance(node);
if (balance > 1 && val < node.left.val) {
//右旋
return rightRotate(node);
}
if (balance < -1 && val > node.right.val) {
//左旋
return leftRotate(node);
}
if (balance > 1 && val > node.left.val) {
//先左旋,再右旋
node.left = leftRotate(node.left);
return rightRotate(node);
}
if (balance < -1 && val < node.right.val) {
//先右旋再左旋
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
public static void main(String[] args) {
AVLTree avlTree = new AVLTree();
//創造AVL樹
avlTree.root = avlTree.insert(avlTree.root, 10);
avlTree.root = avlTree.insert(avlTree.root, 20);
avlTree.root = avlTree.insert(avlTree.root, 30);
avlTree.root = avlTree.insert(avlTree.root, 40);
avlTree.root = avlTree.insert(avlTree.root, 50);
avlTree.root = avlTree.insert(avlTree.root, 23);
avlTree.Preorder(avlTree.root);
}
void Preorder(Node node) {
if (node != null) {
System.out.println(node.val+" ");
Preorder(node.left);
Preorder(node.right);
}
}
}
class Node {
int val;
Node left;
Node right;
/**
* 節點高度,高度是指節點到一片樹葉的最長路徑的長
*/
int height;
public Node(int val) {
this.val = val;
height = 1;
}
}
刪除操作
對於二叉查詢樹,我們都知道它的刪除要分三種情況:
- 刪除的節點為葉子節點
- 刪除的節點左子樹或右子樹有一個為空
- 刪除的節點有兩個子樹
AVL本身是帶有平衡性質的二叉搜尋樹,所以它的刪除策略是與二叉查詢樹相似的,只不過刪除節點後可能會造成樹失去平衡性,所以需要做平衡處理
程式碼實現
Node deleteNode(Node root, int val) {
if (root == null) {
return root;
}
if (val < root.val) {
root.left = deleteNode(root.left, val);
} else if (val > root.val) {
root.right = deleteNode(root.right, val);
} else {
//刪除節點有兩個孩子
if (root.left != null && root.right != null) {
root.val = findMin(root.right);
root.right = deleteNode(root.right, root.val);
} else {
//刪除節點只有一個孩子或者沒有孩子
root = (root.left != null) ? root.left : root.right;
}
}
//以下操作是為了恢復AVL樹的平衡性
if (root == null) {
return root;
}
root.height = max(height(root.left), height(root.right)) + 1;
int balance = getBalance(root);
//左-左情況,這裡使用>=而不是>就是為了保證這些情形下使用的是單旋轉而不是雙旋轉
if (balance > 1 && getBalance(root.left) >= 0) {
return rightRotate(root);
}
//左-右情況
if (balance > 1 && getBalance(root.left) < 0)
{
root.left = leftRotate(root.left);
return rightRotate(root);
}
//右-右情況
if (balance < -1 && getBalance(root.right) <= 0) {
return leftRotate(root);
}
//右-左情況
if (balance < -1 && getBalance(root.right) > 0)
{
root.right = rightRotate(root.right);
return leftRotate(root);
}
return root;
}
private int findMin(Node root) {
if (root.left == null) {
return root.val;
} else {
return findMin(root.left);
}
}
這裡的刪除後的平衡操作實際上與插入後的平衡操作是不一樣的,刪除可能造成樹的一邊比另一邊淺的情況,還可能造成整個二叉樹中有兩個子樹一樣深的情況。
小結
顯然,AVL樹的優勢在於插入,刪除,查詢操作的平均時間複雜度都為O(logn),但是它的缺陷是為了保持AVL樹的平衡性質,動態插入和刪除的代價也是很大的