数据结构与算法分析之AVL平衡树

来源:互联网 发布:ubuntu pyqt 安装 编辑:程序博客网 时间:2024/06/06 02:33


AVL树定义:

 AVL(Adelson-Velskii和Landis)树是带有平衡条件的二叉查找树。平衡条件是:其每个节点的左子树和右子树的高度差最多等于1的二叉查找树。


AVL ADT操作:

Find操作:

AVL树的查找操作跟非AVL二叉查找树的查找是一样的,查找最小值沿左子树寻找,查找最大值沿右子树查找


Insert操作:

AVL树的插入操作除了新建节点并插入到树中,还要为保持树的平衡性做调整的动作。通常通过围绕不平衡的点进行旋转来保持平衡性。


对于AVL树的插入操作破坏的是从插入点到根节点路径上的节点的平衡性。因为只有这些节点的子树由于插入导致平衡性的变化。所以,在插入操作时,

需要沿着插入点到根节点的路径上更新每个节点的新高度,并检查节点左右子树的高度差。当发现节点左右子树的高度差大于等于2时,说明此节点是一个

不平衡的节点,需要通过单旋转或者双旋转来恢复树的平衡性。第一个不平衡的节点也是所有不平衡节点中最深的那个。

假设节点a是一个不平衡的节点,在节点a处出现不平衡的情形有四种:

1.对a的左儿子的左子树进行一次插入(左-左)

2.对a的左儿子的右子树进行一次插入(左-右)

3.对a的右儿子的左子树进行一次插入(右-左)

4.对a的右儿子的右子树进行一次插入(右-右)

其中情形1和4是关于a点的镜像对称,情形2和3是关于a点的镜像对称。所以,理论上可以分为两大类,一类是插入发生在"外边"的情况(即左-左或右-右),

此类情形可以通过围绕a点的单旋转来恢复平衡性;第二类是插入操作发生在内部的情形(即右-左或左-右),此类情形可以通过围绕a点的双旋转来调整。


AVL树的单旋转:

1.左-左情形的单旋转:

左左情形的单旋转又可以细分成两类,一种是a节点的左儿子没有右儿子,另一种是a节点的左儿子没有右儿子。所以编码实现时要适应这两种情况,

避免造成子树的丢失。如下图所示


左左情形中,要围绕不平衡节点(K1)和K1->Left进行旋转,当K1->Left(假定称为K2)有右儿子时,旋转前值的排序肯定是K1>K2->Right>K2。所以使K1->Left=K2->Right

来避免K2的右子树丢失,并最终返回K2的新节点。值得注意的是调整后的新节点K2与其新的父节点(上图中的5,4)是怎样保持链接的,由于插入操作的实现是递归方式的,先递归向下寻找可插入的点,插入完成后会递归返回,在返回时会检查节点的平衡性,如果不平衡做旋转操作。所以调整后新节点K2于其新的父节点的链接是通过对旋转函数的调用来实现的,如:T = SingleRotateWithLeft( T );或K3->Left = SingleRotateWithRight( K3->Left );等。左单旋转code如下所示:

