AVL树了解并简单实现

这篇文章默认知道二叉搜索树,如果了解并不多可以先看看二叉搜索树了解和实现-CSDN博客

目录

1.AVL树概念

2.AVL树节点定义

3.AVL树的插入(重点)

3.1AVL树

3.2AVL树的旋转

3.3AVL树插入代码

4.AVL树的验证

5.AVL树的删除

6.AVL树的性能

7.整体代码


1.AVL树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下
AVL树又称为高度平衡的二叉搜索树,是1962年由两位俄罗斯数学家 G.M.Adelson-Velski和E.M.Landis提出的。引人它的目的,是为了提高二叉搜索树的效率,减少树的平均搜索长度。为此, 当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度。
一棵AVL树是具有以下性质的二叉搜索树:
  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) 

对于AVL树,如果它有n个结点,其高度可保持在 \log_{2}^{n},搜索时间复杂度 \log_{2}^{n}

2.AVL树节点定义


3.AVL树的插入(重点)

3.1AVL树

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

平衡因子 = 右子树高度 - 左子树高度

  • 插入的新结点newNode在当前结点cur的左子树,平衡因子--
  • 插入的新结点newNode在当前结点cur的右子树,平衡因子++
  • 是否要往上更新祖先的平衡因子,要看cur的parent结点所在高度是否发生变化
    1. 插入新节点之前,parent的平衡因子为0,插入新节点后,parent的平衡因子变为1/-1,说明parent所在子树的高度改变了,需要往上更新
    2. 插入新节点之前,parent的平衡因子为1/-1,插入新节点后,parent的平衡因子变为0,说明parent所在子树的高度没有改变,不需要往上更新
    3. 插入新节点之前,parent的平衡因子为1/-1,插入新节点后,parent的平衡因子变为2/-2,需要进行旋转处理


3.2AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构使之平衡化。达到旋转的条件是该结点平衡因子变为2/-2就要进行旋转。根据节点插入位置的不同,AVL树的旋转分为四种:

        1.新节点插入在较高左子树的左侧,解决操作:右单旋

当h越来越大,a,b,c这三颗子树的情况越多,组合起来越大,这里只是进行简单分析了一下

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层, 即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

  1.  30节点的右孩子可能存在,也可能不存在
  2.  60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点。如果是子树,可能是某个节点的左子树,也可能是右子树

把subL往上提,parent作为subL的右孩子,parent的左孩子变为subL的右孩子,修改完后调节平衡因子(subLR可能为空)

// 右单旋
void _RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR != nullptr){subLR->_parent = parent;}Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr) // parent为根结点{_root = subL;subL->_parent = nullptr;}else // parent为其中的一颗子树{// 判断该子树是parentParent的左还是右if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}// 调节平衡因子parent->_bf = 0;subL->_bf = 0;
}

        2.新节点插入较高右子树的右侧,解决操作:左单旋

// 左单旋
void _RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL != nullptr){subRL->_parent = parent;}Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr) // parent为根结点{_root = subR;subR->_parent = nullptr;}else // parent为其中的一颗子树{// 判断该子树是parentParent的左还是右if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}// 调节平衡因子parent->_bf = 0;subR->_bf = 0;
}

        3. 新节点插入较高左子树的右侧,解决操作:先左单旋再右单旋

这里画的新节点插入在b这颗子树中,还有插入到c子树中(b子树的高度为h-1,c子树的高度为h,相应的平衡因子也要修改)
void _RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;// subL位置左单旋_RotateL(parent->_left);// parent位置再右单旋_RotateR(parent);// 调整平衡因子if (bf == 0) // subLR为插入的结点(图中abcd子树都为空,原树只有parent和curL这两个节点){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}

        4. 新节点插入较高右子树的左侧,解决方案:先右单旋再左单旋

void _RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;_RotateR(parent->_right);_RotateL(parent);// 调整平衡因子if (bf == 0) // subRL为插入的结点{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{assert(false);}}

总结:

假如以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2,分以下情况考虑:

1.Parent的平衡因子为2,说明Parent的右子树高,设Parent的右子树为SubR,当SubR的平衡因子为1时,执行左单旋,当SubR的平衡因子为-1时,执行右左双旋

2. Parent的平衡因子为-2,说明Parent的左子树高,设Parent的左子树为SubL,当SubL的平衡因子为-1是,执行右单旋,当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原子树的高度和旋转完的子树高度一样,所以不需要再向上更新。


3.3AVL树插入代码

