平衡树的模拟实现

一.平衡树的介绍

平衡树是以二叉树结构为基础,同时引入了平衡因子进行了限制,以保证树的结点之间的高度差小于等于1,在插入删除结点时通过旋转的方法保持高度相对平衡,从而提高搜索等效率。

二.代码实现

1.平衡树结点

平衡树结点是以二叉树为基础构建的,因此需要左右结点指针left与right。传入pair一部分为值value另一部分为关键字key,以及平衡因子bf(左子树高度减右子树高度)。

下面是结点代码:

template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};

 2.平衡树的插入

插入结点时首先要找到合适的位置空间来放置新结点,所以我们需要遍历树。从根结点开始(cur),若值比当前结点小让cur往左边走,若值比当前结点大让cur往右边走,直到cur跑到空时停止。

接着进行判断,根据结点高度差的不同,结点位置不一样进行不同的旋转方式,调整相应的平衡因子,根据parent结点的高度决定是否要继续向上更新。

下面是寻找结点的相应代码:

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;

若树的结点为空那么直接插入根结点,若不为空按照上述的方法遍历到空结点后,需要注意要确定parent的位置。

 下面我们来讨论一下旋转

首先计算平衡因子,若cur在parent的左边则bf--,若cur在parent的右边则bf++,若parent的bf为0时直接跳出结果。

若parent的左右都存在结点,那么就是平衡的,反之则不平衡,若bf等于1或者-1,时继续向上更新bf平衡值,让cur等于parent,parent等于parent的parent。

向上遍历若得到bf等于2或者-2时,说明此时树已经不平衡了,需要进行旋转调整树的结构。

下面我们依次分析左旋,右旋,左右双旋,右左双旋的情况

1.右单旋

当parent的平衡因子为-2,cur的平衡因子为-1时我们需要右单旋

如图p代表parent,c代表cur此时我们需要进行右旋操作d代表插入的结点。以parent为轴向右旋转让cur变成parent。

我们需要得到parent结点,cur结点(SUL),以及cur结点的右结点(SULR)。

让SULR变成parent的左结点SUL的右结点变成parent,其他的不进行改变。

需要注意的是,首先需要判断SULR结点是否存在,若存在就让其变为parent的左结点,若不存在就不进行操作。以及我们需要找到parent结点的parent结点,若parentparent结点为空则则让root结点直接变为SUL即可,若不为空需要判断parent结点是parentparent结点的左还是右结点,然后将其左或右给给SUL。

下面放上代码:

void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent有可能是整棵树的根,也有可能是局部子树//如果是整棵树的根则修改root//if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}

2.左单旋 

左单旋与右单旋类似,只是parent结点的左右子树互换了位置。

当parent的bf为2时,cur的bf为1时,需要进行左旋操作。

如图p代表parent ,c代表cur结点,插入的结点为a。以parent为轴向左旋转。

我们需要得到cur结点的左结点(SURL),parent结点以及cur结点(SUR)。让parent的右为SURL,SUR的左为parent。其他的不进行改变。 

与上文相同需要找到parent的parent,若parentparent不存在就将root结点设置为SUR,若存在,则根据parent是parentparent的左或者右结点来分配给SUR结点。

下面是代码:

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

3.左右双旋 

当parent的bf等于-2,cur的bf等于1时,进行左右双旋。先对cur进行左旋让其bf等于-1,此时就回到了第一种情况,再对parent进行一次右旋就完成了操作。

当新增结点cur结点与parent结点形成一个角度时,需要进行双旋操作,如果是开口向右则进行左右双旋,若开口向左则进行右左双旋。

此处的难点在于对插入结点bf的分类讨论。

第一种情况 当SULR(R)的bf为0时

 此时我们需要先对SUL进行左旋操作,使其满足三个点连成一条直线,之后再对parent进行右旋操作,得到下图。

我们可以根据最终结果直接写出代码,先对SUL进行左旋再对parent进行右旋,最后设置三个结点的bf都为0即可。

第二种情况 当SULR(R) 的bf为-1时

当我们进行左右双旋操作后

变成如图,此时需要将parent的bf设置为1,SUL和SULR设置为0。

第三种情况 当SURL(R)的bf为1时

进行左右双旋操作之后

 将SUL的bf设置为-1,parent与SULR的bf设置为0即可。

下面附上代码:

	void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}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){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else{assert(false);}}

 5.插入代码总结

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 (cur == parent->_left)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){//更新结果break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续向上更新cur = parent;parent = parent->_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)//右左双旋{RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋{RotateLR(parent);}else if(parent->_bf == -2 && cur->_bf == -1)//右单旋{RotateR(parent);}break;}else{//说明传入的有问题报错日志assert(false);}}return true;}//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent有可能是整棵树的根,也有可能是局部子树//如果是整棵树的根则修改root//if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}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 == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else{assert(false);}}

3.键值查找  树的高度计算 与平衡树判断

查找值时,只需要遍历整个树即可,以根节点(cur)为起始点,当所找的值比cur小时向左树寻找,比cur大时向右树寻找,直到cur值等于寻找值时返回当前结点,若cur为空了仍未找到结点就返回空。

Node* find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}

