平衡二叉树
- 二叉查找树的查找、插入、删除的时间复杂度均线性正比于二叉查找树的高度,高度越小,效率越高。
- 最好的情况下,每次都一分为二,左右子树的节点数均为n/2,左右子树的高度也一样。
原理 AVL树
- 平衡二叉查找树,简称平衡二叉树(AVL)
- 平衡二叉树或为空树,或为具有以下性质的平衡二叉树:①左右子树高度差的绝对值不超过1;②左右子树也是平衡二叉树
- 平衡二叉树除了具有适度平衡性,还具有局部性:①在单次插入、删除后,至多有O(1)处出现不平衡;②总可以在O(logn)时间内,使这O(1)处不平衡重新调整为平衡。
- 对平衡二叉树在动态修改后出现的不平衡,只需局部(最小不平衡子树)调整平衡即可,不需要对整颗树进行调整。
调整平衡的方法
- 调整平衡可以分为4种情况: LL型、RR型、LR型、RL型。
LL型
- 插入新节点x后,从该节点向上找到最近的不平衡节点A,如果最近不平衡节点到新节点的路径前两个都是左子树L,就是LL型。
- 即将节点x插入A的左子树的左子树中,A的左子树因插入新节点而高度增加,造成A的平衡因子由1增加为2,失去平衡。需要进行LL旋转(顺时针)调整平衡。
- LL旋转:A顺时针旋转到B的右子树,B原来的右子树T_{3}被抛弃,A旋转后正好左子树空闲,将这个被抛弃的子树 T 3 T_{3} T3放到A的左子树中即可。
- 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree LL_Rotation(AVLTree &T)//LL旋转
{
AVLTree temp=T->lchild;
T->lchild=temp->rchild;
temp->rchild=T;
updateHeight(T);//更新高度
updateHeight(temp);
return temp;
}
RR型
- 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个都是右子树R,就是RR型。需要进行RR旋转(逆时针)调整平衡。
- RR旋转:A逆时针旋转到B的左子树,B原来的左子树T_{2}被抛弃,A旋转后正好右子树空闲,将这个被抛弃的子树 T 2 T_{2} T2放到A右子树中即可。
- 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree RR_Rotation(AVLTree &T)//RR旋转
{
AVLTree temp=T->rchild;
T->rchild=temp->lchild;
temp->lchild=T;
updateHeight(T);//更新高度
updateHeight(temp);
return temp;
}
LR型
- 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个依次是左子树L、右子树R,就是LR型。
- LR旋转:分两次旋转。C逆时针旋转到A、B之间,C原来的左子树 T 2 T_{2} T2被抛弃,B正好右子树空闲,将这个被抛弃的子树 T 2 T_{2} T2放到B右子树中;这时已经转变为LL型,进行LL旋转即可,实际上也可以看作C固定不动,B进行RR旋转,然后进行LL旋转。
- 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree LR_Rotation(AVLTree &T)//LR旋转
{
T->lchild=RR_Rotation(T->lchild);
return LL_Rotation(T);
}
RL型
- 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个依次是右子树R、左子树L,就是RL型。
- RL旋转:分两次旋转。C顺时针旋转到A、B之间,C原来的右子树 T 3 T_{3} T3被抛弃,B正好左子树空闲,这个被抛弃的子树 T 3 T_{3} T3放到B左子树;这时已经转变为RR型,做RR旋转即可,实际上,也可以看作C固定不动,B进行LL旋转,然后进行RR旋转。
- 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree RL_Rotation(AVLTree &T)//RL旋转
{
T->rchild=LL_Rotation(T->rchild);
return RR_Rotation(T);
}
平衡二叉树的插入
- 在平衡二叉树中插入新的数据元素 x x x,首先要查找其插入的位置,在查找过程中,用 p p p指针记录当前节点,用 f f f指针记录 p p p的双亲。
- 在平衡二叉树中查找 x x x,如果查找成功,则什么也不做,返回 p p p;如果查找失败,则执行插入操作。
- 创建一个新节点 p p p存储 x x x,该节点的双亲为 f f f,高度为1。
- 从新节点子父 f f f出发,向上查找最近的不平衡节点。逐层检查各代祖先节点,如果平衡,则更新其高度,继续向上查找;如果不平衡,则判断失衡类型(沿着高度最大的子树判断,刚插入新节点的子树必然高度大),并作出相应的调整,返回p。
AVLTree Insert(AVLTree &T,int x)
{
if(T==NULL) //如果为空,创建新结点
{
T=new AVLNode;
T->lchild=T->rchild=NULL;
T->data=x;
T->height=1;
return T;
}
if(T->data==x) return T;//查找成功,什么也不做,查找失败时才插入
if(x<T->data)//插入到左子树
{
T->lchild=Insert(T->lchild,x);//注意插入后饭后结果挂接到T->lchild
if(Height(T->lchild)-Height(T->rchild)==2)//插入后看是否平衡,如果不平衡显然是插入的那一边高度大
{ //沿着高度大的那条路径判断
if(x<T->lchild->data)//判断是LL还是LR,即插入的是lchild节点的lchild 还是rchild
T=LL_Rotation(T);
else
T=LR_Rotation(T);
}
}
else//插入到右子树
{
T->rchild=Insert(T->rchild,x);
if(Height(T->rchild)-Height(T->lchild)==2)
{
if(x>T->rchild->data)
T=RR_Rotation(T);
else
T=RL_Rotation(T);
}
}
updateHeight(T);
return T;
}
平衡二叉树的创建
- 平衡二叉树的创建和二叉查找树的创建类似,只是插入操作多了调整平衡而已。可以从空树开始,按照输入关键字的顺序依次进行插入操作,最终得到一颗平衡二叉树。
- 初始化平衡二叉树为空树,T = NULL。
- 输入一个关键字x,将x插入平衡二叉树T中。
- 重复步骤2,直到关键字输入完毕。
AVLTree CreateAVL(AVLTree &T)
{
int n,x;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>x;
T=Insert(T,x);
}
return T;
}
平衡二叉树的删除
- 进行删除操作时需要一直从删除节点之父向上检查,发现不平衡便立即调整,然后继续向上检查,直到树根。
- 在平衡二叉树中查找x,如果查找失败,则返回;如果查找成功,则执行删除操作(同二叉查找树的删除操作)。
- 从实际被删除节点之父g出发(当被删除节点有左右子树时,令其直接前驱(或直接后继)代替其位置,删除其之间前驱,实际被删除节点为其之间前驱(或直接后继)),向上寻找最近的不平衡节点。逐层检查各代祖先节点,如果平衡,则更新其高度,继续向上寻找;如果不平衡,则判断失衡类型(沿着高度大的子树判断),并做相应的调整。
- 继续向上检查,一直到树根。
AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
if(T==NULL) return NULL;
if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
{
if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
T=LL_Rotation(T);
else
T=LR_Rotation(T);
}
if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
{
if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
T=RR_Rotation(T);
else
T=RL_Rotation(T);
}
updateHeight(T);
return T;
}
AVLTree Delete(AVLTree &T,int x)
{
if(T==NULL) return NULL;
if(T->data==x)//如果找到删除节点
{
if(T->rchild==NULL)//如果该节点的右孩子为NULL,那么直接删除
{
AVLTree temp=T;
T=T->lchild;
delete temp;
}
else//否则,将其右子树的最左孩子作为这个节点,并且递归删除这个节点的值
{
AVLTree temp;
temp=T->rchild;
while(temp->lchild)
temp=temp->lchild;
T->data=temp->data;
T->rchild=Delete(T->rchild,T->data);
updateHeight(T);
}
return T;
}
if(T->data>x)//调节删除节点后可能涉及的节点
T->lchild=Delete(T->lchild,x);
if(T->data<x)
T->rchild=Delete(T->rchild,x);
updateHeight(T);
T=adjust(T);
return T;
}
整体算法实现
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef struct AVLNode{
int data;
int height;
struct AVLNode *lchild;
struct AVLNode *rchild;
}*AVLTree;
AVLTree Empty(AVLTree &T)//删除树
{
if(T==NULL) return NULL;
Empty(T->lchild);
Empty(T->rchild);
delete T;
return NULL;
}
inline int Height(AVLTree T)//计算高度
{
if(T==NULL) return 0;
return T->height;
}
void updateHeight(AVLTree &T)
{
T->height=max(Height(T->lchild),Height(T->rchild))+1;
}
AVLTree LL_Rotation(AVLTree &T)//LL旋转
{
AVLTree temp=T->lchild;
T->lchild=temp->rchild;
temp->rchild=T;
updateHeight(T);//更新高度
updateHeight(temp);
return temp;
}
AVLTree RR_Rotation(AVLTree &T)//RR旋转
{
AVLTree temp=T->rchild;
T->rchild=temp->lchild;
temp->lchild=T;
updateHeight(T);//更新高度
updateHeight(temp);
return temp;
}
AVLTree LR_Rotation(AVLTree &T)//LR旋转
{
T->lchild=RR_Rotation(T->lchild);
return LL_Rotation(T);
}
AVLTree RL_Rotation(AVLTree &T)//RL旋转
{
T->rchild=LL_Rotation(T->rchild);
return RR_Rotation(T);
}
AVLTree Insert(AVLTree &T,int x)
{
if(T==NULL) //如果为空,创建新结点
{
T=new AVLNode;
T->lchild=T->rchild=NULL;
T->data=x;
T->height=1;
return T;
}
if(T->data==x) return T;//查找成功,什么也不做,查找失败时才插入
if(x<T->data)//插入到左子树
{
T->lchild=Insert(T->lchild,x);//注意插入后饭后结果挂接到T->lchild
if(Height(T->lchild)-Height(T->rchild)==2)//插入后看是否平衡,如果不平衡显然是插入的那一边高度大
{ //沿着高度大的那条路径判断
if(x<T->lchild->data)//判断是LL还是LR,即插入的是lchild节点的lchild 还是rchild
T=LL_Rotation(T);
else
T=LR_Rotation(T);
}
}
else//插入到右子树
{
T->rchild=Insert(T->rchild,x);
if(Height(T->rchild)-Height(T->lchild)==2)
{
if(x>T->rchild->data)
T=RR_Rotation(T);
else
T=RL_Rotation(T);
}
}
updateHeight(T);
return T;
}
AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
if(T==NULL) return NULL;
if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
{
if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
T=LL_Rotation(T);
else
T=LR_Rotation(T);
}
if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
{
if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
T=RR_Rotation(T);
else
T=RL_Rotation(T);
}
updateHeight(T);
return T;
}
AVLTree Delete(AVLTree &T,int x)
{
if(T==NULL) return NULL;
if(T->data==x)//如果找到删除节点
{
if(T->rchild==NULL)//如果该节点的右孩子为NULL,那么直接删除
{
AVLTree temp=T;
T=T->lchild;
delete temp;
}
else//否则,将其右子树的最左孩子作为这个节点,并且递归删除这个节点的值
{
AVLTree temp;
temp=T->rchild;
while(temp->lchild)
temp=temp->lchild;
T->data=temp->data;
T->rchild=Delete(T->rchild,T->data);
updateHeight(T);
}
return T;
}
if(T->data>x)//调节删除节点后可能涉及的节点
T->lchild=Delete(T->lchild,x);
if(T->data<x)
T->rchild=Delete(T->rchild,x);
updateHeight(T);
T=adjust(T);
return T;
}
void Preorder(AVLTree T)//前序遍历方便看树的结果
{
if(T==NULL) return ;
cout<<T->data<<"\t"<<T->height<<endl;
Preorder(T->lchild);
Preorder(T->rchild);
}
void Inorder(AVLTree T)//中序遍历方便看树的结果
{
if(T==NULL) return ;
Inorder(T->lchild);
cout<<T->data<<"\t"<<T->height<<endl;
Inorder(T->rchild);
}
void Posorder(AVLTree T)//后序遍历方便看树的结果
{
if(T==NULL) return ;
Posorder(T->lchild);
Posorder(T->rchild);
cout<<T->data<<"\t"<<T->height<<endl;
}
void show(AVLTree T)
{
Preorder(T);
cout<<endl;
Inorder(T);
cout<<endl;
Posorder(T);
}
AVLTree CreateAVL(AVLTree &T)
{
int n,x;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>x;
T=Insert(T,x);
}
return T;
}
int main()
{
int x;
AVLTree root=NULL;
root=Empty(root);
CreateAVL(root);
show(root);
cin>>x;
root=Delete(root,x);
show(root);
return 0;
}
输入:
6
25 18 5 10 15 17
5
输出:
15 3
10 2
5 1
18 2
17 1
25 1
5 1
10 2
15 3
17 1
18 2
25 1
5 1
10 2
17 1
25 1
18 2
15 3
15 3
10 1
18 2
17 1
25 1
10 1
15 3
17 1
18 2
25 1
10 1
17 1
25 1
18 2
15 3