二叉平衡树AVL是一种特殊的二叉搜索树,区别在于其能有效控制树的高度,避免二叉树的退化。
二叉平衡树:
其根的左右子树的高度之差的绝对值不超过1;
其根的左右子树都是二叉平衡树。
节点的平衡因子定义为该节点的左子树的高度减去右子树的高度。平衡因子只能是1,0,-1。左子树更高则为+1,否则为-1.
节点中加入平衡因子域 bF,方便插入和删除操作。
s为其插入节点后新节点的具有非0平衡因子(插入之前的值)的最近的祖先。
插入父节点的左边进行左旋转,右边为右旋转。
先考虑左选择,包括LL和LR选择:
考虑旋转前的一些设定:
新节点q已经插入树中,节点s为新节点q的具有非0平衡因子值(插入前的值)的最近的祖先。左旋转q插在s的左子树上,从s到q的所有节点(不包括s)的平衡因子值均已做修正。
情况一:若插入前,从s节点到q节点的节点的平衡因子都为0,则插入q后,只需将s节点的平衡因子改为1,由于其他的节点已经改完,不需要考虑。
情况二:若s->bF = -1,即s节点其左子树比右子树低1,这加入后s的因子修正为0即可。
情况三:若s->bF = 1,即s节点左子树比右节点高1,然后需要进行旋转。旋转有两种情况,LL和LR。记s的左孩子为r。
LL:若r->bF = 1,由于q在s的左子树中,且q的因子为0,则q在r的左子树中,则将s = r,然后将r->rchild = s。并修正因子,s改为0,r的平衡因子也应改为0。
LR:r->bF = -1 ,q在r的右子树中,需要考虑u的情况,u为r的右孩子,这里又有3中情况:替换的操作在之后说明到,这里只要考虑因子的更新即可。
u->bF = 1 :r的因子设为0,s的因子设为-1,这里要靠画图解释的清楚了,大致是若r的右子树高度为h的话,那u的左子树为h,右子树为h-1,s的右子树为h,然后考虑左右交换后的情况,得到这样的结果。
u->bF = -1 : s该为0,r改为1,这里同理,使用画图进行分析,
u->bF = 0 :当u的因子为0时,即插入的点即为u,则由于u为r的右子树,则有u之前,r为0,加入u改为-1,然后出现现在的情况,而s没有右孩子。所以这种情况只要将u的因子设为0,,s的因子和r的因子设为0.
这三种情况都进行相同的替换操作,将u的左孩子赋值给r的右孩子,然后将r赋值给u的左孩子,,在将u的右孩子赋值给s的左孩子,然后将s赋值给u的右孩子,最后 用u替代s,
对于前两种不进行旋转的情况放在递归的insert的函数中判断,而左旋转函数只进行LL旋转和LR旋转
代码如下:
template<typename T>
void AVLTree<T>::LRotation(AVLNode<T>* &s,bool &unBalanced){
AVLNode<T> *u,*r = s->lchild;
if(r->bF == 1){//LL旋转
s->lchild = r->rchild;
r->rchild = s;
s->bF = 0;
s = r;
}else{//LR旋转
u = r->rchild;
r->rchild = u->lchild;
u->lchild = r;
s->lchild = u->rchild;
u->rchild = s;
switch(u->bF){
case 0:s->bF = 0;r->bF = 0;break;
case -1:r->bF = 1;s->bF = 0;break;
case 1:r->bF = 0;s->bF = -1;break;
}
s = u;
}
s->bF = 0;
unBalanced = false;
}
这是左旋转的全部。右旋转同理,刚好相反。
template<typename T>
void AVLTree<T>::RRotation(AVLNode<T>* &s,bool &unBalanced){
AVLNode<T> *u,*r = s->rchild;
if(r->bF == -1){//RR旋转
s->rchild = r->lchild;
r->lchild = s;
s->bF = 0;
s = r;
}else{//RL旋转
u = r->lchild;
r->lchild = u->rchild;
u->rchild = r;
s->rchild = u->lchild;
u->lchild = s;
switch(u->bF){
case 0:s->bF = 0;r->bF = 0;break;
case -1:r->bF = 0;s->bF = 1;break;
case 1:r->bF = -1 ;s->bF = 0;break;
}
s = u;
}
s->bF = 0;
unBalanced = false;
}
然后是插入
插入算法使用递归,步骤如下:
1。搜索新元素x插入位置,构造新节点并插入。
2。修正从新节点到s的平衡因子。
3。如果是情况一二,则算法结束,否则调用LRotation或RRotation,完成旋转。
这里也就是是与之前旋转函数的假定相对应的,在递归的找到x的插入位置,构造新节点后,向上返回其父节点,然后修改其每个祖先节点对应的平衡因子,这也是进行旋转操作时,之前节点的因子已经更新好的原因。
对于三中所说的前两种情况即 当新节点为其父节点的左子树时,s节点的因子为-1或0的情况,以及q节点为右子树时,s节点的因子为1或0的情况。
在递归向上的路途中,其到达s节点之前的祖先节点的因子都为0,因为s节点为第一个非0的祖先节点。
当进行一次旋转后,则不再对因子进行判断以及旋转操作,所以使用unbalanced变量来传递是否已进行旋转操作。由前两点可知,当递归向上路程中遇到0时,继续递归向上,1或-1则停止,在根据左右子树的情况节点其旋转情况。
如果AVL树原来为空,则生成一个新的节点作为根节点。
代码如下:
template<typename T>
bool AVLTree<T>:: Insert(const T& x){
bool unBalanced = true;
return Insert(root,x,unBalanced);
}
template<typename T>
bool AVLTree<T>::Insert(AVLNode<T>* &p,T& x,bool &unBalanced){
if(p == 0){
p = new AVLNode<T>(x);
unBalanced = true;
}else if(x < p->element){
Insert(p->lchild,x,unBalanced);
if(unBalanced){
switch (p->element)
{
case 0:p->bF = 1;break;
case -1:p->bF = 0;unBalanced = false;break;
case 1:LRotation(p,unBalanced);break;
}
}
}else if(x == p->element){
unBalanced = false;
return false;//重复
}else{
Insert(p->rchild,x,unBalanced);
if(unBalanced){
switch (p->element)
{
case 0:p->bF = -1;break;
case -1:RRotation(p,unBalanced);break;
case 1:p->bF = 0; unBalanced = false;break;
}
}
}
return true;
}
最后是平衡二叉树的删除操作:
还是使用向下旋转,如果删除点q有两颗非空子树,则将其与其中序后缀节点交换,改为删除一个最多一颗子树的情况,然后还是要递归的判断。
递归寻找删除点,使用shorter变量记录其子树是否缩短,然后返回上一层,如果缩短,进行旋转,根据下一层传来的缩短信息判断当前节点的子树是否缩短以及是否需要旋转。看到这里,我已经不想继续写下去了,因为AVL树自红黑树出现后就进了博物馆,其删除的操作浪费更多的时间,效率不高,要研究就看下红黑树,知道AVL树的思路即可。