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