一. 引言
AVL树是带有平衡条件的二叉查找树.这个平衡条件必须要容易保持,而且它必须保证树的深度是O(logN).一颗AVL树是其每个节点的左子树和右子树的高度最多差一的二叉查找树.
主要介绍插入算法和删除算法.
二. AVL树的结点定义:
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;
typedef int ElementType;
struct AvlNode
{
ElementType Element;
AvlTree Left;
AvlTree Right;
int Height;
};
三. 各种辅助函数:
/* * 返回两个数中的最大值 */
static int Max(int a, int b)
{
if(a > b)
return a;
else
return b;
}
/* 在AVL树T中寻找元素值最小的节点,并将其返回 */
Position FindMin(AvlTree T)
{
if(T == NULL)
return NULL;
else
if(T->Left == NULL)
return T;
else
return FindMin(T->Left);
}
/* * 返回节点P的高度 */
static int Height(Position P)
{
if (P == NULL)
return -1;
else
return P->Height;
}
四. 对AVL树进行修复的函数
1. 进行旋转的函数
左单旋转:
可以看到在结点k2处失去了平衡, 并且是在k2的左儿子的左子树上插入的,因此对k2结点进行左单旋转,即将k1作为新的根节点,将k2作为其右儿子,原来k2的右儿子作为k2的左结点,详见示意图(图丑勿怪^_^).
左单旋转的代码:
/* * 左单旋转 */
static Position SingleRotateWithLeft(Position K2)
{
Position K1;
K1 = K2->Left;
K2->Left = K1->Right;
K1->Right = K2;
//K2的子节点的高度都没有变化,不需更改
K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;
//返回新的根节点
return K1;
}
右单旋转的过程和左单旋转的过程基本一样,不做赘述,详见代码.
/* * 右单旋转 */
static Position SingleRotateWithRight(Position K2)
{
Position K1;
K1 = K2->Right;
K2->Right = K1->Left;
K1->Left = K2;
//K2的子节点的高度都没有变化
K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;
//返回新的根节点
return K1;
}
2. 左-右双旋转:
从图中可以看出,旋转之前在k2处失去了平衡,但是此时仅在k2时做一次单旋转已经不能保持平衡了,因此进行新的左-右双旋转.从图中可以看出,实际上左-右双旋转相当于先对k3的左儿子k1做一次右单旋转再对k3做一次左单旋转,所以实际上双旋转没有什么新的东西.
左-右双旋转的代码:
/* * 左-右双旋转 */
static Position DoubleRotateWithLeft(Position K3)
{
K3->Left = SingleRotateWithRight(K3->Left);
return SingleRotateWithLeft(K3);
}
右-左双旋转代码:
/* * 右-左双旋转 */
static Position DoubleRotateWithRight(Position K3)
{
K3->Right = SingleRotateWithLeft(K3->Right);
return SingleRotateWithRight(K3);
}
3. 对失去平衡的节点进行修复的函数
假如结点在k2处失去平衡,则要先判断如何执行旋转,具体判断参照代码:
//对失去平衡的节点进行修复的函数
static Position Fix(Position K2)
{
if(Height(K2->Left) > Height(K2->Right))
{
//K2左儿子的左儿子的高度大于K2的左儿子的右儿子的高度, 执行左单旋转, 否则执行左-右双旋转
if(Height(K2->Left->Left) > Height(K2->Left->Right))
K2 = SingleRotateWithLeft(K2);
else if(Height(K2->Left->Left) < Height(K2->Left->Right))
K2 = DoubleRotateWithLeft(K2);
}
else if(Height(K2->Left) < Height(K2->Right))
{
//K2右儿子的右儿子的高度大于K2的右儿子的左儿子的高度, 执行右单旋转, 否则执行右-左双旋转
if(Height(K2->Right->Right) > Height(K2->Right->Left))
K2 = SingleRotateWithRight(K2);
else if(Height(K2->Right->Right) < Height(K2->Right->Left))
K2 = DoubleRotateWithRight(K2);
}
return K2;
}
五. AVL树的插入函数
插入之后要对结点高度进行修改,并且判断是否失去平衡,如果失去平衡则要进行修复.
代码如下:
/*
* 向Avl树插入节点
*/
AvlTree Insert(ElementType X, AvlTree T)
{
if(T == NULL)
{
//创建并返回一个只有一个节点的树
T = malloc(sizeof(struct AvlNode));
if(T == NULL)
{
printf("Out of space!!!\n");
return NULL;
}
T->Element = X;
T->Height = 0;
T->Left = T->Right = NULL;
}
else if(X < T->Element)
{
T->Left = Insert(X, T->Left);
if(Height(T->Left) - Height(T->Right) >= 2)
T = Fix(T);
}
else if(X > T->Element)
{
T->Right = Insert(X, T->Right);
if(Height(T->Right) - Height(T->Left) >= 2)
T = Fix(T);
}
/* 如果X已经在树中了, 那么什么都不做*/
//修改插入后T节点的高度
T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
return T; //千万不能忘
}
六. AVL树的删除函数
删除过程和二叉查找树的删除过程一样, 用被删除节点的右子树上的最小元素代替被删除的节点, 然后递归删除该最小元.
只不过删除完成后要检查节点高度, 如果不满足AVL树的平衡条件,则要进行旋转修复.
如果删除操作较少,懒惰删除恐怕是最好的策略.
代码如下:
/* AVL树的非懒惰删除 */
Position Delete(ElementType X, AvlTree T)
{
Position TmpCell;
if(T == NULL)
{
printf("Element not found!!\n");
return NULL;
}
else if(T->Element > X) //往左边查找元素X
T->Left = Delete(X, T->Left);
else if(T->Element < X) //往右边查找元素X
T->Right = Delete(X, T->Right);
else if(T->Left && T->Right) //元素找到了并且左右子树都不为空
{
//将要删除的节点用右子树中的最小节点代替
TmpCell = FindMin(T->Right);
T->Element = TmpCell->Element;
//递归删除该最小节点
T->Right = Delete(T->Element, T->Right);
}
else //元素找到了并且左右子树有一个为空
{
TmpCell = T;
if(T->Left == NULL)
T = T->Right; //此处是为了让T的父节点指向T的右子树(由于函数最后返回了T)
else if(T->Right == NULL)
T = T->Left;
free(TmpCell); //释放节点
}
if(T != NULL)
{
//删除完节点之后要更新高度
T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
//在节点T处失去平衡
if((Height(T->Left) - Height(T->Right) >= 2) || (Height(T->Right) - Height(T->Left) >= 2))
{
T = Fix(T);
//修复完成后更新节点高度
T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
}
}
return T; //千万不能忘记
}
参考资料: <数据结构与算法分析–C语言描述(第二版)>