一、介绍
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logn)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
二、AVL树的C++实现
2.1 树节点
template <class T>
struct BSTNode{
T data;
int h;
BSTNode<T> * lchild, * rchild;
};
2.2 类声明
template <class T>
class AVLTree{
public:
AVLTree():root(NULL){}
~AVLTree(){Release(root);}
bool Insert(T x){return Insert(root,x);}
bool Delete(T x){Delete(root,x);}
bool Search(T &x){return !(Search(root,x)==NULL);}
bool Modify(const T &x);
void PreOrder(void visit(const T &x)){PreOrder(root,visit);}
void InOrder(void visit(const T &x)){InOrder(root,visit);}
private:
BSTNode<T> * root;
void Release(BSTNode<T> * bst);
inline int Height(BSTNode<T> * bst){return (bst==NULL?-1:bst->h);}
void SingleRotateWithLeft(BSTNode<T> * &bst); //LL型
void DoubleRotateWithLeft(BSTNode<T> * &bst); //LR型
void SingleRotateWithRight(BSTNode<T> * &bst); //RR型
void DoubleRotateWithRight(BSTNode<T> * &bst); //RL型
bool Insert(BSTNode<T> * &bst, const T &x);
bool Delete(BSTNode<T> * &bst, const T &x);
BSTNode<T> * Search(BSTNode<T> * bst, T &x);
void PreOrder(BSTNode<T> * bst, void visit(const T& x));
void InOrder(BSTNode<T> * bst, void visit(const T& x));
};
2.3 插入操作
AVL树在算法设计上与普通二叉排序树的最大区别在于AVL树在插入和删除后必须进行重平衡操作。
原理:
可以想到失衡最小的形状如下四种情况:
图2.1 失衡最小形状的四种情况
继续扩展为:
图2.2 失衡调整(最低的失衡节点g,在g更高一侧子树内,其孩子节点p和孙子节点v必然存在)
四种旋转函数:
/*
函数:左单旋:即LL型
参数:bst:最小不平衡子树根指针
返回值:空
*/
template <class T>
void AVLTree<T>::SingleRotateWithLeft(BSTNode<T> * &bst)
{
BSTNode<T> * t = bst->lchild;
bst->lchild = t->rchild;
t->rchild = bst;
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
t->h = max(Height(t->lchild),bst->h) + 1;
bst = t;
}
/*
函数:右单旋:即RR型
参数:bst:最小不平衡子树根指针
返回值:空
*/
template <class T>
void AVLTree<T>::SingleRotateWithRight(BSTNode<T> * &bst)
{
BSTNode<T> * t = bst->rchild;
bst->rchild = t->lchild;
t->lchild = bst;
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
t->h = max(bst->h,Height(t->rchild)) + 1;
bst = t;
}
/*
函数:左双旋:即LR型
参数:bst:最小不平衡子树根指针
返回值:空
*/
template <class T>
void AVLTree<T>::DoubleRotateWithLeft(BSTNode<T> * &bst)
{
SingleRotateWithRight(bst->lchild);
SingleRotateWithLeft(bst);
}
/*
函数:右双旋:即RL型
参数:bst:最小不平衡子树根指针
返回值:空
*/
template <class T>
void AVLTree<T>::DoubleRotateWithRight(BSTNode<T> * &bst)
{
SingleRotateWithLeft(bst->rchild);
SingleRotateWithRight(bst);
}
插入函数可写为:
/*
函数:插入x
参数:bst:树的根指针,进行调整的时候会修改其值,故用引用 x:要插入的值
返回值:是否成功插入
*/
template <class T>
bool AVLTree<T>::Insert(BSTNode<T> * &bst, const T &x)
{
if(bst == NULL){
bst = new BSTNode<T>{x,0,NULL,NULL};
if(bst == NULL){
cerr << "Out of Space !!!" << endl;
exit(1);
}
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return true;
}
else if(x < bst->data){
bool r = Insert(bst->lchild, x);
if(Height(bst->lchild) - Height(bst->rchild) == 2){
if(x < bst->lchild->data)
SingleRotateWithLeft(bst);
else{
DoubleRotateWithLeft(bst);
}
}
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return r;
}
else if(x > bst->data){
bool r = Insert(bst->rchild, x);
if(Height(bst->lchild) - Height(bst->rchild) == -2){
if(x > bst->rchild->data)
SingleRotateWithRight(bst);
else{
DoubleRotateWithRight(bst);
}
}
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return r;
}
else{
cout << "插入元素已经存在!!!" << endl;
return false;
}
}
2.4 删除操作
普通二叉排序树删除一个节点
有三种情况:设待删除节点为v,其双亲节点为p
(1)v节点为叶子节点:直接删除
(2)v只有左子树或只有右子树:删除后让子树根节点取代删除节点
(3)v既有左子树,又有右子树,分两种情况:
一般情况:
图2.3
特殊情况:
图2.4
AVL树删除一个节点
原理:AVL树删除一个节点的操作与普通二叉排序树相同,但删除后需进行重平衡,删除操作的失衡情况与插入操作大致相同,插入操作失衡是在子树高的一端插入节点,删除操作失衡是在子树低的一端删除节点。代码实现如下:
/*
函数:删除值为x的节点
参数:bst:树根指针,进行调整的时候会修改其值,故用引用 x:要删除的值
返回值:是否成功删除
*/
template <class T>
bool AVLTree<T>::Delete(BSTNode<T> * &bst, const T &x)
{
if(bst == NULL){
cout << "删除元素不存在!!!" << endl;
return false;
}
else if(x < bst->data){
bool r = Delete(bst->lchild, x);
if(Height(bst->lchild) - Height(bst->rchild) == -2){
if(Height(bst->lchild) - Height(bst->rchild->rchild) == -1)
SingleRotateWithRight(bst);
else
DoubleRotateWithRight(bst);
}
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return r;
}
else if(x > bst->data){
bool r = Delete(bst->rchild, x);
if(Height(bst->lchild) - Height(bst->rchild) == 2){
if(Height(bst->lchild->lchild) - Height(bst->rchild) == 1)
SingleRotateWithLeft(bst);
else
DoubleRotateWithLeft(bst);
}
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return r;
}
else{
if(bst->lchild == NULL && bst->rchild == NULL){
delete bst;
bst = NULL;
}
else if(bst->lchild != NULL && bst->rchild == NULL){
BSTNode<T> * p = bst;
bst = bst->lchild;
delete p;
}
else if(bst->lchild == NULL && bst->rchild != NULL){
BSTNode<T> * p = bst;
bst = bst->rchild;
delete p;
}
else{
BSTNode<T> * p = bst->rchild;
while(p->lchild != NULL){
p = p->lchild;
}
bst->data = p->data;
if(p == bst->rchild)//特殊情况
bst->rchild = p->rchild;
else{ //一般情况
bst->rchild->lchild = p->rchild;
bst->rchild->h = max(Height(bst->rchild->lchild),Height(bst->rchild->rchild))+1;
}
delete p;
}
if(bst != NULL)
bst->h = max(Height(bst->lchild),Height(bst->rchild)) + 1;
return true;
}
}
2.5 其他
其他未给出的代码已经放到github上(点击访问),可以访问下载。
三、拓展
统一重平衡算法:
上述重平衡的方法,需要根据失衡节点及其孩子节点、孙子节点的相对位置关系,分别做单旋或双旋调整。按照这一思路直接实现调整算法,代码量大且流程繁杂,必然导致调试困难且容易出错。为此引入一种更为简明的统一处理方法。
无论对于插入或删除操作,新方法也同样需要从刚发生修改的位置x出发逆行而上,直至遇到最低的失衡节点g(x)。于是在g(x)更高一侧的子树内,其孩子节点p和孙子节点v必然存在,而且这一局部必然可以g(x)、p和v为界,分解为四棵子树,将它们按中序遍历次序重命名为至。
若同样按照中序遍历次序,重新排列g(x)、p和v,并将其命名为a、b和c,则这一局部的中序遍历序列应为:
{,a,,b,,c,}
这就意味着,这一局部应等价于如图3.1所示的子树。更重要的是,纵观图2.2的四棵子树可见,它们的高度彼此相差不超过一层,故只需如图3.1所示,将这三个节点与四棵子树重新“组装”起来,恰好即是一颗AVL树!
图3.1 统一重新平衡
实际上,这一理解涵盖了此前两节所有的单旋和双旋情况。相应的重构过程,仅涉及局部的三个节点及其四棵子树,故称作“3+4”重构。
四、总结
本文算法的实现主要参考了《数据结构与算法分析:C语言描述》、《数据结构 C++语言版 邓俊辉》、《数据结构 C++版 王红梅》,对于有理解不正确的地方或代码有bug,欢迎在留言区评论。
箴言录:
勿以恶小而为之,勿以善小而不为。