AVL平衡树 - 二叉搜索树的扩展1
来源:互联网 发布:国外it技术博客 编辑:程序博客网 时间:2024/05/17 22:32
- 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
- AVL平衡树 - 二叉搜索树的扩展1
- AVL平衡二叉搜索树
- AVL平衡搜索二叉树
- AVL平衡二叉搜索树
- 平衡二叉搜索树(AVL树)
- AVL树(平衡二叉搜索树)
- AVL树(一种二叉平衡搜索树)
- 平衡二叉搜索树之AVL树
- 数据结构-平衡搜索二叉树(AVL树)
- 高度平衡的二叉搜索树-----AVL树
- AVL 平衡二叉树
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- AVL 平衡二叉树
- avl平衡二叉树
- 二叉平衡树AVL
- 平衡二叉树(AVL)
- 平衡二叉树(AVL)
- 利用ssh-keygen工具使ssh/sftp无密码登录服务器
- 2.cpp
- Alert Dialog
- [G+smo]domain iterator and boundary iterator
- 【Cocos2d-x】视线和光线:如何创建 2D 视觉范围效果
- AVL平衡树 - 二叉搜索树的扩展1
- vmware不显示usb图标解决办法
- Windows下QT打包发布
- 操作系统概念学习笔记 5 操作系统管理简述
- 【php】1、学生管理系统-欢迎界面
- [G+smo]surface fitting
- 用数组实现约瑟夫环
- virtio的io路径 vhost的io路径 和vhost-user vhost-user *********网络设备
- jquery ajax教程第4课-Get和Post的简写方法(转)