bool insert(const pair<K,V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;// 查找到要插入的位置while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{// 存在数据相同,插入失败return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 链接cur->_parent = parent;// 更新平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}// 判断平衡因子if (parent->_bf == 0) // 插入在矮的那边{break;}else if (parent->_bf == 1 || parent->_bf == -1) {// 往上更新cur = parent;parent = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 旋转if (parent->_bf == 2 && cur->_bf == 1) // 左单旋{_RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1) // 右单旋{_RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1) // 右左双旋{_RotateRL(parent);}else // 左右双旋{_RotateLR(parent);}// 旋转完了不需要往上更新平衡因子break;}else{// 前面出错了assert(false);}}return true;
}

4.AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

1. 验证其为二叉搜索树,如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

2. 验证其为平衡树每个节点子树高度差的绝对值不超过1

int _Height(Node* root)
{if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{// 空树也是AVL树if (nullptr == root) return true;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与root的平衡因子不相等,或者// root平衡因子的绝对值超过1,则一定不是AVL树if (diff != root->_bf || (diff > 1 || diff < -1))return false;// root的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);
}

测试用例

int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16 ,14 };

void testAVLTree()
{bit::AVLTree<int, int> avl;//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16 ,14 };for (auto e : a){avl.insert({ e,e });}avl.InOrder();std::cout << avl.IsBalanceTree() << endl;
}

5.AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

后面我会写一篇AVL树的删除,再把链接放进来。删除的复杂度要比插入复杂,考虑条件也多。


6.AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即\log_{2}^{n}。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。


7.整体代码(挑选重点看就行)

AVL/AVL.h · wrf/C++test_cpp - 码云 - 开源中国

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/16877.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】索引原理及操作

目录 索引原理 初识索引 磁盘原理 磁盘与系统之间的关系 MySQL、系统、磁盘之间的关系 理解索引 页目录 页目录设计的数据结构问题 聚簇索引与非聚簇索引 遗留问题 索引操作 创建索引 查询索引 删除索引 其他索引概念与操作 索引原理 索引&#xff08;I…

代码随想录算法训练营第三十一天| 56. 合并区间 、738.单调递增的数字 。c++转java

