AVL树
AVL(Adelson-Velskii and Landis)树是带有平衡条件的二叉查找树。必须保证树的深度是。
最简单的想法是要求左右子树具有相同的高度。
另一种平衡条件是要求每个节点都必须要有相同高度的左子树和右子树。如果空子树的高度定义为-1(通常就是这么定义的),那么只有具有个节点的理想平衡树(perfectly balanced tree)满足这个条件。因此虽然这种平衡条件保证了树的深度小,但是它太严格,难以使用需要放宽条件。
一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树(空树的高度定义为-1)。可以证明,大致上讲,一个AVL树的高度最多为,但是实际上的高度只比
稍微多一些。因此,除去可能的插入外(我们将假设懒惰删除),所有的树操作都可以以时间
执行。当进行插入操作时,我们需要更新通向根节点路径上那些节点的所有平衡信息,而插入操作隐含着的困难是插入一个节点困难会破坏AVL树的特性。如果发生这种情况,那么就要把性质恢复以后才认为这一步插入完成。事实上,这总可以通过对树进行简单的修正来做到,我们称其为旋转(rotation)
在插入以后,只有那些从插入点到根节点的路径上的节点的平衡可能被改变,因为只有这些点的子树可能发生变化。当我们沿着这条路径上行到根并更新平衡信息时,我们可以找到一个节点,它的新平衡破坏了AVL条件。
让我们把必须重新平衡的节点叫作。高度不平衡时,
的两棵子树高度差2。容易看出,这种不平衡可能出现在下面四种情况中:
1.对的左儿子的左子树进行一次插入
2.对的左儿子的右子树进行一次插入
3.对的右儿子的左子树进行一次插入
4.对的右儿子的右子树进行一次插入
情形1和4是关于点的镜像对称,而情形2和3是关于
点的镜像对称。因此理论上只有两种情况,当然从编程的角度来看还是四种情形。
第一种情形是插入发生在“外边”的情形(左-左 或 右-右),该情形通过对树的一次单旋转(single rotation)而完成调整。
第二种情形是插入发生在“内部”的情形(左-右 或 右-左),该情形通过稍微复杂些的双旋转(double rotation)来处理
单旋转
当发生左-左的情形时,也就是在左儿子的左子树进行插入的情形。如果需要平衡的树为k2,左儿子为k1,k2右子树为Z,k1左子树为X,右子树为Y。那么X一定是只比Y多一层,而比Z多两层,Y不可能在插入以后与X同一层,因为那样在插入之前就会失去平衡了,而且Y也不可能跟Z同一层,因为这样k1就会是破坏平衡的第一个节点。为了使树恢复平衡,我们让X上升一层,Z下降一层。具体做法是,把k1当做新的根,则k2成了k1的右儿子,将k1的右子树变为k2的左子树,而X和Z分别还是k1的左子树和k2的右子树
这就是与左树的单旋转,右树的单旋转就是一种对称情形做法是一样的。
双旋转
而发生左-右的情形时,在k1的右子树就多一层,而单旋转只能让k1的左子树X上升一层到达Z当前的高度,Z下降一层到达之前X的高度,k1变为根,Y变为k2的左子树,可以发现仍然没有平衡。那么其实解决方法也很简单,就是利用旋转,让多出来的一层到外边,里边就会降低一层,然后再利用之前的单旋转解决外边的情形。具体做法是,如果k3是Y的根,在k1和k3之间进行单旋转,那么k3就会变成根,k1变成k3的左儿子,X会下降一层,Y整体会上升一层(Y包含根k3和它的子树)。此时再用k1和左子树的单旋转就可以了。
实现
有个效率问题涉及高度信息的存储。由于真正需要的实际上就是子树高度的差,应该保证它很小。如果真的尝试这种方法,则可用两个二进制位表示这个差。这么做将避免平衡因子的重复计算,但是却丧失某些简明性。最后的程序多多少少要比在每一个节点存储高度时复杂。如果编写递归程序,那么速度恐怕不是主要考虑的问题。此时,通过存储平衡因子所得到的些微的速度优势很难抵消清晰度和相对简明性的损失。
类型声明:
typedef int ElementType;
typedef struct AvlNode* Position;
typedef struct AvlNode* AvlTree;
struct AvlNode
{
ElementType element;
AvlTree left;
AvlTree right;
int height;
};
单旋转:
注意旋转之后要更新高度,这里比较容易忘记。
Position SingleRotateWithLeft(Position k2)
{
Position 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;
}
Position SingleRotateWithRight(Position k1)
{
Position k2 = k1->right;
k1->left = k2->right;
k2->left = k1;
k1->height = Max(Height(k1->left), Height(k1->right)) + 1;
k2->height = Max(Height(k2->left), Height(k2->right)) + 1;
return k2;
}
双旋转:
Position DoubleRotateWithLeft(Position k3)
{
k3->left = SingleRotateWithRight(k3->left);
return SingleRotateWithLeft(k3);
}
Position DoubleRotateWithRight(Position k3)
{
k3->right = SingleRotateWithLeft(k3->right);
return SingleRotateWithRight(k3);
}
插入:
AvlTree Insert(AvlTree t, ElementType x)
{
if (t == NULL)
{
t = (AvlTree)malloc(sizeof(struct AvlNode));
if (t == NULL)
{
printf("insert failed\n");
return NULL;
}
else
{
t->element = x;
t->left = t->right = NULL;
t->height = 0;
}
}
if (x > t->element)
{
t->right = Insert(t->right, x);
if (Height(t->right) - Height(t->left) == 2)
if (x > t->right)
t = SingleRotateWithRight(t);
else
t = DoubleRotateWithRight(t);
}
else if (x < t->element)
{
t->left = Insert(t->left, x);
if (Height(t->left) - Height(t->right) == 2)
if (x < t->element)
t = SingleRotateWithLeft(t);
else
t = DoubleRotateWithLeft(t);
}
t->height = Max(Height(t->left), Height(t->right)) + 1;
return t;
}
返回树高度和比较:
int Max(int x, int y)
{
return x > y ? x : y;
}
int Height(AvlTree t)
{
if (t == NULL)
return -1;
else
return t->height;
}
清空树:
void MakeEmpty(AvlTree t)
{
if (t != NULL)
{
MakeEmpty(t->left);
MakeEmpty(t->right);
free(t);
}
}
查找元素与检索位置p的元素:
Position Find(AvlTree t, ElementType x)
{
if (t == NULL)
return NULL;
if (x > t->element)
Find(t->right, x);
else if (x < t->element)
Find(t->left, x);
else
return t;
}
ElementType Retrieve(Position p)
{
if (p == NULL)
return INT_MAX;
return p->element;
}
打印树:
void PrintTree(AvlTree t)
{
if (t != NULL)
{
PrintTree(t->left);
printf("%d ", t->element);
PrintTree(t->right);
}
}
AVL树的删除多少要比插入复杂,如果删除操作相对较少,那么懒惰操作恐怕是最好的策略。