資料結構:AVL樹的平衡調整——LL,LR,RL,RR
AVL樹的全稱是平衡搜尋二叉樹,本質上也是一個二叉搜尋樹(BST),滿足BST樹的所有性質。
但是我們在使用二叉搜尋樹的時候,我們知道通常情況在BST中搜索一個節點的時間複雜度是O(lgn)。
最壞的情況為O(n),這種情況就是出現連續的左子樹/右子樹,如下圖所示:
這種情況其實就已經是鏈式儲存,無法將樹的優勢體現出來。
為了避免這種情況,我們就要保證這個樹隨時都是平衡的。當然要求不能那麼嚴格,不需要保證它完全平衡(每個節點的左右子樹高度差的絕對值都為0),AVL樹就要求的是隻要每個節點的左右子樹高度差的絕對值不超過1,就可以稱作平衡。
我們將“每個節點的左右子樹的高度差的絕對值”叫做平衡因子(BF)。
在AVL樹中,插入一個節點之後都會判斷是否打破了這個平衡。如果打破了,就需要對樹進行調整,讓它仍然滿足平衡的定義,並且不改變中序遍歷的正確性。這步操作就叫做旋轉。即,是否需要旋轉以及具體的旋轉實現都要包含在插入函式裡。
根據插入的位置不同,旋轉的型別也分為4種:LL,LR,RL,RR。
旋轉的幾種情況: 1.插入點位於x的左孩子的左子樹中。 左左LL 右旋。 2.插入點位於x的左孩子的右子樹中。 左右LR 較低的先左旋,轉換為LL問題,再右旋。 3.插入點位於x的右孩子的左子樹中。 右左RL 較低的先右旋,轉化為RR問題。再左旋。 4.插入點威武x的右孩子的右子樹中。 右右RR 左旋。
接下來我們就用幾個簡單的例子,來分析旋轉操作。
先給出AVL樹的定義。
typedef struct AVLTree
{
int height; //當前節點的高度。
int data;
struct AVLTree* lchild;
struct AVLTree* rchild;
}Tree,*pTree
- LL & RR
如圖所示。
原本的狀態是左邊圖所示,中序遍歷為2,3.此時平衡因子分別為1,0.
在插入了一個節點之後(此時我們還沒有寫帶調整的插入函式,可以理解為使用BST樹的插入函式),我們可以看到這就成了我們最不想看見的情況——變成了鏈式儲存。此時,我們就需要進行調整了。
情況:導致失衡的節點(1)是節點node(3)的左子樹的左孩子,即為LL情況。
具體演算法如下:
1.對於節點node(值為3的節點),先取它的左孩子(值為2的節點)作為臨時節點temp;
2.將temp的右孩子作為node的左孩子;
3.再將node作為temp的右孩子;
4.更新height
5.node = temp。此時,temp就成為了原來node一樣的存在。
下圖是調整後的二叉樹:
可以看到,此時還是一個BST樹且中序遍歷的順序也沒有發生改變,還滿足了AVL的要求。
具體程式碼實現:
前置函式:
int GetHeight(pTree tree) //獲取當前節點的高度。
{
if(tree == nullptr) return 0;
return tree->height;
}
bool IsBalanced(pTree tree) //判斷是否平衡。
{
int BF = GetHeight(tree->lchild) - GetHeight(tree->rchild);
return abs(BF) < 2;
}
LL型旋轉函式:
pTree Rotate_LL(pTree tree)
{
pTree temp = tree->lchild;
tree->lchild = temp->rchild;
temp->rchild = tree;
//更新高度,先更新node再更新temp。
tree->height = max(GetHeight(tree->lchild),GetHeight(tree->rchild))+1;
temp->height = max(GetHeight(temp->lchild),GetHeight(temp->rchild))+1;
return temp;
}
RR型由於和LL型是對稱的,所以只需要將LL中的所有左右互換就可以了。
pTree Rotate_RR(pTree tree)
{
pTree temp = tree->rchild;
tree->rchild = temp->lchild;
temp->lchild = tree;
//更新高度,先更新node再更新temp。
tree->height = max(GetHeight(tree->lchild),GetHeight(tree->rchild))+1;
temp->height = max(GetHeight(temp->lchild),GetHeight(temp->rchild))+1;
return temp;
}
- LR & RL
接下來分析LR型:
即,引起失衡的是node節點的左子樹的右孩子。 即如下圖的情況:
在節點值為4的節點插入5,是4的右孩子。此時已經失衡。即為LR問題。
LR具體演算法:
1.先獲取node(值為10的節點)的左孩子節點,記為temp。
2.對temp進行RR。
3.對node進行LL。
程式碼實現:
pTree Rotate_LR(pTree tree)
{
pTree temp = tree->lchild;
tree->lchild = Rotate_RR(temp);
return Rotate_LL(tree);
}
由於RL和LR是對稱的,同樣只需將所有的R換成L即可。此處不再贅述。