平衡二叉樹圖解以及程式碼實現
平衡二叉樹
平衡二叉搜尋樹(Self-balancing binary search tree
)又被稱為AVL
樹(有別於AVL
演算法),且具有以下性質:
- 它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過
1
; - 左右兩個子樹都是一棵平衡二叉樹。
是為了解決二叉查詢樹查詢節點在最壞的情況下,該二叉樹有可能退化成一個連結串列。
AVL
樹除了滿足二叉查詢樹的性質之外,AVL
樹的特性如下:
AVL
樹的左右子樹的高度差別小於等於1
。AVL
樹的左右子樹也是AVL
樹。
平衡因子:
定義:某結點的左子樹與右子樹的高度(深度)差即為該結點的平衡因子(BF,Balance Factor)。
平衡二叉樹上所有結點的平衡因子只可能是-1
,0
或 1
。如果某一結點的平衡因子絕對值大於1
則說明此樹不是平衡二叉樹。為了方便計算每一結點的平衡因子我們可以為每個節點賦予height
這一屬性,表示此節點的高度。
二叉樹失衡的情況有LL
、RR
、LR
和RL
四種,調整二叉樹為平衡二叉樹的方法有2
種:右旋、左旋。
1、解決失衡
LL
(右旋):
/**
* 右旋轉
*/
private Node rightRotate(Node y){
Node x = y.left;
Node t3 = x.right;
x.right = y;
y.left = t3;
//更新height
y.height = Math.max(getHeight(y.left),getHeight(y.right))+1;
x.height = Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
RR
左旋:
/**
* 左旋轉
*/
private Node leftRotate(Node y){
Node x = y.right;
Node t2 = x.left;
x.left = y;
y.right = t2;
//更新height
y.height = Math.max(getHeight(y.left),getHeight (y.right))+1;
x.height = Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
LR
先左旋後右旋:
RL
先右旋後左旋:
同理LR
的先左旋後右旋。
1、新增節點
// 向二分搜尋樹中新增新的元素(key, value)
public void add(E e){
root = add(root, e);
}
// 向以node為根的二分搜尋樹中插入元素(key, value),遞迴演算法
// 返回插入新節點後二分搜尋樹的根
private Node add(Node node, E e){
if(node == null){
size ++;
return new Node(e);
}
if(e.compareTo(node.e) < 0)
node.left = add(node.left, e);
else if(e.compareTo(node.e) > 0)
node.right = add(node.right, e);
//更新height
node.height = 1+Math.max(getHeight(node.left),getHeight(node.right));
//計算平衡因子
int balanceFactor = getBalanceFactor(node);
if(balanceFactor > 1 && getBalanceFactor(node.left)>0) {
//右旋LL
return rightRotate(node);
}
if(balanceFactor < -1 && getBalanceFactor(node.right)<0) {
//左旋RR
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;
}
2、刪除節點
分為以下3
種情況:
(1)當刪除的節點是葉子節點,則將節點刪除,然後從父節點開始,判斷是否失衡,如果沒有失衡,則再判斷父節點的父節點是否失衡,直到根節點,此時到根節點還發現沒有失衡,則說此時樹是平衡的;如果中間過程發現失衡,則判斷屬於哪種型別的失衡(左左,左右,右左,右右),然後進行調整。
(2)刪除的節點只有左子樹或只有右子樹,這種情況其實就比刪除葉子節點的步驟多一步,就是將節點刪除,然後把僅有一支的左子樹或右子樹替代原有結點的位置,後面的步驟就一樣了,從父節點開始,判斷是否失衡,如果沒有失衡,則再判斷父節點的父節點是否失衡,直到根節點,如果中間過程發現失衡,則根據失衡的型別進行調整。
(3)刪除的節點既有左子樹又有右子樹,這種情況又比上面這種多一步,就是中序遍歷,找到待刪除節點的前驅或者後驅都行,然後與待刪除節點互換位置,然後把待刪除的節點刪掉,後面的步驟也是一樣,判斷是否失衡,然後根據失衡型別進行調整。
public E remove(E e){
Node node = getNode(root, e);
if(node != null){
root = remove(root, e);
return node.e;
}
return null;
}
private Node remove(Node node, E e){
if( node == null )
return null;
Node retNode;
if( e.compareTo(node.e) < 0 ){
node.left = remove(node.left , e);
retNode = node;
}
else if(e.compareTo(node.e) > 0 ){
node.right = remove(node.right, e);
retNode = node;
}
else{ // e.compareTo(node.e) == 0
// 待刪除節點左子樹為空的情況
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
retNode = rightNode;
}
// 待刪除節點右子樹為空的情況
else if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
retNode = leftNode;
}else {
// 待刪除節點左右子樹均不為空的情況
// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
// 用這個節點頂替待刪除節點的位置
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.e);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
}
if(retNode==null)
return null;
//維護平衡
//更新height
retNode.height = 1+Math.max(getHeight(retNode.left),getHeight(retNode.right));
//計算平衡因子
int balanceFactor = getBalanceFactor(retNode);
if(balanceFactor > 1 && getBalanceFactor(retNode.left)>=0) {
//右旋LL
return rightRotate(retNode);
}
if(balanceFactor < -1 && getBalanceFactor(retNode.right)<=0) {
//左旋RR
return leftRotate(retNode);
}
//LR
if(balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){
node.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
//RL
if(balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){
node.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
return retNode;
}
最後總結一下,平衡二叉樹是一棵高度平衡的二叉樹,所以查詢的時間複雜度是 O(logN)
。插入的話上面也說,失衡的情況有4
種,左左,左右,右左,右右,即一旦插入新節點導致失衡需要調整,最多也只要旋轉2次,所以,插入複雜度是 O(1) ,但是平衡二叉樹也不是完美的,也有缺點,從上面刪除處理思路中也可以看到,就是刪除節點時有可能因為失衡,導致需要從刪除節點的父節點開始,不斷的回溯到根節點,如果這棵平衡二叉樹很高的話,那中間就要判斷很多個節點。所以後來也出現了綜合性能比其更好的樹—-紅黑樹。