目录
AVL树的概念
AVL树是一种自平衡的平衡二叉查找树,它是一种高效的数据结构,可以在插入和删除节点时保持树的平衡,从而保证树的操作时间复杂度能够达到O(log n)。
所谓平衡就是保证每个节点的左右子树的高度差不能超过1。例如
对AVL树进行插入或删除操作时,可能会导致某些节点的高度差超过1,即不再平衡。这时就需要进行旋转操作来恢复AVL树的平衡。所以,AVL树的核心内容就是旋转。
旋转
一般来讲,我们将旋转类型分为两大类。左-左、右-右类型的为单旋转,左-右、右-左类型的为双旋转。下面是这四种旋转的操作方式。
单旋转
具体的图解如下:
非原创图,原图出处:CSDN 喵了个呜s
双旋转
具体图解如下:
非原创图,原图出处:CSDN 喵了个呜s
小结
单旋转的过程可以概况为如下的三个步骤(以下图为模型,以单左旋为例):
1、让k2原本指向k1的指针现在指向k1内侧的节点
2、让k1原本指向内侧的指针现在指向k2
3、让原来指向k2的指针现在改为指向k1,并更新各节点的高度
双旋转其实就是两次单旋转,先将内侧的节点通过单旋转“移出来”到外侧。然后再用一次单旋转,最终成为我们想要的平衡状态。
AVL树的视频演示
AVL树动画演示
也可以自己尝试:
AVL Tree Visualzation (usfca.edu)https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
代码示例
#include<stdio.h>
/*max的头文件stdlib.h */
#include<stdlib.h>
typedef char ElementType;
typedef struct AvlTreeNode
{
ElementType Data; //暂定节点内容只有单个字符
int Height;
struct AvlTreeNode* Left;
struct AvlTreeNode* Right;
}AvlTree;
int Height(AvlTree* Node)
{
if (Node == NULL)
return -1;
return Node->Height;
}
AvlTree* SingleLeftRotate(AvlTree* k2) //单左旋,LL旋转(k2的由来详见数据结构与算法分析P94)
{
//旋转节点
AvlTree* k1 = k2->Left;
k2->Left = k1->Right;
k1->Right = k2;
//更新高度
k2->Height = max(Height(k2->Left), Height(k2->Right)) + 1;
k1->Height = max(Height(k1->Left), Height(k1->Right)) + 1;
//返回
return k1;
}
AvlTree* SingleRightRotate(AvlTree* k2) //单右旋,RR旋转(k2的由来详见数据结构与算法分析P94)
{
//旋转节点
AvlTree* k1 = k2->Right;
k2->Right = k1->Left;
k1->Left = k2;
//更新高度
k2->Height = max(Height(k2->Left), Height(k2->Right)) + 1;
k1->Height = max(Height(k1->Left), Height(k1->Right)) + 1;
//返回
return k1;
}
AvlTree* DoubleLRRotate(AvlTree* k3) //双左右旋,LR旋转(k3的由来详见数据结构与算法分析P95)
{
/*一次双旋转等于两次单旋转。
可以理解为先将需要旋转的移至同一方向(即左左、右右这种),然后再用单旋转的方式处理*/
k3->Left = SingleRightRotate(k3->Left);
return SingleLeftRotate(k3);
}
AvlTree* DoubleRLRotate(AvlTree* k3) //双右左旋,RL旋转(k3的由来详见数据结构与算法分析P95)
{
/*一次双旋转等于两次单旋转。
可以理解为先将需要旋转的移至同一方向(即左左、右右这种),然后再用单旋转的方式处理*/
k3->Right = SingleLeftRotate(k3->Left);
return SingleRightRotate(k3);
}
AvlTree* InsertElement(AvlTree** root, ElementType data)
{
//走到空节点(即插入位置),执行插入操作
if ((*root) == NULL)
{
//开辟空间并赋值
*root = (AvlTree*)calloc(1, sizeof(AvlTree));
//成功开辟空间
if ((*root) != NULL)
(*root)->Data = data;
//开辟空间失败
else
puts("heap area is full!");
}
//根节点无内容(值为0),说明为空树,则直接将data插入到根节点
else if ((*root)->Data == 0)
{
(*root)->Data = data;
}
//data比节点内容小,在左侧插入
else if (data < (*root)->Data)
{
//向左走,并更新左子树内容
(*root)->Left = InsertElement(&(*root)->Left, data);
//判断是否需要旋转
if (Height((*root)->Left) - Height((*root)->Right) == 2)
{
//如果data小于左子树的data,说明是data插入到左子树的左节点,符合单旋转的情况(3个节点都在左侧)
if (data < (*root)->Left->Data)
*root = SingleLeftRotate(*root); //左侧单旋转,并更新节点内容
//如果data不小于左子树的data,说明是插入到左子树的右节点,是LR型的双旋转情况
else
*root = DoubleLRRotate(*root); //左右双旋转,并更新节点内容
}
}
//data比节点内容大,在右侧插入
else if (data > (*root)->Data)
{
//向右走,并更新左子树内容
(*root)->Right = InsertElement(&(*root)->Right, data);
//判断是否旋转
if (Height((*root)->Right) - Height((*root)->Left) == 2)
{
//如果data大于右子树的data,说明是data插入到右子树的右节点,符合单旋转的情况(3个节点都在右侧)
if (data > (*root)->Right->Data)
*root = SingleRightRotate(*root); //右侧单旋转,并更新节点内容
//如果data不大于右子树的data,说明是插入到右子树的左节点,是RL型的双旋转情况
else
*root = DoubleRLRotate(*root);
}
}
//data与节点内容值相同
else { /*暂定如果插入的元素内容相同,则什么都不做*/ }
//最后更新节点高度
(*root)->Height = max(Height((*root)->Left), Height((*root)->Right)) + 1;
//返回
return *root;
}
int main()
{
AvlTree* root = (AvlTree*)calloc(1, sizeof(AvlTree));
InsertElement(&root, 'n');
InsertElement(&root, 'g');
InsertElement(&root, 'u');
InsertElement(&root, 'e');
InsertElement(&root, 'd');
InsertElement(&root, 'f');
InsertElement(&root, 'h');
InsertElement(&root, 'a');
InsertElement(&root, 'b');
InsertElement(&root, 'c');
InsertElement(&root, 'i');
InsertElement(&root, 'j');
InsertElement(&root, 'k');
InsertElement(&root, 'z');
puts("insert test over.");
puts("*************************************");
return 0;
}