平衡二叉树(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的平衡因子。
由于在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: {
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->pright->bf==0) {//为L0情况经过平衡化后子树 //的高度和删除前一样 shorter=false; } else //L1,L-1,平衡化后与删除前 //比子树高度减小1,其父树 //还需要平衡化操作 shorter=true;
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,大概的实现方法是这样的。整了几天,还是觉得和方法和代码结合。容易被接受。总结出来也方便以后查看。