-
- AVL树的介绍
- 实现
- 1 旋转
- LL左左
- RR右右
- LR左右
- RL右左
- 2 插入
- 3 删除
- 1 旋转
- 性能
- 完整代码和参考资料
1. AVL树的介绍
AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的。
它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于”二叉查找树”,它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。
如上图(左):非AVL树,如对节点9,左子树高度为0,右子树高度为2
如上图(由):AVL树,对任意节点的两个子树高度最大差1
2. 实现
当因为插入或删除导致AVL的平衡被破坏时,需要旋转处理来修复。
如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)
对于左右的情况为什么不是直接以3节点为轴向左旋转? 因为旋转只能处理根节点和它的左节点(或右节点),来把它的左节点(右节点)提升到根。换句话说,旋转的输入参数(轴)必须是根,提升的只能是字节点,下降的只能是根。
(多级旋转,必须是从底层开始,到顶层结束)
2.1 旋转
LL左左
因为插入或删除,使得根节点的左节点的左节点非空,导致根的左子树比右子树高度大2;只需以根为输入,一次右旋,将原根的左节点旋转到根
//LL情况时,一次右旋--k2是根,k1是k2的左子树,将k1旋转成根 -- 以k2为原点向右旋
AVLTree rotLL(AVLTree k2)
{
AVLTree k1 = k2->left;
k2->left = k1->right;
k1->right = k2;
k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
k1->height = MAX(AVLTreeHeight(k1->left), k2->height) + 1;
return k1;
}
RR右右
因为插入或删除,使得根节点的右节点的右节点非空,导致根的右子树比左子树高度大2;只需以根为输入,一次左旋,将原根的右节点旋转到根
//RR情况时,一次左旋---k2是根,k1是k2的右子树,(k1的右子树非空)将k1旋转成根 -- 以k2为原点向左旋
AVLTree rotRR(AVLTree k2)
{
AVLTree k1 = k2->right;
k2->right = k1->left;
k1->left = k2;
k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
k1->height = MAX(k2->height, AVLTreeHeight(k1->right)) + 1;
return k1;
}
LR左右
插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。需要2次旋转,先以根的右节点为输入做一次左旋(即RR的情况),此时树的形状为LL,再做一次右旋(即LL)。
//LR情况时,先左旋在右旋:k3是根,k1是k3的左子树,k2是k1的右子树,
// 那么先以k1为原点左旋,再以k3为原点右旋,最终把k2旋转到根(相当于先RR旋转,再LL旋转)
AVLTree rotLR(AVLTree k3)
{
k3->left = rotRR(k3->left); //从底层开始到顶层结束
return rotLL(k3);
}
AVLTree rotRL(AVLTree k3)
{
k3->right = rotLL(k3->right); //从底层开始到顶层结束
return rotRR(k3);
}
RL右左
类似于LR情况,不做赘述
2.2 插入
首先,AVL的插入和搜索二叉树的插入相同。不同的是插入会影响AVL的平衡,当因为插入导致不平衡时(左右子树高度差2),同样存在上述的四种情况,需要对应的旋转处理。
AVLTree AVLTreeInsert(AVLTree tree, Item key)
{
if (tree == NULL)
return NewNode(key, NULL, NULL);
if (key < tree->key) {
//插入到左子树
tree->left = AVLTreeInsert(tree->left, key);
//由于插入到左子树上的节点导致不平衡
if (Height(tree->left) - Height(tree->right) > 1) {
//插入到左子树的左侧。则是LL,反之则是LR
if (key < tree->left->key)
tree = rotLL(tree);
else
tree = rotLR(tree);
}
} else if (key > tree->key) {
tree->right = AVLTreeInsert(tree->right, key);
if (Height(tree->right) - Height(tree->left) > 1) {
if (key > tree->right->key)
tree = rotRR(tree);
else
tree = rotRL(tree);
}
} else {
printf("\nExsited key = %d, Insert Failed\n", key);
// return tree;
}
tree->height = MAX(Height(tree->left), Height(tree->right)) + 1;
return tree;
}
2.3 删除
删除同样可能影响AVL的平衡。
- 比如说,如果因为删除左子树中的节点,导致右子树比左子树高度大2,那么此时再观察右子树的左右子树的高度,若右子树的右子树比左子树高,那么则是RR的情况,反之则是RL的情况。
- 而针对于匹配到待删除节点 : 如果恰好是根节点:那么删除根后,左右子树高度还是最大差1,不需旋转;如果不是根(即待删除的在左右子树中),那么在上述两种情况已经判断了是否旋转。
- 对于删除节点后的连接:如果左右子树都非空
若左子树高,则找出左子树的最大值替换根的值,然后在左子树中删除该最大值的节点;
若右子树高,则找出右子树的最小值替换根的值,然后在右子树中删除该最小值的节点;
这样保证了AVL以更大的可能维持平衡。
AVLTree AVLTreeDelete(AVLTree tree, Item key)
{
if (tree == NULL)
return NULL;
//待删除节点在左子树中
if (key < tree->key) {
tree->left = AVLTreeDelete(tree->left, key);
//左子树中删除,若右子树高度比左大2(失去平衡),将右子树中的某节点旋转到根,降低高度
if (Height(tree->right) - Height(tree->left) > 1) {
if (Height(tree->right->right) > Height(tree->right->left))
tree = rotRR(tree);
else
tree = rotRL(tree);
}
} else if (key > tree->key) {
tree->right = AVLTreeDelete(tree->right, key);
//右子树中删除,左子树高度比右大2
if (Height(tree->left) - Height(tree->right) > 1) {
if (Height(tree->left->left) > Height(tree->left->right))
tree = rotLL(tree);
else
tree = rotLR(tree);
}
} else {//匹配到待删除
//待删除节点两字树非空
if (tree->left && tree->right) {
if (Height(tree->left) > Height(tree->right)) {
//左高,找到左子树中的最大值,替换根的值
AVLTree avl_max = findAVLMax(tree->left);
tree->key = avl_max->key;
AVLTreeDelete(tree->left, avl_max->key);
} else {
//右高,找到右子树中的最小值,替换根的值
AVLTree avl_min = findAVLMin(tree->right);
tree->key = avl_min->key;
AVLTreeDelete(tree->right, avl_min->key);
}
} else {
//有一个为空,或者2个为空
AVLTree tmp = tree;
tree = (tree->left == NULL) ? (tree->right) : (tree->left);
free(tmp);
return tree;
}
}
return tree;
}
3. 性能
- 向AVL树插入,可以透过如同它是未平衡的二叉查找树一样,把给定的值插入树中,接着自底往上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有1.44乘log n个节点,即O(log n),而每次AVL旋转都耗费固定的时间,所以插入处理在整体上的耗费为O(logn)时间。
- 从AVL树中删除,可以透过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。本文没有采用
- 搜索,可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树搜寻相对立的,它会因为搜寻而变更树结构。)
4. 完整代码和参考资料
#include <stdio.h>
#include <stdlib.h>
#define MAX(A, B) ((A > B) ? A : B)
#define Height(tree) ((tree == NULL) ? 0 : (tree->height))
typedef int Item;
typedef struct AVLTreeNode AVLTreeNode;
typedef AVLTreeNode* AVLTree;
struct AVLTreeNode {
Item key;
AVLTree left;
AVLTree right;
int height;
};
static int g_error = 0; //错误代码
AVLTree NewNode(Item key, AVLTree left, AVLTree right)
{
AVLTree x = (AVLTree)malloc(sizeof(*x));
if (x == NULL) {
g_error = 1;
exit(-1);
}
x->key = key;
x->left = left;
x->right = right;
x->height = MAX(Height(x->left), Height(x->right)) + 1;
return x;
}
AVLTree AVLTreeInit()
{
return NewNode(10, NULL, NULL);
}
int AVLTreeHeight(AVLTree tree)
{
return (tree == NULL) ? 0 : (tree->height);
}
//RR情况时,一次左旋---k2是根,k1是k2的右子树,(k1的右子树非空)将k1旋转成根 -- 以k2为原点向左旋
AVLTree rotRR(AVLTree k2)
{
AVLTree k1 = k2->right;
k2->right = k1->left;
k1->left = k2;
k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
k1->height = MAX(k2->height, AVLTreeHeight(k1->right)) + 1;
return k1;
}
//LL情况时,一次右旋--k2是根,k1是k2的左子树,将k1旋转成根 -- 以k2为原点向右旋
AVLTree rotLL(AVLTree k2)
{
AVLTree k1 = k2->left;
k2->left = k1->right;
k1->right = k2;
k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
k1->height = MAX(AVLTreeHeight(k1->left), k2->height) + 1;
return k1;
}
//LR情况时,先左旋在右旋:k3是根,k1是k3的左子树,k2是k1的右子树,
// 那么先以k1为原点左旋,再以k3为原点右旋,最终把k2旋转到根(相当于先RR旋转,再LL旋转)
AVLTree rotLR(AVLTree k3)
{
k3->left = rotRR(k3->left); //从底层开始到顶层结束
return rotLL(k3);
}
AVLTree rotRL(AVLTree k3)
{
k3->right = rotLL(k3->right); //从底层开始到顶层结束
return rotRR(k3);
}
AVLTree AVLTreeInsert(AVLTree tree, Item key)
{
if (tree == NULL)
return NewNode(key, NULL, NULL);
if (key < tree->key) {
tree->left = AVLTreeInsert(tree->left, key);
if (Height(tree->left) - Height(tree->right) > 1) {
if (key < tree->left->key)
tree = rotLL(tree);
else
tree = rotLR(tree);
}
} else if (key > tree->key) {
tree->right = AVLTreeInsert(tree->right, key);
if (Height(tree->right) - Height(tree->left) > 1) {
if (key > tree->right->key)
tree = rotRR(tree);
else
tree = rotRL(tree);
}
} else {
printf("\nExsited key = %d, Insert Failed\n", key);
// return tree;
}
tree->height = MAX(Height(tree->left), Height(tree->right)) + 1;
return tree;
}
void traversal(AVLTree tree)
{
if (tree == NULL) {
printf("NIL\t");
return;
}
printf("%d\t", tree->key);
traversal(tree->left);
traversal(tree->right);
return;
}
AVLTree findAVLMin(AVLTree tree)
{
if(tree == NULL || tree->left == NULL)
return tree;
return findAVLMin(tree->left);
}
AVLTree findAVLMax(AVLTree tree)
{
if (tree == NULL || tree->right == NULL)
return tree;
return findAVLMax(tree->right);
}
AVLTree AVLTreeDelete(AVLTree tree, Item key)
{
if (tree == NULL)
return NULL;
//待删除节点在左子树中
if (key < tree->key) {
tree->left = AVLTreeDelete(tree->left, key);
//左子树中删除,右子树高度比左大2,将右子树中的某节点旋转到根,降低高度
if (Height(tree->right) - Height(tree->left) > 1) {
if (Height(tree->right->right) > Height(tree->right->left))
tree = rotRR(tree);
else
tree = rotRL(tree);
}
} else if (key > tree->key) {
tree->right = AVLTreeDelete(tree->right, key);
//右子树中删除,左子树高度比右大2
if (Height(tree->left) - Height(tree->right) > 1) {
if (Height(tree->left->left) > Height(tree->left->right))
tree = rotLL(tree);
else
tree = rotLR(tree);
}
} else { //匹配到待删除节点 : 如果恰好是根节点:那么删除跟后,左右子树高度还是最大差1,不需旋转;如果不是根,那么在上述两种情况判断是否旋转
//待删除节点两字树非空
if (tree->left && tree->right) {
if (Height(tree->left) > Height(tree->right)) {
//左高,找到左子树中的最大值,替换根的值
AVLTree avl_max = findAVLMax(tree->left);
tree->key = avl_max->key;
AVLTreeDelete(tree->left, avl_max->key);
} else {
//右高,找到右子树中的最小值,替换根的值
AVLTree avl_min = findAVLMin(tree->right);
tree->key = avl_min->key;
AVLTreeDelete(tree->right, avl_min->key);
}
} else {
AVLTree tmp = tree;
tree = (tree->left == NULL) ? (tree->right) : (tree->left);
free(tmp);
return tree;
}
}
return tree;
}
int main()
{
AVLTree avl_tree = NULL;
for (int i = 0; i < 10; i++) {
int key = rand()%100;
avl_tree = AVLTreeInsert(avl_tree, key);
printf("%d\t", key);
}
printf("\nTraversal\n");
traversal(avl_tree);
AVLTreeDelete(avl_tree, 41);
printf("\nDeleted Traversal\n");
traversal(avl_tree);
getchar();
}
参考资料:
1. AVL树(一)之 图文解析 和 C语言的实现:http://www.cnblogs.com/skywang12345/p/3576969.html
2. AVL树-维基百科:https://zh.wikipedia.org/wiki/AVL%E6%A0%91