C++模拟实现——AVL树

AVL树

1.介绍

AVL树是对搜索二叉树的改进,通过特定的方法使得每个节点的左右子树高度差绝对值不超过1,使得避免出现歪脖子的情况,最核心的实现在于插入值部分是如何去实现平衡调整的,由于前面详细实现和解析过搜索二叉树,因此本篇文章着重整理AVL树核心的部分,插入的实现,以及旋转是如何操作的

2.基本框架

先搭建一个搜索二叉树的基本框架

节点定义部分

平衡因子的概念:

一个节点的平衡因子指的是左右子树的高度差,根据自己定义,可以是左边高度减右边高度,或者是右边高度减左边高度,在AVL树中,调节平衡因子是实现平衡的关键

ps:本篇对平衡因子_bf的定义都是右边减左边

AVL树的框架

3.核心部分

基本框架

插入部分重点还是在于平衡因子的调整部分,前面就不详细分析了

平衡因子的调整

每次插入要保持每个节点的平衡因子绝对值都小于1,则说明整棵树是符合AVL树的规则的,即每个节点的左右子树高度差的绝对值不超过1。

因此,再每次插入一个新的节点时,要考虑每个节点之间平衡因子的变化,通过画图观察发现,当一个新节点插入后,有可能受影响的是其部分或者全部祖先

通过画图观察总结出更新的规律,新增节点后,当新增节点在其父母节点的左边则对父母节点的bf进行--,在右边则进行++(这是因为我们定义bf采用右边减左边),当父母节点bf改变后,如果绝对值为1,那么需要继续向上调整,调整的规则也是,看该节点在其父母节点的左边还是右边,左边则--,右边则++,直到遇到每次调整parent时,parent的bf变为0,则说明不需要调整了,并且插入成功,又或者调整parent时,其bf绝对值等于2,则说明需要对该部分子树进行旋转调整了(降高度)

旋转调整

旋转又分成四种方式,分别是左单旋、右单旋、左右双旋、右左双旋

1.单旋

左单旋,当遇到这种最右边高左边低的时候,采用左单旋,将60位置的左子树给30位置,再由60去取代30原先的位置,形象点看就是,好像吧一个旋钮往左旋转了一样

右单旋,同理就是与左单旋的情况相反,最左边高,右边低,将30的b给60,30取代60的位置

代码实现

从画图上来看,这就是左单旋和右单旋,看似简单,但在转化成代码的时候,要注意几个问题:

1.要注意每个节点的parent指针需要被维护

2.一开始我们就对需要用到的位置,用指针去一 一对应的话,要注意中间的那个b是有可能为空的

3.最上面的位置不一定就是根节点,因此在更换掉最上面位置节点的时候,要考虑对其上面是否还有节点进行判断,如果还有节点,则进行进一步链接,为根节点则需要将其parent置空

右单旋同理,需要注意的细节和左旋是一样的,这里给出左单旋和右单旋的代码参考

左单旋代码参考:

	void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;subR->_left = parent;parent->_parent = subR;parent->_right = subRL;if (subRL)subRL->_parent = parent;if (pparent){if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}else{_root = subR;_root->_parent = nullptr;}parent->_bf = subR->_bf = 0;}

右单旋代码参考:

	void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;parent->_left = subLR;if (subLR)subLR->_parent = parent;if (pparent){if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}subL->_parent = pparent;}else{_root = subL;_root->_parent = nullptr;}parent->_bf = subL->_bf = 0;}
2.双旋

双旋是为了解决子树往中间偏的问题,当子树往中间偏高时,可以先将其往一边掰直,转换成上面两种情况之一,然后再调整,因为需要两次单旋操作,所以叫双旋

左右双旋,对30位置进行左旋,然后再对90位置进行右旋,从结果来看就是,将60最终放到最上面,60的左子树给左边的30,60的右子树给了右边90

右左双旋同理

双旋的代码实现:

双旋的难点不是在于实现双旋操作本身,因为只需要复用左单旋和右单旋就可以了,真正的难点在于对平衡因子的更新,与单旋不同,双旋对平衡因子的更新并不是直接将调整位置的值都变成0,而是需要根据实际情况去分类讨论的

拿左右双旋举例

左右双旋代码分析

代码参考:

左右双旋:

	void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int subLR_bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (subLR_bf == -1)//在b下新增节点{parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (subLR_bf == 1)//在c下新增节点{parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (subLR_bf == 0)//subRL就是新增节点{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}

右左双旋:

	void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);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 if (bf == 0){parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}}
