前面介绍了二叉查找树,二叉查找树的平均时间复杂度为O(logN),是相当低的,因此是一种非常优异的模型。但是这只是对一个随机输入的二叉查找树有效(平衡的二叉树)。当我们进行一定次数的插入(或删除)后,可能会导致二叉树不平衡,甚至退化成链表,这样会重新使时间复杂度变为O(logN).为此,引入AVL树。
AVL树是Adelson-Velskii和Landis于1962年发明的自平衡二叉树。在一个二叉树中引入平衡条件,最简单的当然是每个节点的左子树和右子树的高度一样。但是这个条件太强了,因此AVL树引入例如下面的一个限制:
“每个节点的左子树的高度和右子树的高度的差的绝对值不超过1”(空树的高度定义为-1)
1)旋转
对于AVL树,除了插入结点外,所有的操作都可以在O(logN)的时间内完成。对于插入节点的情况,插入可能会破坏AVL树的约束条件,而我们可以通过对树的适当旋转来修正。
对一个AVL树插入一个节点,分4中情况
(1)对左子树插入左子节点
(2)对右子树插入左子节点
(3)对左子树插入右子节点
(4)对右子树插入右子节点
其中(1)和(4)是镜面对称情况,(2)和(3)是镜面对称情况,因此他们相应的旋转情况是一致的,因此这里只分析(1)和(2)的情况.
单旋转:
对于(1)的情况适用于单旋转,如上面的图所示,插入节点5,就是在左子树插入左子节点,这个结果直接导致了不符合AVL的约束条件,对于节点7来说,它已经失去了平衡了,节点4,8的失衡也是由于节点7的失衡,因此为了恢复平衡,我们要把节点7的左子树提高一个level,把节点7的右子树降低一个level,因此可以得到上图的旋转后的结果
要旋转之前首先要清晰到底应该是哪个节点导致的不平衡。
对于编程实现来说,旋转本质上是指针的一些改变
从上图可以看到旋转后的树与旋转前的树的高度是一样的。这其实很好理解,正因为增加了一个节点导致树变深了,进而导致了unbalance,所以通过旋转必然要减掉这个深度,否则一样的unbalance.
双旋转:
对应于(2)的情况,单旋转已经不能解决问题了。因此要引入两次但旋转,即双旋转来解决
在这个例子中根节点8是平衡的,不平衡的是节点9,因此要对节点9做双旋转。
另一个例子:
这个例子中根节点6不平衡,因此要对根节点做旋转。
2)编程实现:
AVL树的定义:
struct Avltree;
typedef struct AvlNode *AvlTree;
typedef AvlTree Position;
struct AvlNode
{
ElementType Element;
AvlTree Left;
AvlTree Right;
int Height;
};
// calculate height
static int Height(Position P)
{
if(P==NULL)
return -1;
else
return P->Height;
}
插入节点:
AvlTree Insert(Element X,AvlTree T)
{
if(T==NULL)
{
T = malloc(sizeof(struct AvlNode));
if(T==NULL)
Error("Out of Space!");
T->Left = T->Right = NULL;
T->Element = X;
T->Height = 0;
}
else if(X<T->Element)
{
T->Left = Insert(X,T->Left);
//if X==T->Left,the next if condition is false,it will do nothing
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->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->Height = Max(Height(T->Left),Height(T->Right))+1;
return T;
}
左节点单旋转:
Position SingleRotateWithLeft(Position P1)
{
Position P2;
P2 = P1->Left;
P1->Left = P2->Right;
P2->Right = P1;
P1->Height = Max(Height(P1->Left),Height(P1->Right));
P2->Height = Max(Height(P2->Left),P1->Height);
return P2; //new root
}
右节点单循环:(与上面的是镜面对称)
Position SingleRotateWithRight(Position P1)
{
Position P2;
P2 = P1->Right;
P1->Right = P2->Left;
P2->Left = P1;
P1->Height = Max(Height(P1->Left),Height(P1->Right));
P2->Height = Max(Height(P2->Right),P1->Height);
return P2; //new root
}
下面是双旋转:其实双旋转的编程实现比较简单,就是把单旋转做两遍,一次左,一次右。
左节点双旋转:
Position DoubleRotateWithLeft(Position P3)
{
P3->Left = SingleRotateWithRight(P3->Left);
return SingleRotateWithLeft(P3);
}
右节点双旋转:
Position DoubleRotateWithRight(Position P3)
{
P3->Right = SingleRotateWithLeft(P3->Right);
return SingleRotateWithRight(P3);
}
这两个双旋转是镜面对称的。
3)总结:
AVL树是平衡的二叉树,它有限制:“每个节点的左子树的高度和右子树的高度的差的绝对值不超过1”(空树的高度定义为-1)。对AVL树插入值很有可能导致二叉树不再平衡,因此可以通过旋转来消除这种不平衡。旋转分单旋转和双旋转,分别对应于不同的插入情况。最多只需要双旋转。