AVL树这样一棵搜索二叉树,它的左右子树的深度之差不超过1。因此,他是带有条件的搜索二叉树。这个条件保证了AVL树的深度是O(log n).最简单的想法是左右两棵子树保持相同的高度。但是这种条件过于苛刻,难以使用。AVL只要求深度之差不超过1。AVL解决了二叉搜索树带来的不平衡问题。但是要求变成了我们必须在每次操作后进行调整,以使得AVL树保持平衡。另一种较新的方法是放弃平衡条件,允许树有任意的深度,但是在每次操作后要进行调整,以使得后面的操作效率更高。有一种这样的树称之为伸展树。
在AVL树的每一个节点中保留其高度信息是必须的。在一棵高度为h的AVL树中,最少节点数S(h) = S(h-1)+S(h-2)+1。对于h为0时,S(h)=1;h为2时,S(h)=2。这个函数与斐波那契数列密切相关。
如果一个插入操作破坏了AVL树的平衡条件,那么这插入后形成的左右子树的高度之差最大是2。因此只会出现4种情形。
- 对节点的左儿子的左子树进行一次插入;
- 对节点的右儿子的右子树进行一次插入;
- 对节点的左儿子的右子树进行一次插入;
- 对节点的右儿子的左子树进行一次插入;
上述4种情形之中,1和2是镜像对称的,3和4是镜像对称的。所以看起来只有两种情况,但是对于编程实现而言还是4种情形。对于1,2这样的插入操作,可以通过单旋转来完成;对于3,4这样的插入操作,需要通过稍微复杂的双旋转操作来完成。
单旋转:插入之前的高度是和插入之后的高度是保持一致的。插入操作不仅仅是修改局部的变化,树的其余部分也必须知道这个变化。插入可能会导致多个节点的平衡被破坏,但是我们只需要修复距离这个插入节点最近的被破坏平衡的节点。
双旋转:对于1,2两种情形,如果采用单旋转来做,那么会发现,单旋转并没有降低树的深度。它还是不平衡的。双旋转解决了这个问题,它等价于做了两次单旋转。
插入以后树可能还是平衡的,这时候不需要调整树的结构,但是我们仍然需要更新树的深度。
代码实现如下:
AVL树的ADT:(avl.h文件)
#ifndef AVLTREE
#define AVLTREE
#include<stdio.h>
#include<stdlib.h>
#define MAX(a,b) ((a) > (b) ? (a) : (b))
typedef int ELementType;
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode {
ELementType Data; /* 结点数据 */
AVLTree Left; /* 指向左子树 */
AVLTree Right; /* 指向右子树 */
int Height; /* 树高 */
};
AVLTree Insert(ELementType x, AVLTree T);
int Height(Position P);
AVLTree LLRotate(Position K);
AVLTree LRRotate(Position K);
AVLTree RRRotate(Position K);
AVLTree RLRotate(Position K);
void InorderTraversal(AVLTree T);
#endif // AVLTREE
main.cpp文件:(测试流程)在二叉搜索树中已经实现了绝大多数的查找树操作,例如前序遍历,中序遍历,后序遍历,求最大,最小值等。在AVL树中就不一一实现了,只就插入做了实现,我对删除采用的是懒惰删除法。在此不在说明。只测试一下AVL树的深度是不是O(log n)以及中序遍历输出是不是有序的。这些足以证明它就是我们要求的AVL树。
#include"avl.h"
int main()
{
AVLTree avltree = NULL;
printf("AVL树元素插入顺序如下:");
for (int i = 5; i > 0 ; i--)
{
printf("%d ",i);
avltree = Insert(i, avltree);
}
for (int i = 6; i < 10; i++)
{
printf("%d ", i);
avltree = Insert(i, avltree);
}
printf("
中序遍历输出:");
InorderTraversal(avltree);
printf("
AVL树的高度:%d
",Height(avltree));
system("pause");
return 0;
}
alv.cpp文件
#include "avl.h"
AVLTree Insert(ELementType x, AVLTree T)
{
if (NULL == T) //T为空,创建树
{
T = (AVLTree)malloc(sizeof(struct AVLNode));
if (NULL == T)
{
printf("创建树失败!");
}
else
{
T->Data = x;
T->Height = 0; //树根深度为0
T->Left = NULL;
T->Right = NULL;
}
}
else
{
if (x < T->Data) //x小于根节点数据
{
T->Left = Insert(x, T->Left); //递归插入左子树
if (Height(T->Left) - Height(T->Right) == 2) //AVL树不平衡了,调整一下
{
if (x < T->Left->Data)
{
T = LLRotate(T); //插入到左子树的左子树
}
else
{
T = LRRotate(T); //插入到左子树的右子树
}
}
}
else if (x > T->Data)
{
T->Right = Insert(x, T->Right); //递归插入右子树
if (Height(T->Right) - Height(T->Left) == 2) //AVL树不平衡了,调整一下
{
if (x > T->Right->Data)
{
T = RRRotate(T); //插入右子树的右子树
}
else
{
T = RLRotate(T); //插入右子树的左子树
}
}
}
else
{
//x在这棵AVL树中,我们什么都不做,当然,我们也可以重新设计AVL树的ADT。
//使得ADT可以保存数据出现的次数,如果有相同数据插入,我们就使得次数加1。
//这样的做法为我们在AVL树中做一个删除也提供了一种方式,即:懒惰删除。
//我们并不将这个节点从树中删除,而只是去更改数据出现的次数减1。
//这样我们就不需要做多余的操作去调整可能出现的不平衡状态。
//这种做法在有重复关键字时很好用。并且这种做法只会导致树的高度略微上升。
//这样的做法使得删除变得非常快,并且如果被删除的元素重新插入,那么省去
//了重新申请空间的开销。
}
T->Height = MAX(Height(T->Left), Height(T->Right)) + 1; //更新树的高度
}
return T;
}
int Height(Position P) //AVL树的节点保存了高度这一信息,直接返回即可
{
if (NULL == P)
{
return 0; //节点为空,定义高度为0
}
else
{
return P->Height;
}
}
AVLTree LLRotate(Position K)
{
Position M;
//调整AVL树
M = K->Left;
K->Left = K->Left->Right;
M->Right = K;
K->Height = MAX(Height(K->Left), Height(K->Right)) + 1; //更新右子树的高度
M->Height = MAX(Height(M->Left), Height(M->Right)) + 1; //更新新的根节点高度
//M->Height = MAX(Height(M->Left), K->Height) + 1; Height(M-Right) == K->Height
return M;
}
AVLTree LRRotate(Position K)
{
//这里设计的很巧妙,通过先旋转K的左子树。然后在整体旋转K。
K->Left = RRRotate(K->Left);
return LLRotate(K);
}
AVLTree RRRotate(Position K)
{
Position M;
//调整AVL树
M = K->Right;
K->Right = M->Left;
M->Left = K;
K->Height = MAX(Height(K->Left), Height(K->Right)) + 1; //更新左子树的高度
M->Height = MAX(Height(M->Right), Height(M->Left)) + 1; //更新新的根节点高度
//M->Height = MAX(Height(M->Left), K->Height) + 1; Height(M-Right) == K->Height
return M;
}
AVLTree RLRotate(Position K)
{
K->Right = LLRotate(K->Right);
return RRRotate(K);
}
void InorderTraversal(AVLTree T)
{
if (T)
{
InorderTraversal(T->Left);
printf("%d ", T->Data);
InorderTraversal(T->Right);
}
}