3.根据不同情况选择不同旋转方式

以上,就是AVL树的核心实现,最重要的就是控制平衡,通过旋转调整平衡的方式,保证了每个节点的左右子树绝对值不超过1,实现平衡,大大加快了搜索二叉树的效率,搜索效率能够达到
O(logN)

4.测试接口

中序遍历

中序遍历有序只能保证树是二叉搜索树,但不能保证平衡

	void _InOrder(){InOrder(_root);cout << endl;}void InOrder(const Node* root){if (root == nullptr){return;}InOrder(root->_left);cout << root->_value.first << " ";InOrder(root->_right);}

验证平衡因子是否绝对值不超过1

这里提供一个IsBalanceTree接口验证是否平衡因子绝对值不超过1

	int _Height(){return Height(_root);}int Height(const Node* root){if (root == nullptr)return 0;int left = Height(root->_left) + 1;int right = Height(root->_right) + 1;return left > right ? left:right;}bool _IsBalanceTree(){return IsBalanceTree(_root);}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;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (diff != root->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return IsBalanceTree(root->_left) && IsBalanceTree(root -> _right);}

随机值插入验证测试

bool test()
{//16, 3, 7, 11, 9, 26, 18, 14, 15//4, 2, 6, 1, 3, 5, 15, 7, 16, 14srand(time(0));const size_t N = 1000;AVLTree<int, int> n;for (int i = 0; i < N; i++){size_t x = rand()%100;n.Insert(make_pair(x, x));}n._InOrder();return n._IsBalanceTree();
}

总结

本篇内容整理总结了AVL树的核心实现,分析了其中的内部原理,对原理以及实现都画图进行了分析,提供了源码参考

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OV5640的参数与配置方法

分辨率和速率&#xff08;FPS&#xff09; 寄存器配置 I/O 板的驱动能力和方向控制 system clock control OV5640 PLL 允许输入时钟频率范围为 6~27 MHz&#xff0c;最大 VCO 频率为 800 MHz。 MipiClk 用于 MIPI&#xff0c;SysClk 用于图像信号处理 (ISP) 模块的内部时钟。 …

新版本Idea设置启动参数

1.进入配置页面 2.点击下图红框的部分&#xff0c;会看到有很多操作可选 3.选择添加VM参数即可 此时就会多出一个可以输入参数的框了&#xff0c;如下&#xff1a;

网络和Linux网络_1(网络基础)网络概念+协议概念+网络通信原理

目录 1. 网络简介 1.1 独立模式和互联网络模式 1.2 局域网LAN和广域网WAN 2. 协议和协议分层 2.1 协议的作用 2.2 协议分层 2.3 OSI七层模型 3.2 TCP/IP四层(五层)模型 3. 网络通信原理 3.1 协议报头 3.2 局域网和解包分用 3.3 广域网和跨网络 4. 网络中的地址 4…

JVM之类加载器

文章目录 版权声明类加载器类加载器的分类启动类加载器拓展类加载器&应用程序类加载器 双亲委派机制解决三个问题 打破双亲委派机制自定义类加载器案例演示线程上下文类加载器案例梳理OSGi模块化 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我…

mac 安装使用svn教程

mac 安装使用svn教程 一、安装Homebrew 要在Mac OS上安装SVN&#xff0c;首先需要安装Homebrew。Homebrew是一个流行的包管理器&#xff0c;因此我们将使用它来安装SVN。 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"…

介绍 Docker 的基本概念和优势,以及在应用程序开发中的实际应用

Docker是一种基于容器的虚拟化技术&#xff0c;它允许开发者将应用程序及其依赖项打包到一个轻量级容器中&#xff0c;然后在任何可用的开发、测试和生产环境中进行部署和运行。 下面是Docker的基本概念和优势&#xff1a; 容器&#xff1a;Docker容器是一种独立运行的软件包&a…

【开源】基于Vue.js的校园失物招领管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目详细录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系统公告模块2.4 感谢留言模块 三、界面展示3.1 登录注册3.2 招领模块3.3 寻物模块3.4 公告模块3.5 感谢留言模块3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 基于Vue…

数列计算

题目描述 有一列数是 : 请找出这个数列的规律&#xff0c;编写程序计算并输出这个数列的第项&#xff0c;要求是分数形式&#xff0c;并计算这个数列的前项和 ( 结果四舍五入保留两位小数 ) 输入格式 第一行仅有一个正整数 &#xff08;) 。 输出格式 共有 行&#xff0c;第一…

Java基础-基础语法

