平衡二叉树(Balancedbinary tree)是由阿德尔森-维尔斯和兰迪斯(Adelson-Velskiiand Landis)于1962年首先提出的,所以又称为AVL树。
定义:平衡二叉树或为空树,或为如下性质的二叉排序树:
(1)左右子树深度之差的绝对值不超过1;
(2)左右子树仍然为平衡二叉树.
平衡二叉树可以避免排序二叉树深度上的极度恶化,使树的高度维持在O(logn)来提高检索效率。
平衡二叉树算法思想
若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树 。
要维持二叉树的平衡需要使用左旋和右旋操作。
右旋(如图1):以A为中心顺时针旋转。实际就是把B移到A的位置,把B的右子树变为A的左子树,在把A作为B的右子树。在平衡二叉树中以平衡因子为2的节点进行右旋。
右旋前 右旋后
右旋代码:
//右旋 void RotateRight(tNode** root) { tNode* p=*root;//图中相当于A tNode* rc=p->pleft;//相当于B tNode* rrc=rc->pright;//相当于D p->pleft=rrc; rc->pright=p; *root=rc; }
左旋(如图):以A为中心逆时针旋转,实际就是:把B移到A的位置,把B的左子树作为A的右子树,再把A变成B的左子树。在平衡树种以平衡因子为-2的节点为中心做左旋操作。
左旋前 左旋后
左旋代码:
//左旋
void RotateLeft(tNode** root)
{
tNode * p=*root;//相当于A
tNode* lc=p->pright;//相当于B
tNode* llc=lc->pleft;//相当于C
p->pright=llc;
lc->pleft=p;
*root=lc;
}
插入新的节点:
1、LL的情况:
由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。
2、LR情况:
由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。
左平衡代码:
void AVLTree::LeftBalance(tNode** root) { tNode* p=*root; tNode* c=p->pleft; tNode* rc=c->pright; switch(c->bf) { //LL情况 case 1: { p->bf=c->bf=0; RotateRight(root); break; } //LR情况 case -1: { switch(rc->bf) { case 1: { c->bf=0; p->bf=-1; break; } case 0: { c->bf=0; p->bf=0; break; } case -1: { c->bf=1; p->bf=0; break; } } rc->bf=0; RotateLeft(&c); RotateRight(root); break; } } }
左平衡操作在处理LR情况时又根据rc节点也就是图中D的平衡因子分三种情况来确定平衡后图中B和A的平衡因子。其他的平衡因子不变。
怎么来确定A和B左平衡操作后的平衡因子?
假设在LR情况下,新的节点插入后C的平衡因子是1,那么设C的左子树的高度为h则右子树为h-1,B的右子树为h+1,B的平衡因子是-1,则它左子树高度为h,A左子树高度为h+2,它的平衡因子为2,则它的右子树高度为h。在平衡化后,A的左子树以C为根,右子树为D,所以A的平衡因子为0.而B的右子树以A为根,则它的平衡因子为-1.
在C的平衡因子为0,-1的情况下也可以用上面方法推出,平衡后A和B的平衡因子。
3 RR情况:
由于在A的右孩子C 的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。
4、RL情况:
由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树。
右平衡代码:
void AVLTree::RightBalance(tNode** root) { tNode* p=*root; tNode* c=p->pright; tNode* lc=c->pleft; switch(c->bf) { //RR情况 case -1: { p->bf=-1; c->bf=0; RotateLeft(root); break; } //RL情况 case 1: { switch(lc->bf) { case 1: { p->bf=0; c->bf=-1; break; } case 0: { p->bf=0; c->bf=0; break; } case -1: { p->bf=1; c->bf=0; break; } lc->bf=0; RotateRight(&c); RotateLeft(root); break; } } } }
右平衡在RL的情况下平衡因子的变化也分三种情况。因子推断与上面相同。
左旋,右旋,左平衡,右平衡都齐全了。那么插入新节点操作也只是把他们穿针引线的连起来而已。
插入新的节点代码 :
插入新节点以递归的方式。在插入新节点成功后回溯更新和维护树的平衡性。
int AVLTree::Insert(tNode** root,int key,int& taller) { if(*root==NULL) { tNode* newNode=new tNode(key); taller=1; *root=newNode; return 1; } else if((*root)->mvalue==key) { return 0; } else if(key<(*root)->mvalue) { if(Insert(&((*root)->pleft),key,taller)) { return 1; } if(taller) { switch((*root)->bf) { case 1: LeftBalance(root); taller=0; break; case 0: (*root)->bf=1; taller=1; break; case -1: (*root)->bf=0; taller=0; break; } } return 0; } else if(key>(*root)->mvalue) { if(Insert(&((*root)->pright),key,taller)) { return 1; } if(taller) { switch((*root)->bf) { case -1: RightBalance(root); taller=0; break; case 0: (*root)->bf=-1; taller=1; break; case 1: (*root)->bf=0; taller=0; break; } } return 0; } }
删除节点操作:
删除节点操作比插入稍微复杂。插入只需要一次平衡操作即可恢复树的平衡性。而删除节点在删除后,可能需要多次操作才能恢复树的平衡。
要先找到最底部不平衡的子树,将其恢复平衡。在根据情况判断需不需要继续沿着子树向上恢复平衡。
为了找到最底部不平衡的子树,将删除操作迁移到最底部的子树上进行。
删除节点的代码为:
//返回1表示要删除的点在最底部子树上,删除成功。返回0表示将删除节点向下迁移。
int AVLTree::deleteNode(tNode* p,tNode* pp,int& key,bool& shorter)
{
//p表示当前节点,pp表示当前节点的父节点。key表示要删除元素的键值,
//shorter 表示是否需要平衡化操作。
if(p->pleft&&p->pright)
{
tNode* s=p->pleft;
tNode* ps=p;
while(s->pright!=NULL)
{
ps=s;
s=s->pright;
}
p->mvalue=s->mvalue;//覆盖原来要删除的点的值。
key=s->mvalue;//将关键字的值更改。
return 0;//没删除成功只是替换了要删除的元素
}
tNode* c=NULL;
if(p->pleft)
c=p->pleft;
else
c=p->pright;
if(p==pRoot)//pRoot表示树的根
pRoot=c;
else
{
if(p==pp->pleft)
{
pp->pleft=c;
}
else
{
pp->pright=c;
}
}
shorter=true;
delete p;
return 1;
}
删除节点的代码
int AVLTree::Delete(tNode* root,tNode* parent,int key,bool& shorter)
{
if(root==NULL)
{
shorter=false;
return 0;
}
else if(root->mvalue==key)
{
if(deleteNode(root,parent,key,shorter))
return 1;
}
else if(root->mvalue>=key)
{
if(Delete(root->pleft,root,key,shorter))
{
if(shorter)
{
switch(root->bf)
{
case 1:
{
root->bf=0;
shorter=true;
break;
}
case 0:
{
root->bf=-1;
shorter=false;
break;
}
case -1:
{
if(root->pright->bf==0)
{//为L0情况经过平衡化后子树
//的高度和删除前一样
shorter=false;
}
else
//L1,L-1,平衡化后与删除前
//比子树高度减小1,其父树
//还需要平衡化操作
shorter=true;
RightBalance(&root);break;}}}return 1;}return 0;}else if(root->mvalue<key){if(Delete(root->pright,root,key,shorter)){if(shorter){switch(root->bf){case 1:{
if(root->pleft->bf==0)
shorter=false;
else
shorter=true;
LeftBalance(&root);break;}case 0:{root->bf=1;shorter=false;break;}case -1:{root->bf=0;shorter=true;break;}}}return 1;}return 0;}}
平衡树类定义代码:
struct tNode
{
int bf;//平衡因子
int mvalue;
tNode* pleft,
*pright,
*parent;
tNode():bf(0),mvalue(0),pleft(0),pright(0),parent(0)
{
}
tNode(int ivalue):bf(0),mvalue(ivalue),pleft(0),pright(0),parent(0)
{
}
};
class AVLTree
{
public:
AVLTree(void):pRoot(0){}
~AVLTree(void);
//插入值为key的节点
int AVL_Insert(int key)
{
int taller;
Insert(&pRoot,key,taller);
}
//删除值为key的节点
int AVL_Delete(int key)
{
bool shorter;
Delete(pRoot,0,key,shorter);
}
private:
//左旋
void RotateLeft(tNode** root)
{
tNode * p=*root;
tNode* lc=p->pright;
tNode* llc=lc->pleft;
p->pright=llc;
lc->pleft=p;
*root=lc;
}
//右旋
void RotateRight(tNode** root)
{
tNode* p=*root;
tNode* rc=p->pleft;
tNode* rrc=rc->pright;
p->pleft=rrc;
rc->pright=p;
*root=rc;
}
//左平衡
void LeftBalance(tNode** root);
//右平衡
void RightBalance(tNode** root);
//插入新的节点
int Insert(tNode** root,int key,int& taller);
//删除某个节点
int Delete(tNode* root,tNode* parent,int key,bool& shorter);
//删除节点的实际操作
int deleteNode(tNode* p,tNode* pp,int& key,bool& shorter);
private:
tNode* pRoot;
};
程序有Bug,大概的实现方法是这样的。整了几天,还是觉得和方法和代码结合。容易被接受。总结出来也方便以后查看。