AVL( Adelson-Velsky and Landis)树是一棵自平衡的二叉查找树BST(binary search tree),所谓二叉查找树就是:左子树结点值<根节点值<右子树结点值。在查找结点值时相对普通二叉树的查找可以进行剪枝处理,当待查找值小于当前结点值时,往左子树查找,当待查结点值大于当前结点值时往右子树查找,这样查找时总体能达到o(logn)的复杂度。但当二叉查找树在建立过程中如果退化成一条链表,那么查找时间复杂度就是o(n),AVL树正是为了防止这一极端情况的发生,它要求在对树的操作过程中要保证每个结点的平衡因子绝对值都小于2,这样以保证在查找过程中总能保持logn的复杂度。因此,相比普通二叉树,AVL树的建立过程更为复杂一些。
node* avl_newNode(int x)
{
node*Node=new node;
Node->data=x;
Node->height=1;//初始高度设置为1
Node->lchild=Node->rchild=NULL;
return Node;
}
int getHeight(node*root)//返回结点的高度
{
if(root==NULL)
return 0;
return root->height;
}
int getbalanceFactor(node*root)//返回平衡因子
{
return getHeight(root->lchild)-getHeight(root->rchild);
}
void update_height(node*root)//并未对指针做改变,所以不需要引用
{
root->height=max(getHeight(root->lchild),getHeight(root->rchild))+1;//根结点高度等于左右子树高度较大值加1
}
上面的操作都比较基础,值得注意的是,国外一般喜欢把叶结点的高度认为是0(比如Mark Allen Weiss的《数据结构与算法分析》中就树叶的高置为0),而国内一般是把叶结点的高度设为1,这有点像楼层的叫法区别,我这里还是将叶节点的高度初始化为1,毕竟无论考试还是应用都不会算错。有了这些基本操作还无法构造一棵AVL树,因为在建树过程中最关键的调整部分还没写出来。AVL树的调整就两种,左旋和右旋,无非是在下一次插入节点的时候如果失衡,我该如何调整的问题,比如下图我要插入一个结点值为2的结点,那么根节点将会失去平衡,因此需要先调整到右图再进行插入操作,图中这一过程称为右旋,就好像是135这三个结点按照树的形状顺时针旋转了一下。
这部分对应的代码如下,左旋同理:
void R(node*&root)//右旋
{
node*temp=root->lchild;
root->lchild=temp->rchild;
temp->rchild=root;
update_height(root);
update_height(temp);
root=temp;
}
void L(node*&root)//左旋
{
node*temp=root->rchild;
root->rchild=temp->lchild;
temp->lchild=root;
update_height(root);//更新两节点高度
update_height(temp);
root=temp;
}
有了调整操作,这时如果出现了不平衡的情况,找到对应的调整方法,套用上面的代码即可,不平衡情况主要就下面四种,LL(左子树L左子节点L插入导致不平衡)、LR(左子树右子节点插入导致不平衡)、RR(右子树右子节点插入导致不平衡)、RL(右子树左子节点插入导致不平衡)
可以看出来这些根节点的平衡因子要么为2要么为-2,其实这些树要想平衡,肯定都是转化为以结点值为2的结点为根,另外两个结点分别是左右子树的形式。首先考虑LL型,可以看出来只需要一次右旋即可平衡;对于LR型,可以先对以root->lchild为根节点的子树进行一次左旋,即可转换成LL型,然后再对root进行一次右旋,即可平衡;对于RR型和RL型与前两种恰好是相反的,不必赘述,可以得到AVL树插入操作的代码如下:
void balance_insert(node*&root,int x)
{
if(root==NULL)//root为空,直接新建结点
{
root=avl_newNode(x);
return;
}
if(x<root->data)//待插入结点值小于根节点值,插入左子树
{
balance_insert(root->lchild,x);
update_height(root);//插入完成及时更新树高,若不进行此步,所有结点的height都是默认的height
if(getbalanceFactor(root)==2)//LL或者LR型
{
if(getbalanceFactor(root->lchild)==1)//LL型,右旋操作即可
{
R(root);
}
else if(getbalanceFactor(root->lchild)==-1)//LR型,先对左子树左旋,再整体右旋
{
L(root->lchild);
R(root);
}
}
}
else
{
balance_insert(root->rchild,x);
update_height(root);
if(getbalanceFactor(root)==-2)
{
if(getbalanceFactor(root->rchild)==-1)//RR型,直接左旋即可
{
L(root);
}
else if(getbalanceFactor(root->rchild)==1)//RL型,先对右子节点右旋,再整体左旋
{
R(root->rchild);
L(root);
}
}
}
}
有了插入节点的操作,建树就很简单了,直接一个一个插入即可,下面是建树代码:
node* balance_creat(int data[],int n)
{
node*root=NULL;//新建空结点,后面写了root->lchild=root->rchild=NULL凉过一次
for(int i=0;i<n;++i)
balance_insert(root,data[i]);
return root;
}
建树完毕,这棵树在使用上并没有太多技巧,主要还是二叉查找树那一套。