和二叉查找树相同,AVL树的基本操作有查找、插入、建树以及删除。
查找操作
由于AVL树是一颗二叉查找树,因此其查找的操作与二叉查找树相同
代码如下:
void search(node *root,int x){
if(root==NULL){
printf("search failed\n");
return;
}
if(x==root->data){
printf("%d\n",root->data);
}else if(x<root->data){
search(root->lchild,x);
}else{
search(root->rchild,x);
}
}
插入操作
这个调整过程被称为左旋(Left Rotation).假设指针root指向结点A,指针temp指向结点B,于是调整过程可以分为三个步骤,如下:
- 让B的左子树成为A的右子树
- 让A成为B的左子树
- 将根结点设定为结点B
对应的代码如下:
//左旋
void L(node *&root){
node *temp=root->rchild;//root指向结点A,temp指向结点B
root->rchild=temp->lchild;//步骤1
temp->lchild=root;//步骤2
updateHeight(root);//更新结点A 的高度
updateHeight(temp);//更新结点B 的高度
root=temp;//步骤3
}
右旋和左旋是对称的过程
步骤如下:
- 让A的右子树成为B的左子树
- 让B成为A的右子树
- 将根结点设定为结点A
代码如下:
//右旋
void R(node *&root){
node *temp=root->lchild;//root指向结点A,temp指向结点B
root->lchild=temp->rchild;//步骤1
temp->rchild=root;//步骤2
updateHeight(root);//更新结点A 的高度
updateHeight(temp);//更新结点B 的高度
root=temp;//步骤3
}
左旋和右旋互为逆操作
假设现在已经有一颗平衡二叉树,那么可以预见到,在往其中插入一个结点时,一定会有结点的平衡因子发生改变,此时可能会有结点的平衡因子的绝对值大于1,值可能为2或者-2,这样以该结点为根结点的子树就是失衡的,需要进行调整,显然,只有在从根结点到插入结点的路径上的结点的平衡因子才有可能发生变化,因此只需要对这条路径上失衡的结点进行调整。可以证明,只要把最靠近插入节点的失衡结点调整为正常,路径上的所有结点就都会平衡,
假设最靠近插入结点的失衡结点是A,显然它的平衡因子只可能是2或-2的情况,这两种情况完全对称,因此主要讨论结点A的平衡因子是2的情形。
由于A的平衡因子是2,因此A的左子树的高度比右子树大2于是以结点A为根结点的子树一定是LL与LR型之一,(注意LL和LR只表示树形),可以发现,结点A的左孩子的平衡因子是1时为LL型,是-1时为LR型
先考虑LL型,可以把C为根结点的子树看作一个整体,然后以结点A作为root进行右旋,达到平衡的效果。
然后考虑LR型,可以先忽略结点A,以结点C为root进行左旋,就可以把情况转化为LL型,然后再按照上面的LL型进行一次右旋即可
至此,结点A的平衡因子是2的情况已经讨论清楚,下面简要说明平衡因子是-2的情况,显然两种情况是完全对称的
由于结点A的平衡因子为-2,因此右子树的高度比左子树大2,于是以结点A为根结点的两种形态可能为RR型或RL型,即当结点A的右孩子的平衡因子是-1时为RR型,为1时是RL型。
对RR型而言,可以把以C为根结点的子树看作一个整体,然后以结点A作为root进行左旋,即可达到平衡。
对RL型而言,可以先忽略结点A,以结点C为root进行右旋,就可以把情况转化为RR型,然后按照上面的RR型的做法进行一次左旋即可。
至此,对LL型、LR型、RR型、RL型的调整方法已经讨论清楚,下面是汇总表
树型 判定条件 调整方法
LL BF(root)=2,BF(root->lchild)=1 对root进行右旋
LR BF(root)=2,BF(root->lchild)=-1 对root->lchild进行左旋, 对root进行右旋
RR BF(root)=-2,BF(root->rchild)=-1 对root进行左旋
RL BF(root)=-2,BF(root->rchild)=1 对root->rchild进行右旋, 对root进行左旋
首先,AVL树的插入操作是在二叉查找树的插入代码的基础上增加平衡操作的,因此,如果不考虑平衡操作,代码是这样的
//insert函数将在二叉查找树中插入一个数据域为x的新结点(注意参数root要加引用)
void insert(node *&root,int x){
if(root==NULL){
//空树,查找失败
root=newNode(x);//新建结点
return;
}
if(x==root->data){
//查找成功,结点已经存在,直接返回
return;
}else if(x<root->data){
insert(root->lchild,x);
}else{
insert(root->rchild,x);
}
}
在这个基础上,由于需要从插入的结点开始从下向上判断结点是否失衡,因此需要在每个insert函数之后更新当前子树的高度,并在这之后根据树型是LL型、LR型、RR型、RL型之一来进行平衡操作,代码如下:
//插入权值为v的结点
void insert(node *&root,int v){
if(root==NULL){//到达空结点
root=newNode(v);
return;
}
if(v<root->v){
insert(root->lchild,v);
updataHeight(root);//更新树高
if(getBalanceFactor(root)==2){
if(getBalanceFactor(root->lchild)==1){
//LL型
R(root);
}else if(getBalanceFactor(root->lchild)==-1){
//LR型
L(root->lchild);
R(root);
}
}
}else{
//v比根结点的权值大
insert(root->rchild,v);//往右子树中插入
updateHeight(root);//更新树高
if(getBalanceFactor(root)==-2){
if(getBalanceFactor(root->rchild)==-1){
//RR型
L(root);
}else if(getBalanceFactor(root->rchild)==1){
//RL型
R(root->lchild);
L(root);
}
}
}
}
AVL树的建立
有了上面的插入操作的基础,AVL树的建立就非常的简单了,因为只需要依次插入n个结点即可,代码如下:
//AVL树的建立
node *Create(int data[],int n){
node *root=NULL;//新建空根结点root
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;//返回根结点
}