static Position SingleRotateWithLeft(Position K1){Position K2;K2 = K1->Left;K1->Left = K2->Right;K2->Right = K1;K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;K2->Height = Max(Height(K2->Left), K1->Height) + 1;//或者K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;return K2;}


2.右右情形的单旋转:

右右情形和左左情形类似,也可以细分为两种情况,可以跟左左情况对比来看。如下图所示:

右右单旋转code如下所示:

static Position SingleRotateWithRight(Position K1){Position K2;K2 = K1->Right;K1->Right = K2->Left;K2->Left = K1;K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;K2->Height = Max(Height(K2->Right), K1->Height) + 1;return K2;}

左右情形:

左右情形即在节点K3的左儿子的右子树上插入造成的。可以通过两步单旋转来完成所谓的双旋转操作:先将K3的左儿子进行一次右单旋转,旋转后的结果是K3左儿子的左子树更深,所以再通过一次围绕K3的左单旋转最终修复K3的不平衡性。左右情形的双旋转如下图所示:


左右双旋转的code如下所示:

static Position DoubleRotateWithLeft(Position K3){K3->Left = SingleRotateWithRight(K3->Left);return SingleRotateWithLeft(K3);}

左情形的双旋转:

和左右情形的双旋转类似,右左情形是在节点K3的右儿子的左子树上插入造成不平衡性。也可以通过两次单旋转来完成所谓的双旋转操作:先将K3的右儿子进行一次左单旋转,然后将K3再进行一次右单旋转。右左情形的双旋转示意图如下:

右左情形的双旋转code实现如下:

static Position DoubleRotateWithRight(Position K3){K3->Right = SingleRotateWithLeft(K3->Right);return SingleRotateWithRight(K3);}

Insert操作code实现如下:

AvlTree Insert(ElementType X, AvlTree T){if (T == NULL) {/* 找到插入点 */T = malloc(sizeof(struct AvlNode));if (T == NULL) {printf("Out of space!!!\n");exit(1);} else {T->Element = X;T->Height = 0;T->Left = T->Right = NULL;}} else if (X < T->Element) {//需要在T的左子树上插入T->Left = Insert(X, T->Left);/* 插入完成返回时检查节点的平衡性 *//* 因为是在左子树上插入,所以左子树高度在前减右子树高度 */if (Height(T->Left) - Height(T->Right) == 2)if (X < T->Left->Element)T = SingleRotateWithLeft(T);//左左情形elseT = DoubleRotateWithLeft(T);//左右情形} else if (X > T->Element) {//需要在T的右子树插入T->Right = Insert(X, T->Right);if (Height(T->Right) - Height(T->Left) == 2)if (X > T->Right->Element)T = SingleRotateWithRight(T);//右右情形elseT = DoubleRotateWithRight(T);//右左情形}//更新T节点的高度T->Height = Max(Height(T->Left), Height(T->Right)) + 1;return T;//最终返回的是新的根节点}


删除操作

节点的删除操作也会造成不平衡,根据插入操作分析的4种不平衡性,删除操作也会造成这四种不平衡性,即LL,LR,RR,RL。相应的也要通过单旋转或者双旋转来修复不平衡性。可以把删除造成的不平衡的4种情形看作插入时造成的4种不平衡情形的镜像。

删除时的策略还是使用跟普通二叉查找树一样,将删除节点的右子树中最小的那个节点替换删除节点,并递归的删除替换节点,直到替换节点只有左子树。

删除节点后,开始递归的在删除节点到根节点的路径上检查每个节点的平衡性,若不平衡则调整。4种不平衡的情形,在code处理时可分为两大类并检查。同样假设a节点不平衡。

1.删除的节点在a节点的左子树

2.删除的节点在a节点的右子树

进一步细分上面两种情形:

1.删除的节点在a节点的左子树:

此种情形会减小a节点左子树的高度,此时根据a节点的左儿子的左右子树高度的差异,可以确定删除操作对应的不平衡性是LL还是LR。

1.1如果a节点的左儿子的左子树比右子树高,则属于LL类型,只需要将a节点进行左单旋转即可。

1.2如果a节点的左儿子的左子树比右子树低,则属于LR类型,只需要将a节点进行左双旋转即可。


2.删除的节点在a节点的右子树:

此种情形会减小a节点的右子树高度,根据a节点右儿子的左右子树高度大小,可以确定删除操作造成的不平衡性属于RR还是RL。

2.1如果a节点的右儿子的左子树比右子树低,则属于RR类型,只需要将a节点进行右单旋转即可。

2.2如果a节点的右儿子的左子树比右子树高,则属于RL类型,只需要将a节点进行右双旋转即可。

无图无真相,还是上图吧,下图是左子树删除的情形


下图是右子树删除的情形:


删除操作code实现

AvlTree Delete(ElementType X, AvlTree T){AvlTree TmpCell = NULL;if (T == NULL)return NULL;else if (X < T->Element) {/* 左子树删除的情形 */T->Left = Delete(X, T->Left);/* 在左子树删除,会造成右高左低,所以右高度减去左高度 */if (Height(T->Right) - Height(T->Left) == 2) {//T节点失去平衡if (Height(T->Right->Left) > Height(T->Right->Right))DoubleRotateWithRight(T);//RL情形elseSingleRotateWithRight(T);//RR情形}} else if (X > T->Element) {/* 删除的节点在T的右子树 */T->Right = Delete(X, T->Right);/* 在右子树删除,会造成左高右低,所以左高度减右高度 */if (Height(T->Left) - Height(T->Right) == 2) {//T节点失去平衡if (Height(T->Left->Right) > Height(T->Left->Left))DoubleRotateWithLeft(T);//LR情形elseSingleRotateWithLeft(T);//LL情形}} else if (T->Left && T->Right) {//找到了要删除的节点T, 并使用T的右子树中最小的那个替换TmpCell = FindMin(T->Right);T->Element = TmpCell->Element;T->Right = Delete(T->Element, T->Right);} else {//找到最后一个替换的节点,其实该节点肯定没有左儿子AvlTree Position = T;if (T->Left)T = T->Left;if (T->Right)T = T->Right;free(Position);}if (T)T->Height = Max(Height(T->Left), Height(T->Right)) + 1;return T;}


1 0