计算树的高度时我们采用递归左右子树的方法实现,判断左右子树的大小取大值,逐层递归得到最终值。

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;}

 判断是否平衡时,我们可以调用左右子树的高度,然后相减,得到diff。若diff大于2说明高度异常,若根结点的bf不等于diff说明平衡因子计算存在错误。

bool _IsBalanceTree(Node* root){if (nullptr == root)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = leftHeight - rightHeight;if (abs(diff) >= 2){cout << root->_kv.first << "高度异常" << endl;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}

 三.总结

平衡树要求任意节点的左右子树高度差绝对值不超过 1,且左右子树本身也是平衡树。通过限制树的高度(保持在 O(logn) 级别),确保查找、插入、删除等操作的时间复杂度稳定在 O(logn),避免退化为链表的 O(n) 复杂度。每次插入或删除后通过旋转(左旋、右旋、左右双旋、右左双旋)立即恢复平衡。

它的优点在于时间复杂度低,适合大量数据的操作,缺点在于需要动态的更新数据进行旋转增大了时间开销。

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

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

相关文章

JavaScript基础-获取元素

在Web开发中&#xff0c;使用JavaScript动态地访问和操作网页上的元素是一项基本技能。通过获取页面上的特定元素&#xff0c;我们可以对其进行各种操作&#xff0c;比如修改内容、样式或属性等。本文将详细介绍几种获取DOM元素的方法&#xff0c;并探讨它们的特点及适用场景。…

为什么要用(:deep、::v-deep、>>>)样式穿透

在 Vue.js 中&#xff0c;当你使用像 Element UI 这样的 UI 库时&#xff0c;它们的样式通常是全局的&#xff0c;即使你在组件中使用了 scoped 样式&#xff08;为什么要用scoped&#xff09;&#xff0c;仍然可能需要对这些全局样式进行修改。 为了实现这一点&#xff0c;样…

MySQL中的事务隔离级别有哪些

MySQL中的事务隔离级别 一、事务并发问题二、MySQL 事务隔离级别1. READ UNCOMMITTED&#xff08;读未提交&#xff09;2. READ COMMITTED&#xff08;读已提交&#xff09;3. REPEATABLE READ&#xff08;可重复读&#xff09;&#xff08;MySQL 默认级别&#xff09;4. SERIA…

Python----计算机视觉处理(Opencv:图像镜像旋转)

一、图像镜像旋转 图像的旋转是围绕一个特定点进行的&#xff0c;而图像的镜像旋转则是围绕坐标轴进行的。图像镜像旋转&#xff0c;也可 以叫做图像翻转&#xff0c;分为水平翻转、垂直翻转、水平垂直翻转三种。 通俗的理解为&#xff0c;当以图片的中垂线为x轴和y轴时&#x…

hibernate 自动生成数据库表和java类 字段顺序不一致 这导致添加数据库数据时 异常

hibernate 自动生成的数据库表和java类 字段顺序不一致 这导致该书写方式添加数据库数据时 异常 User user new User( null, username, email, phone, passwordEncoder.encode(password) ); return userRepository.save(user);Hibernate 默认不会保证数据库表字段的顺序与 Ja…

python|结构的模式匹配match|同步迭代

在 Python 中&#xff0c;模式匹配&#xff08;Pattern Matching&#xff09; 是一种强大的功能&#xff0c;用于根据数据的结构或内容进行匹配和处理。Python 3.10 引入了 match 语句&#xff0c;使得模式匹配更加直观和灵活。模式匹配可以用于处理复杂的数据结构&#xff0c;…

博客图床 VsCode + PigGo + 阿里云OSS

关键字 写博客&#xff0c;图床&#xff0c;VsCode&#xff0c;PigGo&#xff0c;阿里云OSS 背景环境 我想把我在本地写的markdown文档直接搬到CSDN上和博客园上&#xff0c;但是图片上传遇到了问题。我需要手动到不同平台上传文件&#xff0c;非常耗费时间和经历。 为了解决…

路由器安全研究:D-Link DIR-823G v1.02 B05 复现与利用思路

前言 D-Link DIR-823G v1.02 B05存在命令注入漏洞&#xff0c;攻击者可以通过POST的方式往 /HNAP1发送精心构造的请求&#xff0c;执行任意的操作系统命令。 漏洞分析 binwalk提取固件&#xff0c;成功获取到固件。 现在我们已经进入到应用里了&#xff0c;那么我们在进行分析…

c++ 类和对象 —— 下 【复习总结】

1. 深入构造函数 1.1 函数体赋值 前文我们提到&#xff0c;创建对象时&#xff0c;编译器会调用构造函数给成员变量赋值。但这并不能称为对对象中成员变量的初始化。因为初始化只能初始化一次&#xff0c;但构造函数体内可以多次赋值。构造函数体中语句只能称为赋初值 那么&…

【量化科普】Volatility,波动率

【量化科普】Volatility&#xff0c;波动率 &#x1f680;量化软件开通 &#x1f680;量化实战教程 在金融市场中&#xff0c;波动率&#xff08;Volatility&#xff09;是衡量资产价格变动幅度的一个重要指标。它反映了资产价格的稳定性和风险水平。高波动率意味着资产价格…

PCIe(Peripheral Component Interconnect Express)详解

一、PCIe的定义与核心特性 PCIe&#xff08;外设组件互连高速总线&#xff09;是一种 高速串行点对点通信协议&#xff0c;用于连接计算机内部的高性能外设。它取代了传统的PCI、PCI-X和AGP总线&#xff0c;凭借其高带宽、低延迟和可扩展性&#xff0c;成为现代计算机系统的核…

idea 编译打包nacos2.0.3源码,生成可执行jar 包常见问题

目录 问题1 问题2 问题3 问题4 简单记录一下nacos2.0.3&#xff0c;编译打包的步骤&#xff0c;首先下载源码&#xff0c;免积分下载&#xff1a; nacos源码&#xff1a; https://download.csdn.net/download/fyihdg/90461118 protoc 安装包 https://download.csdn.net…

通过 TTL 识别操作系统的原理详解

TTL 的工作原理 TTL&#xff08;Time to Live&#xff0c;生存时间&#xff09;是网络中用于控制数据包生命周期的一个关键参数。它通过限制数据包在网络中可以经过的最大路由跳数&#xff08;或最大转发时间&#xff09;&#xff0c;确保数据包不会在网络中无休止地转发。TTL…

总结Solidity 的数据类型

数据类型 在 Solidity 中&#xff0c;类型系统非常丰富&#xff0c;主要分为 值类型&#xff08;Value Types&#xff09;和 引用类型&#xff08;Reference Types&#xff09;。此外&#xff0c;还有一些特殊类型和全局变量。 一.值类型 布尔型&#xff08;bool&#xff09…

Android audio(8)-native音频服务的启动与协作(audiopolicyservice和audioflinger)

音频策略的构建 1、概述 2、AudiopolicyService 2.1 任务 2.2 启动流程 2.2.1 加载audio_policy.conf&#xff08;xml&#xff09;配置文件 2.2.2 初始化各种音频流对应的音量调节点 2.2.3 加载audio policy硬件抽象库 2.2.4设置输出设备 ps:audiopatch流程简介 2.2.5打开输出设…

DeepSeek:从入门到精通

DeepSeek是什么&#xff1f; DeepSeek是一家专注通用人工智能&#xff08;AGI&#xff09;的中国科技公司&#xff0c;主攻大模型研发与应 用。DeepSeek-R1是其开源的推理模型&#xff0c;擅长处理复杂任务且可免费商用。 Deepseek可以做什么&#xff1f; 直接面向用户或者支持…

【一起来学kubernetes】17、Configmap使用详解

前言概述核心特性创建 ConfigMap使用 ConfigMap1. **环境变量**2. **Volume 挂载**3. **命令行参数** 更新与热重载Docker容器中Java服务使用Configmap**一、通过环境变量注入****步骤说明****示例配置** **二、通过 Volume 挂载配置文件****步骤说明****示例配置** **三、动态…

【八股文】从浏览器输入一个url到服务器的流程

1.url解析与DNS解析 浏览器解析用户输入的URL&#xff0c;提取协议&#xff08;HTTP\HTTPS&#xff09;、域名、端口及路径等信息 浏览器首先检查本地DNS缓存和系统DNS缓存&#xff0c;若未命中&#xff0c;查询本地hosts文件 最后递归查询向本地DNS服务器发起请求&#xff…

网络空间安全(34)安全防御体系

前言 安全防御体系是一个多层次、多维度的系统&#xff0c;旨在保护组织或个人的信息资产免受各种网络攻击和威胁。 一、技术层面 网络边界防御 防火墙&#xff1a;部署在网络边界&#xff0c;通过设定规则允许或阻止特定流量的进出&#xff0c;保护内部网络不受外部攻击。入侵…

Linux 入门:权限的认识和学习

目录 一.shell命令以及运行原理 二.Linux权限的概念 1.Linux下两种用户 cannot open directory .: Permission denied 问题 2.Linux权限管理 1).是什么 2).为什么&#xff08;权限角色目标权限属性&#xff09; 3).文件访问者的分类&#xff08;角色&#xff09; 4).文…