数据结构——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& ...
随机推荐
- C语言编写一个程序,从键盘读入一个矩形的两个边的值(整数),输出矩形面积。
#include<stdio.h> int main() { int i, j, s;//定义矩形的长,宽,面积变量 scanf_s("%d,%d", &i, ...
- java.util.List如何用
起因是这样,我在学习Javaweb,然后就突然有很多类似的语句 这是什么意思呢?让我们一起来解决看看吧! List有序集合(也成为序列),用户可以精确控制列表中每个元素的插入位置.用户可以通过整数索引 ...
- [编程] AI助力软件项目正向生成,注释编写的革命
引言 软件项目质量直接影响着用户体验和企业效益.随着软件的应用范围不断扩大,提高软件质量的重要性也日益凸显.传统上,软件工程师通常采用自下而上的开发模式,自行设计实现代码并进行测试,这给质量把控带来一 ...
- [CF1034C] Region Separation
题目描述 There are $ n $ cities in the Kingdom of Autumn, numbered from $ 1 $ to $ n $ . People can trav ...
- MyBatisPlus简介
MyBatisPlus特性 国内的一个网站 网站地址简介 | MyBatis-Plus (baomidou.com)
- 后端程序员必会的前端知识-02:JavaScript
第二章. Javascript 它是一种脚本语言,可以用来更改页面内容,控制多媒体,制作图像.动画等等 例子 修改页面内容 js 代码位置 <script> // js 代码 </s ...
- Oracle-startup和shutdown
startup不同参数作用 startup nomount 非安装启动,以这种方式启动可执行: 1.重建控制文件. 2.重建数据库读取init.ora文件. 3.启动实例,即启动SGA和后台进程,需要 ...
- pytest框架中conftest.py的作用
conftest.py 是 pytest 框架中的一个特殊文件.它允许你为测试提供自定义的配置和钩子函数.以下是 conftest.py 的主要作用: 提供全局配置:你可以在 conftest.py ...
- 38. 干货系列从零用Rust编写负载均衡及代理,负载均衡中ip通行与禁止
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...
- Java 并发编程(四)同步工具类
本文使用的 JDK 版本为 JDK 8 基本同步工具类 闭锁(CountDownLatch) 闭锁是一种工具类,可以延迟线程的进度直到其到达终止状态.闭锁的作用相当与一扇门:在闭锁的状态到达之前,这扇 ...