AVL树是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持,而且必须保证树的深度为为O(logN)。最简单的方法是要求左右子树具有相同的高度。
另一种平衡条件是要求每个结点都必须要有相同高度的左子树和右子树,这个条件太严格,需要放宽条件。
一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。如下面两棵树,左边的树是AVL树,右边的树不是。
图1 图2
除去可能的插入操作外,所有的树操作都可以以时间O(logN)执行。当进行插入操作时,我们需要更新通向根节点路径上那些节点的所有平衡信息,而插入操作隐含着困难的原因在于,插入一个节点可能破坏AVL树的特性。例如将6插入到AVL树中会破坏关键字为8的节点的平衡条件。如果发生这种情况,那么就需要性质恢复以后才认为这一步插入完成。事实上,这总可以通过对树进行简单的修正来做到,称其为旋转。
在插入以后,只有那些从插入点到根节点的路径上的节点的平衡可能被改变,因为只有这些节点的子树可能发生变化。当我们沿着这条路径上行到根并更新平衡信息时,我们可以找到一个节点,它的新平衡破坏了AVL条件。我们将指出如何在第一个这样的节点重新平衡这棵树。
我们把必须重新平衡的节点叫做a,由于任意节点最多有两个儿子,因此高度不平衡时,a点的两棵子树的高度差2。这种不平衡可能户出现在下面四种情况中:
1.对a的左儿子的左子树进行一次插入
2.对a的左儿子的右子树进行一次插入
3.对a的右儿子的左子树进行一次插入
4.对a的右儿子的右子树进行一次插入
情形1和4关于a点镜像对称,2和3是关于a点的镜像对称。第一种情况是插入发生在“外边”的情况(即左-左的情况或右-右的情况),该情况通过对树的一次单旋转而完成调整。第二种情况是插入发生在“内部”的情形(即左-右或右-左的情况),该情况通过复杂些的双旋转来处理。
单旋转
下面显示单旋转如何调整情形1.旋转前的图在左边,而旋转后的图在右边。节点k2不满足AVL树的特性,因为它的左子树比右子树深2层。改图所描述的情况只是情形1的一种可能情况,在插入之前k2满足AVL树特性,但在插入之后这种特性被破坏了。子树X已经长出一层,这使得它比子树Z深出2层。
图3 调整情形1的单旋转
图4 单旋转修复情形4
为了使树恢复平衡,我们把X上移一层,并把Z下移一层。为此我们重新安排节点以形成一棵等价的树。让k1变成新的根,二叉树的性质告诉我们,在原树中k2>k1,于是在新树中k2变成了k1的右儿子,X和Z仍然分别是k1的左儿子和k2的右儿子。子树Y包含原树中介于k1和k2之间的那些节点,可以将它放在新树中k2的左儿子的位置上。
这样的操作只需一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上移动了一层,Y停留在原来的水平上,而Z下移一层。k2和k1不仅满足AVL要求,而且他们的子树都恰好在同一高度上。
下图显示将6插入左边原始的AVL树后节点8便不再平衡。于是,我们在7和8之间做一次单旋转,结果得到右边的树。
图5 插入6破坏了AVL特性,而后经过单旋转又将特性恢复
双旋转
如下图,对于情形2和3上面的做法无效。问题在于子树Y太深,单旋转没有减低它的深度。
图 6 单旋转不能修复情形2
因此我们用双旋转来解决,如下图:
图7 左-右双旋转修复情形2
为了重新得到平衡,不能再让k3作为根了,k3和k1之间的旋转解决不了问题,唯一的选择是把k2作为新的根。这迫使k1是k2的左儿子,k3是它的右儿子,从而完全确定了这四棵树的最终位置。容易看出,最后得到的树满足AVL树的特性,与单旋转的情形一样,把树的高度恢复到插入以前的水平。下图指出,对称情形3也可以通过双旋转得以修正。在这种情形下,其效果与先在a的儿子和孙子之间的旋转而后在a和它的新儿子之间的旋转的效果是相同的。
图8 右-左双旋转修复情形3
下面的例子中,这个右双旋转将涉及7,16和15.此时,k1是具有关键字7的节点,k3是具有关键字16的节点,而k2是具有关键字15的节点。子树A、B、C、D是空树。
旋转之前 旋转之后
下面插入14,它也需要一个双旋转。此时修复该树的双旋转还是右-左旋转,它将涉及6、15和7。
旋转之前 旋转之后
下面是相关代码:
#include<stdio.h>
#include<iostream.h>
using namespace std;
struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;
AvlTree MakeEmpty(AvlTree T);
Position Find(int x,AvlTree T);
Position FindMin(AvlTree T);
Position FindMax(AvlTree T);
AvlTree Insert(int x,AvlTree T);
AvlTree Delete(int x,AvlTree T);
int Retrieve(Position p);
struct AvlNode {
int data;
AvlTree left;
AvlTree Right;
int Height;
};
static int Height(Position p) {
if (p == NULL)return -1;
else
return p->Height;
}
static Position SingleRotateWithLeft(Position k2) {
Position k1;
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),k2->Height)+1;
return k1;
}
static Position DoubleRotateWithLeft(Position k3) {
k3->left = SingleRotateWithRight(k3->left);
return SingleRotateWithLeft(k3);
}
AvlTree Insert(int x, AvlTree T) {
if (T == NULL) {
T = (AvlTree)malloc(sizeof(struct AvlNode));
if (T == NULL)error("No space");
else {
T->data = x;
T->Height = 0;
T->left = T->Right = NULL;
}
}
else if (x < T->data) {
T->left = Insert(x,T->left);
if (Height(T->left) - Height(T->Right) == 2)
if (x < T->left->data)
T = SingleRotateWithLeft(T);
else
T = DoubleRotateWithLeft(T);
}
else if (x > T->data) {
T->Right = Insert(x, T->left);
if (Height(T->Right) - Height(T->left) == 2)
if (x > T->Right->data)
T = SingleRotateWithRight(T);
else
T = DoubleRotateWithRight(T);
}
}