数据结构——AVL树
AVL树是一种特殊的二叉查找树,其特征在于:对所有节点来说,其左子树和右子树间的高度差小于等于1。本文简要总结下AVL树的几种基本操作。
节点结构体定义
typedef struct Node_s {
int element;
struct Node_s * left, * right;
int height;
} Node;
为了突出说明核心问题,节点数据类型使用最简单的int表示;height为树的高度,叶子节点高度为0,每向上一层加1,即每个节点的深度为其左右子树最大深度加1。可使用以下几个宏定义来计算及获取深度:
#define Height(T) (T == NULL ? -1 : T->height)
#define MAX(a, b) (a > b ? a : b)
#define CalHeight(T) (T->height = MAX(Height(T->left), Height(T->right)) + 1)
AVL树的旋转
旋转操作是AVL树特有的操作,也是学习AVL树的核心,旋转的目的在于解决插入、删除等操作造成的AVL树不平衡问题。AVL树的不平衡一共只有4种情况(以插入为例说明):
- LL:在左子树左节点进行插入
- LR:在左子树右节点进行插入
- RR:在右子树右节点进行插入
- RL:在右子树左节点进行插入
示意图:


关于旋转的详细分析可参考第一篇参考资料,此处仅给出实现代码及简要思路。
LL及RR单旋转
二者是镜像操作,实现方法比较简单。
二者是镜像操作,实现方法比较简单。
Node * RotateLL (Node * T) {
Node * t = T->left;
T->left = t->right;
t->right = T;
CalHeight(T);
CalHeight(t);
return t;
}
Node * RotateRR (Node * T) {
Node * t = T->right;
T->right = t->left;
t->left = T;
CalHeight(T);
CalHeight(t);
return t;
}
调整完节点关系后,需要重新计算一下节点的高度。
LR及RL双旋转
二者也是镜像操作,可以视为两次单旋转的结合。
Node * RotateLR(Node * T) {
T->left = RotateRR(T->left);
return RotateLL(T);
}
Node * RotateRL(Node * T) {
T->right = RotateLL(T->right);
return RotateRR(T);
}
因为单旋转操作已经正确的调整了节点高度,双旋转中无需再调整节点高度。
常用操作
插入
一般使用递归形式,递归函数返回时,检查当前节点是否平衡,不平衡则执行旋转操作。
Node * Insert(Node * T, int val) {
/* 新插入的元素必定为叶子节点 */
if (T == NULL) {
T = (Node *)malloc(sizeof(Node));
T->element = val;
T->left = T->right = NULL;
}
/* 在左子树插入 */
else if (val < T->element) {
T->left = Insert(T->left, val);
if (Height(T->left) - Height(T->right) == 2) {
if (val < T->left->element)
T = RotateLL(T);
else
T = RotateLR(T);
}
}
/* 在右子树插入 */
else if (val > T->element) {
T->right = Insert(T->right, val);
if (Height(T->right) - Height(T->left) == 2) {
if (val > T->right->element)
T = RotateRR(T);
else
T = RotateRL(T);
}
}
/* 若元素已存在,不执行任何操作 */
/* 递归函数返回前调整节点高度 */
/* 这保证了每一层递归函数返回的节点高度都是正确的 */
/* 进而保证了整棵树的节点高度正确 */
CalHeight(T);
return T;
}
查找元素
根据二叉查找树的基本性质,可以很容易的写出查找最大元素、最小元素、任意元素的代码。
Node * FindMax(Node * T) {
while(T->right != NULL) {
T = T->right;
}
return T;
}
Node * FindMin(Node * T) {
while(T->left != NULL) {
T = T->left;
}
return T;
}
Node * Find(Node * T, int val) {
while (T != NULL) {
if (T->element == val)
break;
else if (val < T->element)
T = T->left;
else
T = T->right;
}
return T;
}
删除
删除是最复杂的操作,如果删除操作不多的话,可以考虑使用懒惰删除的策略,即增加一个标志位,表明当前节点是否被删除了。如果需要真实的删除元素,使用以下方法进行:
Node * Delete(Node * T, int val) {
/* 待删除元素在左子树中 */
if (val < T->element) {
T->left = Delete(T->left, val);
if (Height(T->right) - Height(T->left) == 2) {
if (Height(T->right->left) < Height(T->right->right))
T = RotateRR(T);
else
T = RotateRL(T);
}
}
/* 待删除元素在右子树中 */
else if (val > T->element) {
T->right = Delete(T->right, val);
if (Height(T->left) - Height(T->right) == 2) {
if (Height(T->left->right) < Height(T->left->left))
T = RotateLL(T);
else
T = RotateLR(T);
}
}
/* 删除当前节点 */
else {
/* 当前节点有两个儿子 */
/* 选择高度较大那一边进行删除,以此避免AVL树不平衡 */
if (T->left && T->right) {
/* 选择左树的话,用左树中最大节点代替当前节点,并删除最大节点原位置 */
if (Height(T->left) > Height(T->right)) {
Node * tmax = FindMax(T->left);
T->element = tmax->element;
T->left = Delete(T->left, tmax->element);
}
/* 选择右树的话,用右树中最小节点代替当前节点,并删除最小节点原位置 */
else {
Node * tmin = FindMin(T->right);
T->element = tmin->element;
T->right = Delete(T->right, tmin->element);
}
}
/* 当前节点是叶子节点或只有一个儿子,直接删除 */
else {
Node * tmp = T;
T = T->left ? T->left : T->right;
free(tmp);
}
}
/* 递归返回非空节点时,需要重新计算其高度 */
if (T)
CalHeight(T);
return T;
}
基本策略和插入一样,依然是递归的进行删除,若待删除节点有两个儿子时,使用树的删除操作中的一般方法,即选择较高一侧子树中最大或最小元素代替当前元素,之后再删除那个最大或最小元素。最大或最小元素一定是叶子元素,这样之后的删除操作就会很简单,且这样的替代删除策略不会导致树的不平衡。
遍历
同样有前序、中序、后序及层序四种遍历策略,就是树的通用遍历策略,可参考二叉树的遍历算法。
数据结构——AVL树的更多相关文章
- 数据结构-AVL树的旋转
http://blog.csdn.net/GabrieL1026/article/details/6311339 平衡二叉树在进行插入操作的时候可能出现不平衡的情况,AVL树即是一种自平衡的二叉树,它 ...
- 简单数据结构———AVL树
C - 万恶的二叉树 Crawling in process... Crawling failed Time Limit:1000MS Memory Limit:32768KB 64b ...
- JAVA数据结构--AVL树的实现
AVL树的定义 在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下的时间复杂度都是.增 ...
- 数据结构--Avl树的创建,插入的递归版本和非递归版本,删除等操作
AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1). 也就是说,AVL树,本质上 ...
- 再回首数据结构—AVL树(一)
前面所讲的二叉搜索树有个比较严重致命的问题就是极端情况下当数据以排序好的顺序创建搜索树此时二叉搜索树将退化为链表结构因此性能也大幅度下降,因此为了解决此问题我们下面要介绍的与二叉搜索树非常类似的结构就 ...
- 再回首数据结构—AVL树(二)
前面主要介绍了AVL的基本概念与结构,下面开始详细介绍AVL的实现细节: AVL树实现的关键点 AVL树与二叉搜索树结构类似,但又有些细微的区别,从上面AVL树的介绍我们知道它需要维护其左右节点平衡, ...
- 第三十二篇 玩转数据结构——AVL树(AVL Tree)
1.. 平衡二叉树 平衡二叉树要求,对于任意一个节点,左子树和右子树的高度差不能超过1. 平衡二叉树的高度和节点数量之间的关系也是O(logn) 为二叉树标注节点高度并计算平衡因子 AVL ...
- Java数据结构——AVL树
AVL树(平衡二叉树)定义 AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,并且拥有自平衡机制.在AV ...
- 数据结构 - AVL 树
简介 基本概念 AVL 树是最早被发明的自平衡的二叉查找树,在 AVL 树中,任意结点的两个子树的高度最大差别为 1,所以它也被称为高度平衡树,其本质仍然是一颗二叉查找树. 结合二叉查找树,AVL 树 ...
- 数据结构-AVL树
实现: #ifndef AVL_TREE_H #define AVL_TREE_H #include "dsexceptions.h" #include <iostream& ...
随机推荐
- 【uniapp】【外包杯】学习笔记day07 | 微信小程序轮播图、分类导航、楼层图的开发与实现
1.创建home分支 2.配置网络请求 由于平台的限制,现需要建立uni-app中使用第三方包请求网络数据请求 在 uni-app 项目中使用 @escook/request-miniprogram ...
- 在net中通过Autofac实现AOP的方法及实例详解
在本示例中,我们将使用Autofac和AspectC(Autofac.Extras.DynamicProxy2)来演示如何实现AOP(面向切面编程).我们将创建一个简单的C#控制台应用程序,并应用AO ...
- jdk9模块化
JDK 9是Java开发语言的一个重大版本.其中最令人兴奋的新特性之一是模块化系统.模块化系统提出了一种新的代码组织方式,它可以帮助开发人员更好地组织和管理代码,从而使Java应用程序更加可维护.可扩 ...
- [ABC246F] typewriter
Problem Statement We have a typewriter with $N$ rows. The keys in the $i$-th row from the top can ty ...
- ElasticSearch给索引起"别名"和其重要性
创建别名: https://www.elastic.co/guide/en/elasticsearch/reference/6.8/indices-aliases.html 我们有时候并不能确保索引库 ...
- 盘点前端的那些Ajax请求:从ES5到React
说起前端开发,Ajax请求是绕不开的技术点.然而,程序语言更新换代越来越快,Ajax请求的方式也是各有不同. 在使用ES5开发的时候,我们还在使用最原始的XMLHttpRequest对象: // cr ...
- 若依集成mybatisplus实现mybatis增强
- HTML之CSS Animation 属性常用动画
引入下面的样式表后 -webkit-animation: tada 1s ease 0.3s infinite both; -webkit-animation: name duration timin ...
- 不用手动创建数据库,直接导入sql文件,就能生成数据库和数据表
- Windows和Linux下通过go实现自删除
自删除在攻防中都挺常见的,自写远控通常也有需要.可是在度娘里搜不到什么办法,于是就查查Windows api学习记录一回. linux 先获得当前程序的文件名,再使用syscall这个包中的Unlin ...