数据结构——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& ...
随机推荐
- CPF C#跨平台UI框架开源了
介绍 C#跨平台UI框架 提供NETStandard2.0和net4的库,通过Netcore可以跨平台,支持Windows.Mac.Linux,Net4的可以支持XP. 各个平台运行效果一致,不依赖系 ...
- 【Javaweb】servlet二
servlet程序常见错误 1.url-pattern路径没有以 / 打头 2.servlet-name配置的值不存在 3.servlet-class标签的全类名配置错误 servlet-url地址如 ...
- vertx的学习总结2
一.什么是verticle verticle是vertx的基本单元,其作用就是封装用于处理事件的技术功能单元 (如果不能理解,到后面的实战就可以理解了) 二.写一个verticle 1. 引入依赖( ...
- Head First Java学习:第八章-接口和抽象类
第八章:接口和抽象类 深入多态 1.抽象类:有些类不应该被初始化 在类声明前面加上抽象类的关键字,abstract. 防止类被初始化,即不能被"new"创建该类的实例(要求) 还是 ...
- 基于python人脸识别考勤系统(语音播报)
介绍: 本项目是大二寒假在家没事写的,一直没有时间讲本项目分享出来,现在有时间了哈.那就让我简单的将项目介绍一下吧.好了废话不多说了,直接上图 初始化界面: 可以看到所有的功能都展现在了左边的功能栏中 ...
- 文心一言 VS 讯飞星火 VS chatgpt (162)-- 算法导论13.1 2题
二.用go语言,对图13-1中的红黑树,画出对其调用TREE-INSERT 操作插人关键字 36 后的结果.如果插人的结点被标为红色,所得的树是否还是一棵红黑树?如果该结点被标为黑色呢? 文心一言,代 ...
- Weblogic获取端口IP
Weblogic获取端口IP 获取端口IP只为了判断哪个节点 调用 private WebMBeanServer server=new WebMBeanServer(); synlog.info(&q ...
- Java 并发编程(三)锁与 AQS
本文 JDK 对应的版本为 JDK 13 由于传统的 synchronized 关键字提供的内置锁存在的一些缺点,自 JDK 1.5 开始提供了 Lock 接口来提供内置锁不具备的功能.显式锁的出现不 ...
- 解决QObject::moveToThread: Current thread (0x56059f9b0f70) is not the object's t
对 opencv 降级 pip install opencv-python==4.1.2.30
- Spring源码学习笔记6——Spring bean的实例化
一丶前言 前面我们了解到读取xml or 根据扫描路径生成BeanDefinition并注册到BeanFactory,相当于我们具备了生火做饭的原材料:BeanDefinition,接下来就是Spri ...