数据结构与算法分析之AVL平衡树
2017-04-14 16:58
344 查看
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);//左左情形 else T = 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);//右右情形 else T = 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情形 else SingleRotateWithRight(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情形 else SingleRotateWithLeft(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; }
相关文章推荐
- 栈的链表实现_JAVA描述《数据结构与算法分析》
- 数据结构与算法分析课后习题第四章(3)
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(2) IntCell类
- 《数据结构与算法分析》5000字缩写(下)
- 数据结构与算法分析——快速排序
- 由《数据结构与算法分析-C语言描述》开始的数据结构与算法学习之旅
- 《数据结构与算法分析-C语言描述》第二章部分习题思路
- 数据结构与算法分析-第一章Java类(04)
- 《数据结构与算法分析》习题-----第二章(3)(关于list的题目)
- 数据结构与算法分析 java 版学习笔记 第一章
- 数据结构与算法分析之图3-7和图3-8——Vector类
- 数据结构与算法分析-用C语言实现栈(链表方式)
- 数据结构与算法分析:c语言版学习记录
- 《数据结构与算法分析》学习笔记(五)——队ADT
- 有bug的AVL平衡树
- 数据结构与算法分析笔记(6)——归并排序
- AVL平衡树的实现(2)
- 【技术-数据结构与算法分析】一致性哈希算法与Java实现
- 数据结构与算法分析 L6
- 《数据结构与算法分析——c语言描述》读后笔记 8