1、概述 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。 对象&#xff1a;对象是类的一个实例&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个对象&#xff0c;它的状态有&#xff1a;颜色、名字、品种&#xff1b;…

Java之“数字困境”:资产管理项目中的Bug追踪与启示

目录 1 前言2 问题的发现3 调试的开始4 深入调试5 调试心得与反思6 结语 1 前言 在程序员的日常工作中&#xff0c;我们时常面对各种令人头疼的问题&#xff0c;其中最令人崩溃的瞬间之一&#xff0c;就是当我们花费大量时间追踪一个看似复杂的bug&#xff0c;最终发现问题的根…

蒙特卡洛树搜索(Monte Carlo Tree Search)揭秘

一. 什么是蒙特卡洛树搜索 蒙特卡洛树搜索(MCTS)是一种启发式搜索算法&#xff0c;一般用在棋牌游戏中&#xff0c;如围棋、西洋棋、象棋、黑白棋、德州扑克等。MCTS与人工神经网络结合&#xff0c;可发挥巨大的作用&#xff0c;典型的例子是2016年的AlphaGo&#xff0c;以4:1…

JAVA集合学习

一、结构 List和Set继承了Collection接口&#xff0c;Collection继承了Iterable Object类是所有类的根类&#xff0c;包括集合类&#xff0c;集合类中的元素通常是对象&#xff0c;继承了Object类中的一些基本方法&#xff0c;例如toString()、equals()、hashCode()。 Collect…

实战Leetcode(五)

Practice makes perfect&#xff01; 实战一&#xff1a; 思路&#xff1a;我们要用复制的节点来组成一个新的链表&#xff0c;而原链表的节点随机指向其中一个节点&#xff0c;我们首先给每一个节点都复制并且插入到原来节点的后面&#xff0c;然后用复制的节点指向我们原来节…

物理问题中常见的分析问题----什么样的函数性质较好

物理问题中常见的积分符号位置交换问题 重极限与累次极限 高数下的定义 累次极限&#xff1a;求极限时需要遵循一定的顺序重极限&#xff1a;任意方向趋于的极限 两者之间的关系&#xff1a; 两者没啥关系存在累次极限存在而不相等的函数...... 求和符号与积分符号互换--逐项积…

RK3568笔记五:基于Yolov5的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 一. 部署概述 环境&#xff1a;Ubuntu20.04、python3.8 芯片&#xff1a;RK3568 芯片系统&#xff1a;buildroot 开发板&#xff1a;ATK-DLRK3568 开发主要参考文档&#xff1a;《Rockchip_Quick_Start_RKNN_Toolkit2_C…

专题知识点-二叉树-(非常有意义的一篇文章)

这里写目录标题 二叉树的基础知识知识点一(二叉树性质 )树与二叉树的相互转换二叉树的遍历层次优先遍历树的深度和广度优先遍历中序线索二叉树二叉树相关遍历代码顺序存储和链式存储二叉树的遍历二叉树的相关例题左右两边表达式求值求树的深度找数找第k个数二叉树非递归遍历代码…

C++ builder 常见问题汇总

1、CB静态编译设置 2、CB10.3设置经典编译器&#xff08;用于解决10.3弹出代码提示慢&#xff09; 3、CBuilder生成Release版本 &#xff1a; project->Options->CCompiler->Build Configuration 选择 Release project->Options->CLinker中取消Use dynamic RTL…

简单实现,在nodejs中简单使用kafka

什么是 Kafka Kafka 是由 Linkedin 公司开发的&#xff0c;它是一个分布式的&#xff0c;支持多分区、多副本&#xff0c;基于 Zookeeper 的分布式消息流平台&#xff0c;它同时也是一款开源的基于发布订阅模式的消息引擎系统。 Kafka 的基本术语 消息&#xff1a;Kafka 中的…

深入理解 Django 单元测试

概要 在现代软件开发流程中&#xff0c;单元测试是确保代码质量和可维护性的关键组成部分。对于使用 Django 框架的项目来说&#xff0c;Django 提供了一套强大的测试工具来帮助开发者编写和运行单元测试。本文将深入探讨 Django 中的单元测试&#xff0c;包括测试原理、编写测…

vue3 ref 与shallowRef reactive与shallowReactive

ref 给数据添加响应式&#xff0c;基本类型采用object.defineProperty进行数据劫持&#xff0c;对象类型是借助reactive 实现响应式&#xff0c;采用proxy 实现数据劫持&#xff0c;利用reflect进行源数据的操作 let country ref({count:20,names:[河南,山东,陕西],objs:{key…