AVL樹的構造、插入和刪除(C++)
昨天搞了一個晚上,可算是把AVL樹的概念搞明白。而後又參閱了大量的部落格和書籍,終於是自己搞了一套程式碼(期間各種BUG搞得我欲仙欲死= =、)雖然考研可能不要求實現,但還是寫點東西,就算是自己總結一下吧。
AVL樹是一種BST(二叉搜尋樹),但是AVL是需要保持平衡的,即若AVL非空,那麼它上面每一結點的左、右子樹高度之差的絕對值不超過1,即 ,其中hl - hr記做平衡因子bf
定義部分:
#include <iostream> #include <string> #include <cmath> using namespace std; // AVL樹 結點結構體定義 typedef int ElementType; typedef struct TreeNode{ ElementType val; TreeNode* left; TreeNode* right; int bf; // 平衡因子 = 左子樹高度 - 右子樹高度,只可能是-1,0,1 int height; // 樹高 // 建構函式 TreeNode(ElementType x):val(x), left(nullptr), right(nullptr) , bf(0), height(0){} }*AVLTree;
這就要求了AVL樹在插入、刪除時可能會造成某些結點的不平衡,此時就需要對離插入、刪除結點最近的那一個失去平衡的結點進行一些操作,使之重新達到平衡
每一次插入、刪除、旋轉都是需要更新樹高和平衡因子(這裡定義空樹高度為-1)
int getHeight(TreeNode* t) { return ( t==nullptr ? -1:t->height ); } void reHeight_Bf(AVLTree root) { // 按我們學校教材規定,空樹的樹高為 -1 int hl = getHeight(root->left); int hr = getHeight(root->right); // 更新樹高、平衡因子 root->height = max(hl, hr) + 1; root->bf = hl - hr; }
具體的操作是什麼呢? 有四種,分別是LL, RR, LR, RL
注:四種旋轉,因為RL和LR旋轉是基於RR和LL的,因此要按順序定義。另外需要注意的是,旋轉後的結點的高度、平衡因子均可能發生變化,記得更新(昨天被這裡坑了,除錯了半天o(╯□╰)o)
LL平衡旋轉:是由於在結點A的左子樹的左子樹上插入,導致該結點平衡因子hl - hr大於1,失去平衡。此時就需要LL旋轉,具體如下:
取下A結點(即為t),A的左子樹記為B,B結點右子樹BR連結到A的左子樹,B替換A的位置,t連結到B的右子樹
// rotation n. 旋轉,轉動; 輪流,迴圈; void LL_rotation(AVLTree& A) { AVLTree t = A; //儲存A AVLTree B = A->left; //A的左子樹 AVLTree BR = B->right; //A的左子樹 的 右子樹 B->right = t; // B結點右子樹連結原來的A結點 t->left = BR; // A結點左子樹連結原來B結點的右子樹 A = B; // 原來樹中的A結點用B結點替代 // 旋轉後結點位置變化,需要更新樹高 reHeight_Bf(A->right); reHeight_Bf(A); }
RR平衡旋轉:與LL映象相反。
是由於在結點A的右子樹的右子樹上插入,導致該結點平衡因子hl - hr小於-1,失去平衡。此時就需要RR旋轉,具體如下:
取下A結點(即為t),A的右子樹記為B,B結點左子樹BL連結到A的右子樹,B替換A的位置,t連結到B的左子樹
void RR_rotation(AVLTree& A)
{
AVLTree t = A; //儲存A
AVLTree B = A->right; //A的左子樹
AVLTree BL = B->left; //A的右子樹 的 左子樹
B->left = t; // B結點左子樹連結原來的A結點
t->right = BL; // A結點右子樹連結原來B結點的左子樹
A = B; // 原來樹中的A結點用B結點替代
// 旋轉後結點位置變化,需要更新樹高
reHeight_Bf(A->left);
reHeight_Bf(A);
}
LR平衡旋轉:是因為在結點A的左子樹的右子樹上插入,導致該結點A的平衡因子hl - hr大於1,失去平衡。此時就需要1次RR操作,1次LL操作。具體如下:
①、對A的左子樹B進行RR操作;②、對A進行LL操作
void LR_rotation(AVLTree& A)
{
RR_rotation(A->left); // 先對A的左子樹根結點RR旋轉
LL_rotation(A); // 再對A結點LL旋轉
}
RL平衡旋轉:與LR映象相反
是因為在結點A的右子樹的左子樹上插入,導致該結點A的平衡因子hl - hr小於-1,失去平衡。此時就需要1次LL操作,1次RR操作。具體如下:
①、對A的右子樹B進行LL操作;②、對A進行RR操作
void RL_rotation(AVLTree& A)
{
LL_rotation(A->right); // 先對A的右子樹根結點LL旋轉
RR_rotation(A); // 再對A結點RR旋轉
}
之前在BST那塊寫了非遞迴的插入和刪除,這裡就偷懶一哈寫遞迴的了(遞迴還是簡潔好懂啊~)
插入:
// BST寫過非遞迴的,這裡就寫遞迴形式的好了( 其實是想偷懶了(#^.^#) )
bool AVL_Insert(AVLTree& root, const ElementType& x)
{
if( !root ){
root = new TreeNode(x);
return true;
}
// AVL樹滿足BST性質,不允許有相同的值存在
if( x == root->val ){
cerr << "Same value in AVL_Tree!\n\n";
return false;
}
// 左子樹遞迴
else if( x < root->val )
{
AVL_Insert(root->left, x);
reHeight_Bf(root); // 插入後記得更新
if( root->bf > 1 ){
// 結點插入到左子樹的左結點,LL
if( x < root->left->val ){
cout << "value " << root->val << " LL rotation!\n";
LL_rotation(root);
}
// 否則插入到左子樹的右節點,LR
else{
cout << "value " << root->val << " LR rotation!\n";
LR_rotation(root);
}
}
}
// 右子樹遞迴
else if( x > root->val )
{
AVL_Insert(root->right, x);
reHeight_Bf(root); // 插入後記得更新
if( root->bf < -1 ){
// 結點插入到右子樹的右結點,RR
if( x > root->right->val ){
cout << "value " << root->val << " RR rotation!\n";
RR_rotation(root);
}
// 否則插入到右子樹的左節點,RL
else{
cout << "value " << root->val << " RL rotation!\n";
RL_rotation(root);
}
}
}
return true;
}
刪除:
之前的搞錯了,刪除比我想象的要複雜點。大體分2種:
①、刪除後平衡因子仍在範圍內,不作處理;
②、1)刪除左子樹的結點後,若失衡,令t = 右子樹,若t的左子樹高度 > t的右子樹高度,相當於在右子樹的左子樹插入結點,執行RL操作;否則執行RR操作
2)刪除右子樹的結點後,若失衡,令t = 左子樹,若t的左子樹高度 > t的右子樹高度,相當於在左子樹的左子樹插入結點,執行LL操作; 否則執行LR操作
// 中序遍歷下的前驅
TreeNode* find_LeftMax(AVLTree root)
{
TreeNode* t = root->left; //左子樹中查詢最右下結點
while( t->right )
t = t->right;
return t;
}
// 中序遍歷下的後繼
TreeNode* find_RightMin(AVLTree root)
{
TreeNode* t = root->right; //右子樹中查詢最左下結點
while( t->left )
t = t->left;
return t;
}
bool AVL_Delete(TreeNode*& p, const int& x)
{
if( !p ){
cerr << "No found value " << x << "\n";
return false;
}
// 查詢到要刪除的結點p
if( x == p->val)
{
// 以下直接更改p的指向,是因為傳入的引數是引用型
// 引用型是直接對原樹的結點(而非拷貝)進行操作
TreeNode* t = p;
// 左、右子樹均存在
if( p->left && p->right){
// 若刪除結點的 左子樹高度 > 右子樹高度
// 找該結點的前驅
if( getHeight(p->left) > getHeight(p->right) ){
t = find_LeftMax(p);
p->val = t->val;
AVL_Delete(p->left, t->val);
}
// 否則找該結點的後繼
else{
t = find_RightMin(p);
p->val = t->val;
AVL_Delete(p->right, t->val);
}
}
else{
p = (p->left) ? p->left:p->right;
delete t;
}
}
// 左子樹遞迴
else if( x < p->val )
{
// 未找到刪除節點,直接返回
if( !AVL_Delete(p->left, x) )
return false;
reHeight_Bf(p);
// 刪除左子樹結點後失去平衡
if( p->bf < -1 ){
TreeNode* t = p->right;
// 右子樹的左子樹 比 右子樹的右子樹高
// 相當於在右子樹的左子樹插入結點 , RL
if( getHeight(t->left) > getHeight(t->right) ){
cout << "value " << p->val << " RL rotation!\n";
RL_rotation(p);
}
// 否則相當在右子樹的右子樹插入結點,RR
else{
cout << "value " << p->val << " RR rotation!\n";
RR_rotation(p);
}
}
}
// 右子樹遞迴
else if( x > p->val )
{
// 未找到刪除節點,直接返回
if( !AVL_Delete(p->right, x) )
return false;
reHeight_Bf(p);
// 刪除右子樹結點後失去平衡
if( p->bf > 1 ){
TreeNode* t = p->left;
// 左子樹的左子樹 比 左子樹的右子樹高
// 相當於在左子樹的左子樹插入結點 , LL
if( getHeight(t->left) > getHeight(t->right) ){
cout << "value " << p->val << " LL rotation!\n";
LL_rotation(p);
}
// 否則相當在左子樹的右子樹插入結點,LR
else{
cout << "value " << p->val << " LR rotation!\n";
LR_rotation(p);
}
}
}
return true;
}
前、中、後序遍歷:
void visit(TreeNode* p)
{
cout << p->val << " ";
}
// 因為AVL樹滿足BST樹的性質,
// 即 左結點值 < 根節點值 < 右節點值
// 因此按中序遍歷恰好可以輸出嚴格遞增序列
void inOrder(AVLTree root)
{
if( root ){
inOrder(root->left);
visit(root);
inOrder(root->right);
}
}
void preOrder(AVLTree root)
{
if( root ){
visit(root);
preOrder(root->left);
preOrder(root->right);
}
}
void postOrder(AVLTree root)
{
if( root ){
postOrder(root->left);
postOrder(root->right);
visit(root);
}
}
測試程式碼:參考了一位部落格園大佬的例子(AVL樹的C測試程式,點選左側跳轉)
int main( )
{
AVLTree root = nullptr;
int value[] = {3, 2, 1, 4, 5, 6, 7, 16, 15,
14, 13, 12, 11, 10, 8, 9};
for(int x : value){
cout << "Insert value " << x << ":\n";
AVL_Insert(root, x);
cout << "preOrder: ";
preOrder(root);
cout << "\n";
cout << "inOrder: ";
inOrder(root);
cout << "\n\n";
}
cout << "\n";
preOrder(root);
cout << "\n";
inOrder(root);
cout << "\n";
postOrder(root);
cout << "\n\n\n";
int n = sizeof(value) / sizeof(value[0]);
while( n-- ){
AVL_Delete(root, value[n]);
cout << "preOrder: ";
preOrder(root);
cout << "\n";
cout << "inOrder: ";
inOrder(root);
cout << "\n\n";
}
return 0;
}