AVL樹
定義
- AVL樹本質上還是一顆二叉搜索樹,其特性是對於任意一個節點,左子樹和右子樹的高度差不能超過1,所以AVL樹也稱爲平衡二叉樹
- 平衡二叉樹的高度和節點數量之間的關係也是O(logN)
- 平衡因子:
左右子樹的高度差
爲什麼需要AVL樹
當我們順序將一組從小到大的數據(1,2,3,4,5)插入到一顆空的二叉搜索樹,那麼你可以發現這個時候二叉搜索樹已經完全退化成爲了一個鏈表,他的各操作的時間複雜度也將從O(logN)變成爲線性的O(N)。
AVL樹的自平衡機制
什麼時候會破壞平衡
當插入一個節點後,會更新新節點對應的父親節點以及祖先節點的平衡因子,導致向上的節點的左右子樹高度差超過1。所以當插入一個節點後,我們應該沿着新節點向上維護AVL樹的平衡性。
需要維護平衡的四種情況
右旋轉
以下情況中,當插入節點2到樹中時候,根據二叉搜索樹的性質,會將節點2插入到節點6的左邊,從而打破本來的平衡性,使得節點9的平衡因子大於1.這個時候我們可以通過右旋轉
這種操作來維護節點9的平衡性。首先將節點6的右子樹T2和節點6分離,然後將節點9下沉,掛載到節點6的右子樹上,最後將節點6原來的右子樹T2掛載到節點9的左子樹上,此時節點9達到平衡,並且依然保持二叉搜索樹的基本性質。
代碼實現如下:
// 對節點y進行向右旋轉操作,返回旋轉後新的根節點x
// y x
// / \ / \
// x T4 向右旋轉 (y) z y
// / \ - - - - - - - -> / \ / \
// z T3 T1 T2 T3 T4
// / \
// T1 T2
/**
* 右旋轉
* @param node
* @return
*/
private Node rightRotate(Node y) {
// 使X和T3脫離
Node x = y.left;
Node T3 = x.right;
// 旋轉過程
x.right = y;
y.left = T3;
// 更新高度
y.height = Math.max(y.left.height, y.right.height) + 1;
x.height = Math.max(x.left.height, x.right.height) + 1;
return x;
}
左旋轉
以下情況中,當插入節點11到樹中時候,根據二叉搜索樹的性質,會將節點11插入到節點10的右邊,從而打破本來的平衡性,使得節點9的平衡因子大於1。這個時候我們可以通過左旋轉
這種操作來維護節點9的平衡性。首先將節點10的左子樹T2和節點10分離,然後將節點9下沉,掛載到節點10的左子樹上,最後將節點10原來的左子樹T2掛載到節點9的右子樹上,此時節點9達到平衡,並且依然保持二叉搜索樹的基本性質。
代碼實現如下
// 對節點y進行向左旋轉操作,返回旋轉後新的根節點x
// y x
// / \ / \
// T1 x 向左旋轉 (y) y z
// / \ - - - - - - - -> / \ / \
// T2 z T1 T2 T3 T4
// / \
// T3 T4
/**
* 左旋轉
* @param y
* @return
*/
private Node leftRotate(Node y) {
// 使x和T2脫離
Node x = y.right;
Node T2 = x.left;
// 左旋轉過程
x.left = y;
y.right = T2;
// 更新高度
y.height = Math.max(y.left.height, y.right.height) + 1;
x.height = Math.max(x.left.height, x.right.height) + 1;
return x;
}
RL
除過上面提到的情況,我們還有可能發生下面這種情況。當我們在樹中插入節點10的時候,按照二叉搜索樹的規則,10會被插入到節點11的左邊,這時候也會打破原來的平衡,使得節點9的平衡因子大於1。這個時候我們的解決方案是先對節點10做一次右旋轉,這個時候變成我們上面討論過的第二種情況,然後再做一次左旋轉,即可達到平衡。
LR
當然我們新插入的節點也可能這樣分佈,如下圖所示。當我們在樹中插入節點10的時候,發生下面這種情況,和上面的RL正好是對稱的,因此我們只需要根據上面做對稱的操作,即先對節點10進行一次左旋轉,變成上面討論的第一種情況,在進行一次右旋轉,以11爲根的樹就達到平衡。
AVL樹的基本API
插入
有了以上的討論基礎,下面我們給出AVL樹的插入操作代碼。即在二叉搜索樹的插入邏輯上再加上上述四種情況下平衡的維護邏輯即可。
/**
* 向以node爲根的樹中添加元素
* @param node
* @param key
* @param value
* @return 插入元素後新的樹結構
*/
private Node add(Node node, K key, V value) {
if (null == node) {
size ++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
// 維護每一個節點的height
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
// 計算平衡因子
int balanceFactor = getBalanceFactor(node);
// 平衡維護
// LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
return rightRotate(node);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return leftRotate(node);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
更新和查詢
同二叉搜索樹的。不多討論
/**
* 返回以node爲跟的書中key所在的節點
* @param key
* @return
*/
private Node getNode(Node node, K key) {
if (null == node) {
return null;
}
if (key.equals(node.key)) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else {
return getNode(node.right, key);
}
}
public V get(K key) {
Node node = getNode(root, key);
return null == node ? null : node.value;
}
public void set(K key, V value) {
Node node = getNode(root, key);
if (null == node) {
throw new IllegalArgumentException(key + "does not exists");
}
node.value = value;
}
刪除
刪除操作也和二叉搜索樹基本一樣,只是加上平衡的維護邏輯。關於二叉搜索樹的刪除操作,這裏不再贅述,參考二叉搜索樹的筆記。二叉搜索樹
private Node remove(Node node, K key){
if(null == node) {
return null;
}
Node retNode;
if( key.compareTo(node.key) < 0 ) {
node.left = remove(node.left , key);
retNode = node;
} else if(key.compareTo(node.key) > 0 ) {
node.right = remove(node.right, key);
retNode = node;
} else { // key.compareTo(node.key) == 0
// 待刪除節點左子樹爲空的情況
if(null == node.left) {
Node rightNode = node.right;
node.right = null;
size --;
retNode = rightNode;
// 待刪除節點右子樹爲空的情況
} else if(null == node.right) {
Node leftNode = node.left;
node.left = null;
size --;
retNode = leftNode;
// 待刪除節點左右子樹爲空的情況
} else {
// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
// 用這個節點頂替待刪除節點的位置
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
}
if(null != retNode) {
// 更新height
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
// 計算平衡因子
int balanceFactor = getBalanceFactor(retNode);
// 平衡維護
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) {
return rightRotate(retNode);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) {
return leftRotate(retNode);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
}
return retNode;
}
注:主要參考慕課網的課程玩轉算法系列--數據結構精講
。作爲學習筆記總結。