昨天搞了一個晚上,可算是把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;
}