56. 合并区间 class Solution {public int[][] merge(int[][] intervals) {//对区间按照右边界排序Arrays.sort(intervals,(a,b) -> Integer.compare(a[0],b[0]));List<int[]> p new LinkedList<>();int l intervals[0][0],r intervals[0][1];for(int i 1;i…

厦大南洋理工最新开源,一种面向户外场景的特征-几何一致性无监督点云配准方法

导读 本文提出了INTEGER&#xff0c;一种面向户外点云数据的无监督配准方法&#xff0c;通过整合高层上下文和低层几何特征信息来生成更可靠的伪标签。该方法基于教师-学生框架&#xff0c;创新性地引入特征-几何一致性挖掘&#xff08;FGCM&#xff09;模块以提高伪标签的准确…

模型运行速度笔记: s/epoch VS s/iter

1 概念介绍 在模型训练中&#xff1a; s/epoch 表示每个epoch所需的秒数&#xff0c;即完成一轮完整数据集训练的时间。s/iter 表示每个iteration&#xff08;迭代&#xff09;所需的秒数&#xff0c;即处理一个batch的时间。 它们的关系是&#xff1a; 2 举例 比如我tra…

k8s 中传递参数给docker容器

文章目录 docker启动时传递参数使用k8s env传递完全覆盖 ENTRYPOINT 和 CMD 在 Kubernetes 中&#xff0c;可以通过多种方式将参数传递给 Dockerfile 或其运行的容器&#xff0c;常见的方式包括使用环境变量、命令行参数、配置文件等。以下是一些常用的方法&#xff1a; docker…

Map Set

在学习TreeMap和TreeSet之前需要先学习有关搜索树的相关知识以及接口Map和Set。 1. 搜索树 1.1 概念 二叉搜索树又称二叉排序树&#xff0c;其特点是&#xff0c;该节点的左边都比其小&#xff0c;右边都比其大&#xff0c;每一棵子树都必须满足这个条件。如下图所示例子。2…

Android OpenGLES2.0开发(八):Camera预览

严以律己&#xff0c;宽以待人 引言 终于到该章节了&#xff0c;还记得Android OpenGLES2.0开发&#xff08;一&#xff09;&#xff1a;艰难的开始章节说的吗&#xff1f;写这个系列的初衷就是因为每次用到GLSurfaceViewCamera预览时&#xff0c;总是CtrlC、CtrlV从来没有研究…

基础 IO

目录 一、基本共识 二、复习C语言中的文件操作 三、与文件操作有关的系统调用接口 1. open 与 close 1.1 umask 2. write 3. read 四、如何理解文件 1. 文件描述符 fd 2. 文件fd分配规则 3. 重定向的引入 4. 重定向的本质 5. dup2 6. 理解 >、>>、…

ThriveX 博客管理系统前后端项目部署教程

前端 前端项目地址&#xff1a;https://github.com/LiuYuYang01/ThriveX-Blog 控制端项目地址&#xff1a;https://github.com/LiuYuYang01/ThriveX-Admin Vercel 首先以 Vercel 进行部署&#xff0c;两种方式部署都是一样的&#xff0c;我们以前端项目进行演示 首先我们先…

[含文档+PPT+源码等]精品基于springboot实现的原生Andriod手机使用管理软件

软件开发环境及开发工具&#xff1a; 数据库管理工具&#xff1a;phpstudy/Navicat或者phpstudy/sqlyog 开发工具&#xff1a;Android Studio 后台管理系统涉及技术&#xff1a; 后台使用框架&#xff1a;Springboot 前端使用技术&#xff1a;Vue,HTML5,CSS3、JavaScript等…

华为三层交换机禁止VLAN间通讯(两种解决方案)

在日常办公中&#xff0c;有时会禁止内网中各个部门间的访问&#xff0c;例如&#xff1a; ①访客不能访问内网任何终端及服务器 ②财务部门不能被其他部门访问 实验环境&#xff1a;华为Ensp模拟器 内网架构&#xff1a;三层网络 环境说明&#xff1a;三层交换机承载着网…

为以人工智能为中心的工作负载重新设计的全局控制台

MinIO 控制台多年来一直是一个不断发展的产品。每次学习时&#xff0c;我们都会思考如何改进交互框架中这个非常重要的部分。首先是控制台&#xff0c;它在推出后的一年内就被广泛采用。更具体地说&#xff0c;超过 10K 个组织。接下来是企业控制台。这从对象存储与其 GUI 之间…

stm32在linux环境下的开发与调试

环境安装 注&#xff1a;文末提供一键脚本 下载安装stm32cubeclt 下载地址为&#xff1a;https://www.st.com/en/development-tools/stm32cubeclt.html 选择 linux版本下载安装 安装好后默认在家目录st下 > $ ls ~/st/stm32cubeclt_1.16.0 …

【leetcode】N皇后 回溯法c++

目录 51.N皇后 52.N皇后II 51.N皇后 51. N 皇后 - 力扣&#xff08;LeetCode&#xff09; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间…

GESP4级考试语法知识(贪心算法(六))

寻找平面上的极大点代码 #include<iostream> #include<algorithm> using namespace std; struct node {int x,y; }a[101]; bool vis[101]; bool cmp(node A,node B) {if(A.x!B.x) return A.x<B.x;return A.y<B.y; } int main() {int n;cin>>n;for(int…

如何构建高效的知识库系统?实现智能信息管理

在当今信息化迅速发展的时代&#xff0c;企业和组织面临着海量信息的挑战。如何有效地管理这些信息&#xff0c;使其安全、易于访问&#xff0c;并且能够快速响应用户的需求&#xff0c;成为了智慧管理的核心。而知识库系统(Knowledge Base System)正是为了解决这一问题而应运而…

动态规划29:673. 最长递增子序列的个数

动态规划解题步骤&#xff1a; 1.确定状态表示&#xff1a;dp[i]是什么 2.确定状态转移方程&#xff1a;dp[i]等于什么 3.初始化&#xff1a;确保状态转移方程不越界 4.确定填表顺序&#xff1a;根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接&#xff1a;673.…

AI驱动的桌面笔记应用Reor

网友 竹林风 说&#xff0c;已经成功的用 mxbai-embed-large 映射到 text-embedding-ada-002&#xff0c;并测试成功了。不愧是爱折腾的人&#xff0c;老苏还没时间试&#xff0c;因为又找到了另一个支持 AI 的桌面版笔记 Reor Reor 简介 什么是 Reor ? Reor 是一款由人工智…

AWS CLI

一、介绍 1、简介 aws configure 是 AWS 提供的一个命令行工具&#xff0c;用于快速配置 AWS CLI&#xff08;命令行界面&#xff09;和 AWS SDK&#xff08;软件开发工具包&#xff09;中使用的凭证、默认区域以及输出格式。这个命令是 AWS CLI 中的一个配置工具&#xff0c…

Unity图形学之Shader2.0 深度测试

1.什么是深度&#xff1a;物体1和物体2的深度都是10&#xff0c;深度是物体和相机视角水平垂线的投影 物体3的深度是8 2.什么是深度缓存 要渲染的像素的深度信息&#xff0c;比当前存在Gbuffer里的缓存的深度信息小&#xff08;靠近相机&#xff09;的时候&#xff0c;就会替换…