AVL平衡树
作者:老九—技术大黍
产品:查看原文
社交:知乎
公众号:老九学堂(新手有福利)
特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权
前言
在讲解算法时我先参考了O'Reilly出版的《Mastering Algorithms with C》、Wiki网站和严蔚敏的《数据结构(C语言版)》教材。因为,我们希望通过多方面专业的参考,希望学习算法时是非常严谨的。
AVL树概述
O'Reilly描述
在《Mastering Algorithms with C》一书中我没有找到关于AVL树概念的描述,在它的Trees章节的Description of Binary Trees有如下内容:
- Traversal Methods
- Preorder traversal
- Inorder traversal
- Postorder traversal
- Level-order traversal
- Tree Balancing
原文描述截图如下:
我这里不再参考翻译了,请大家自行百度理解,或者寻找其他方法领悟,因为最后我们参考老严的书给出一个自我认知的说法。
维基描述
我在这里也不做参考翻译了,列出来是帮助大家可以多维度理解AVL树正确的概念,希望能够帮助到大家,如果大家还有什么更专业的好书,希望大家也共享出来。
数据结构(C语言版)的解读
下面的描述是根据老严的数据结构教材,结合自己的理解来解读的说法,请大家不要当成标准,如果有不足之处请指正和补充。
平衡二叉树 (AVL树)的定义
它或者是一颗空树,或者具有以下性质的二叉树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。
平衡因子的定义
结点的左子树的深度减去右子树的深度,那么显然-1≤平衡因子≤1。
AVL树是根据它的发明者G.M.Adelson-Velsky和E.M.Landis命名的。它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于“二叉查找树”,AVL树中任何节点的两个子树的高度最大差别为1。
AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。
由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树(如下图所示)。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL树还是较优于红黑树。
AVL树的实现原理
平衡检测
AVL树的操作基本和二叉查找树一样,重点关注两个变化很大的操作:插入和删除。
对于一个二叉排序树来说,每次插入的元素只可能放在叶子结点上。所以只能影响某个子树是否平衡,对其他子树不会有任何的影响。在这种情况下,我们只需要根据搜索的路径,从孩子往祖先找,如果有不平衡的结点就可以被找到;如果一直到根结点都没有发现不平衡结点,则可以认为这次的插入操作没有造成树的不平衡。
在每次删除元素的时,我们同样可以根据搜索的路径,从孩子往祖先找,如果有不平衡的结点就可以被找到;如果一直到根结点都没有发现不平衡结点,则可以认为这次的插入操作没有造成树的不平衡。
这样的搜索过程,我们称作平衡检测。
重平衡
在平衡检测过程中,根据AVL树的定义,计算得到的平衡因子会出现两种情况:
- 如果平衡因子是0,1,-1这三个数的话,可以认定该节点是符合平衡树的定义的;
- 否则,该结点不平衡,需要重新平衡。
如果发现了某个不平衡的结点,那么就需要对该结点进行重平衡。实现重平衡的方法,是对该节点的子树进行旋转(rotation)。
旋转在理论上有两种情况:
- 一种称为左旋转(关于X结点的左旋转);
- 一种称为右旋转(关于Y结点的右旋转)。
在真实的情况下,我们会遇到四种可能出现的情况:
图中的N,X,Y,Z所代表的含义如下:
-
N代表新插入的元素结点;
-
Z代表从插入元素的位置开始,逆插入元素时的访问结点的顺序,从孩子向祖先方向开始检测平衡因子时发现的第一个不平衡结点;
-
Y代表插入元素时的访问结点路径上,访问z结点之后访问的结点;
-
X代表插入元素时的访问结点路径上,访问y结点之后访问的结点。
这四种情况,都可以通过一次或者两次的旋转,来使得不平衡的结点变平衡。其中,第1和第4种情况可以通过单次旋转重新平衡,第2和第3种情况可以通过双次旋转重新平衡。
单次旋转(Singly Rotation)
单次旋转得到重新平衡的子树的示意图如下所示,需要注意的是分清对应的结点。
双次旋转(Doubly Rotation)
双次旋转顾名思义,就是要进行两次旋转来使子树重新平衡,流程如下图示:
- 在第1种情况下,需要先对y结点进行一次左旋转,然后再对z结点进行一次右旋转;
- 在第3种情况下,需要先对y结点进行一次右旋转,然后再对z结点进行一次左旋转。
AVL树C语言实现
AVL树的结点
struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;
typedef int ElementType;
struct AvlNode
{
ElementType Element;
AvlTree Left;
AvlTree Right;
int Height;
};
复制代码
AVL树的数据结构
AvlTree MakeEmpty(AvlTree T);
Position Find(ElementType X, AvlTree T);
Position FindMin(AvlTree T);
Position FindMax(AvlTree T);
AvlTree Insert(ElementType X, AvlTree T);
ElementType Retrieve(Position P);
static int Height(Position P);
static int Max(int, int);
static Position SingleRotateWithLeft(Position P);
static Position StingleRotateWithRight(Position P);
static Position DoubleRotateWithLeft(Position P);
static Position DoubleRotateWithRight(Position P);
AvlTree Delete(Position P, AvlTree T);
复制代码
左旋
static Position SingleRotateWithLeft(Position P)
{
Position K1;
K1 = P->Left;
P->Left = K1->Right;
K1->Right = P;
P->Height = Max(Height(P->Left), Height(P->Right)) + 1;
K1->Height = Max(Height(K1->Left), Height(P->Height)) + 1;
return K1;
}
复制代码
右旋
static Position StingleRotateWithRight(Position P)
{
Position K1;
K1 = P->Right;
P->Right = K1->Left;
K1->Left = P;
P->Height = Max(Height(P->Left), Height(P->Right)) + 1;
K1->Height = Max(Height(K1->Left), Height(P->Height)) + 1;
return K1;
}
复制代码
双次旋转
static Position DoubleRotateWithLeft(Position P)
{
P->Left = StingleRotateWithRight(P->Left);
return SingleRotateWithLeft(P);
}
static Position DoubleRotateWithRight(Position P)
{
P->Right = StingleRotateWithRight(P->Right);
return StingleRotateWithRight(P);
}
复制代码
插入元素(Insert)
AvlTree Insert(ElementType X, AvlTree T)
{
if (T == NULL)
{
T = malloc(sizeof(struct AvlNode));
if (T == NULL)
{
Error("Error: out of space!!");
}
else
{
T->Element = X;
T->Left = T->Right = NULL;
T->Height = 0;
}
}
else if (X < T->Element)
{
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
{
T->Right = Insert(X, T->Right);
if (Height(T->Right) - Height(T->Right) == 2)
{
if (X < T->Right->Element)
{
T = StingleRotateWithRight(T);
}
else
{
T = DoubleRotateWithRight(T);
}
}
}
T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
return T;
}
复制代码
删除元素(Delete)
AvlTree Delete(Position P, AvlTree T)
{
Position PMix;
Position Tmp;
if (T != NULL)
{
if (T->Element > P->Left)
{
T->Left = Delete(P, T->Left);
T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
if (Height(T->Right) - Height(T->Left) == 2)
{
if (T->Right->Element < P->Element)
{
return StingleRotateWithRight(T);
}
else
{
return DoubleRotateWithRight(T);
}
}
}
else if (T->Element < P->Left)
{
T->Right = Delete(P, T->Right);
T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
if (Height(T->Left) - Height(T->Right) == 2)
{
if (T->Left->Element > P->Element)
{
return SingleRotateWithLeft(T);
}
else
{
return DoubleRotateWithLeft(T);
}
}
}
else
{
if (T->Right != NULL && T->Left != NULL)
{
if (Height(T->Right) > Height(T->Left))
{
PMix = FindMin(T->Right);
T->Element = PMix->Element;
T->Right = Delete(PMix,T->Right);
}
else
{
PMix = FindMax(T->Left);
T->Element = PMix->Element;
T->Left = Delete(PMix,T->Left);
}
T->Height = Max(Height(T->Right), Height(T->Left)) + 1;
}
else
{
Tmp = P;
T = P->Right ? P->Right: P->Left;
free(Tmp);
}
}
}
return T;
}
复制代码
搜索(Search)
Position Find(ElementType X, AvlTree T)
{
if (T == NULL)
{
return NULL;
}
else if(X < T->Element)
{
return Find(X, T->Left);
}
else if (X > T->Element)
{
return Find(X, T->Right);
}
else
{
return T;
}
}
Position FindMax(AvlTree T)
{
Position pL = T;
Position pR = T;
if(T->Left != NULL)
{
pL = findMax(T->Left);
}
if(T->Right != NULL)
{
pR = findMax(T->Right);
}
return pL->Height > pR->Height ? pL : pR;
}
Position FindMin(AvlTree T)
{
Position pL = T;
Position pR = T;
if(T->Left != NULL)
{
pL = findMin(T->Left);
}
if(T->Right != NULL)
{
pR = findMin(T->Right);
}
return pL->Height < pR->Height ? pL : pR;
}
复制代码
辅助函数
void MakeEmpty(AvlTree T)
{
*T = (AvlTree)malloc(sizeof(AvlNode));
(*T).Left = NULL;
(*T).Right = NULL;
}
static int Height(Position P)
{
return P->Height;
}
ElementType Retrieve(Position P)
{
return P->Element;
}
static int Max(int x, int y)
{
return x > y ? x : y;
}
总结
在《Essential Algorithms》一书这样描述算法的特点:
我们这个C语言实现肯定不是最好的,这里只是抛砖引玉,希望与给大家在学习算法时提供思路和帮助。如果有不足之处,请大家指正和补充。
最后
感觉有用的同学,请记得给大黍❤️关注+点赞+收藏+评论+转发❤️
作者:老九学堂—技术大黍
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。