当前位置: 首页 > news >正文

数据结构·AVL树

1. AVL树的概念

        二叉搜索树虽可以缩短查找的效率,但如果存数据时接近有序,二叉搜索将退化为单支树,此时查找元素效率相当于在顺序表中查找,效率低下。因此两位俄罗斯数学家 G.M.Adelson-Velskii 和E.M.Landis 在1962年发明了一种解决上述问题的方法:当二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度差不大于 1 的绝对值,即可降低树的高度,从而减少平均搜索长度。

        一个标准的AVL树任意节点的左右子树高度差的绝对值不大于1,我们将记录高度差的数据称为平衡因子。

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

                                        

2. AVL树节点的定义

                ​​​​​​​        

        我们根STL库保持一致,将key和value合并定义成pair类型,再在节点中添加一个平衡因子。

3. AVL树的插入

        关于平衡因子,我们可以通过其定义得知这样两条条性质:

        1. 插入节点时只会影响到祖先的平衡因子,而不会影响到其他节点的平衡因子。

        2. 父节点右侧插入节点其平衡因子 +1,左侧插入节点其平衡因子 -1

        对于插入节点的父节点来说,插入后父节点的平衡因子只会出现3种情况:第一种情况是平衡因子为0 、第二种情况是平衡因子为1 或 -1 、第三种情况是有祖先平衡因子出现 2 或 -2

        插入后父节点平衡因子为0时

        说明在左侧或右侧插入一个节点后,父节点平衡了,但此时并不会影响除父节点外的所有祖先的平衡因子,因此平衡因子不需要再向上更新了。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        插入后父节点平衡因子为 1 或 -1 时

        说明父节点之前的平衡因子一定为0,左侧插入能使父节点变-1,右侧插入能使父节点变1。为什么父节点平衡因子之前一定为0?因为AVL树的平衡因子只可能出现-1、0、1三种情况。

        此时平衡因子需要向上更新

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        插入后有祖先平衡因子出现 2 或 -2

        说明这个祖先更新前是1或-1,新节点插入在高的那棵树上,进一步加剧了高差,此时已经违反高差不大于1的规则了,此时需要旋转处理

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

        平衡因子调整的逻辑就是这样添加到insert函数中的,我会把完整的代码贴到最后的

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4. AVL树的旋转

4.1 左单旋

        插入节点在较高右子树的右侧时就要进行左单旋。

        我这个图中的长方形就代表一颗子树。下面外我们基于这个旋转逻辑编写代码

        经过我们前面更新平衡因子之后找到不符合规则的父节点30,我们进行旋转代码编写的时候不仅仅要弄节点向下的链接内容,还要记得修改节点的parent指针,也就是向上链接的内容。

        我们要更改的主要就是 30(parent节点) 60(subR节点) tree_b(subRL)

                ​​​​​​​        ​​​​​​​        

        但是我们把代码写成这个还是不对的,因为parent的parent没有向下链接。在它向下链接的过程中还有两种情况:就是parent本身就是整颗树的根节点,旋转后subR成为整棵树的根节点;或parent只是树中的一个普通节点,那就要考虑parent的parent向下链接的时候链接左子树还是右子树了。

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​​​​​​​​

4.2 右单旋

        当要在较高左子树的左侧插入新节点就要进行右单旋

        右单旋的思路和代码和左单旋是一样的,反过来而已,先向下更新链接内容,再向上更新链接内容,然后更新parentParent的向下链接,最后调整平衡因子

        ​​​​​​​        ​​​​​​​        ​​​​​​​        

4.3 右左双旋

        当新节点插入在较高右子树的左侧时就要进行右左双旋。

        如果我们单纯以30为parent或者说旋转基点进行单左旋,结果就是60这棵子树变成30的右子树,但是这棵树还是不平衡的,c比d高出了2,此时再以90为基点右旋结果就是又回去了。

        当较高的树在最两侧的时候我们进行单左旋或单右旋是可以让树平衡的,但是如果较高的树在中间的时候我们进行单左右旋就会让这个较高树一直在中间来回变动,而树一直不会平衡。

        所以AVL就用我图中的方案解决了这一问题。

        ​​​​​​​        

        但是如果我们直接把代码写成这样肯定是不行的,因为平衡因子的问题还没有得到更新。

        如果是我图中画的情况也就是说插入在c树中最后的平衡因子就是 -1、0、0

        如果新节点插入在b树中,最后平衡因子就应该是 0、0、1

        如果 h=0,也就是说60是新插入的节点,最后的平衡因子就应该是 0、0、0

        那具体是这三种情况中的哪种,我们可以通过观察插入后60节点的平衡因子来判断

        ​​​​​​​        ​​​​​​​        ​​​​​​​​​​​​​​

4.4 左右双旋

        当在较高左子树右侧插入新节点时要进行左右双旋

        代码与右左双旋一个思路,注意最后要调整平衡因子。

                        

5. AVL树代码

        删除的思路和插入是一致的,但是删除的更新平衡因子会比较复杂,因为删除在旋转了之后平衡因子可能还要继续向上更新。今天先不写删除了,如果之后有精力我会把删除更新上的。

#include<assert.h>
#include<iostream>
using namespace std;template<class K, class V>
struct AVLTNode
{AVLTNode(const pair<K, V>& kv): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}pair<K, V> _kv;AVLTNode<K, V>* _left;AVLTNode<K, V>* _right;AVLTNode<K, V>* _parent;int _bf;	//balance factor 平衡因子
};template<class K, class V>
class AVLTree
{typedef AVLTNode<K, V> Node;public://构造AVLTree() = default;//拷贝构造AVLTree(const AVLTree<K, V>& t){_root = Copy(t._root);}//赋值运算符重载void operator=(const AVLTree<K, V>& t){AVLTree<K, V> new_t(t);std::swap(new_t._root, _root);}//析构~AVLTree(){Destroy(_root);_root = nullptr;}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(const Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->kv);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}//插入bool Insert(const pair<K, V>& kv);//搜索Node* Find(const K& x);//中序遍历void InOrder(){_InOrder(_root);cout << endl;}//树的高度int Height(){return _Height(_root);}//统计节点总个数(插入时可能会有重复数据)int Size(){return _Size(_root);}private://左单旋void RotateL(Node* parent);//右单旋void RotateR(Node* parent);//右左双旋void RotateRL(Node* parent);//左右双旋void RotateLR(Node* parent);//中序遍历(子函数)void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}//树的高度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;}//统计节点总个数(插入时可能会有重复数据)int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}private:Node* _root = nullptr;
};//插入
template<class K, class V>
bool AVLTree<K, V>::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;}elsereturn false;}cur = new Node(kv);if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = 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)//右旋情况{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋{RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋{RotateLR(parent);}break;//旋完就退出更新}else{//出现其他奇奇怪怪的情况直接报错assert(false);}}return true;
}	//搜索
template<class K, class V>
AVLTNode<K, V>* AVLTree<K, V>::Find(const K& x)
{Node* cur = _root;while (cur){if (cur->_kv.first < x){cur = cur->_right;}else if (cur->_kv.first > x){cur = cur->_left;}else{return cur;}}return nullptr;
}//左单旋
template<class K, class V>
void AVLTree<K, V>::RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = parent->_right->_left;//修改向下链接内容parent->_right = subRL;subR->_left = parent;//修改向上链接内容subR->_parent = parent->_parent;parent->_parent = subR;if (subRL)//防止该树点为空{subRL->_parent = parent;}//parent的parent向下链接Node* parentParent = subR->_parent;if (parentParent == nullptr)//整棵树的根{_root = subR;}else{if (parent == parentParent->_right){parentParent->_right = subR;}else{parentParent->_left = subR;}}//调整平衡因子parent->_bf = 0;subR->_bf = 0;
}//右单旋
template<class K, class V>
void AVLTree<K, V>::RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;//修改向下链接内容parent->_left = subLR;subL->_right = parent;//修改向上链接属性subL->_parent = parent->_parent;parent->_parent = subL;if (subLR){subLR->_parent = parent;}//修改parentParentNode* parentParent = subL->_parent;if (parentParent == nullptr){_root = subL;}else{if (parent == parentParent->_right){parentParent->_right = subL;}else{parentParent->_left = subL;}}//更新平衡因子subL->_bf = 0;parent->_bf = 0;
}//右左双旋
template<class K, class V>
void AVLTree<K, V>::RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//更新平衡因子if (bf == 0){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);}
}//左右双旋
template<class K, class V>
void AVLTree<K, V>::RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子if (bf == 0){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);}
}

相关文章:

数据结构·AVL树

1. AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果存数据时接近有序&#xff0c;二叉搜索将退化为单支树&#xff0c;此时查找元素效率相当于在顺序表中查找&#xff0c;效率低下。因此两位俄罗斯数学家 G.M.Adelson-Velskii 和E.M.Landis 在1962年发明了一种解…...

记一次Mycat分库分表实践

接了个活,又搞分库分表。 一、分库分表 在系统的研发过程中,随着数据量的不断增长,单库单表已无法满足数据的存储需求,此时就需要对数据库进行分库分表操作。 分库分表是随着业务的不断发展,单库单表无法承载整体的数据存储时,采取的一种将整体数据分散存储到不同服务…...

数据分析:微生物数据的荟萃分析框架

介绍 Meta-analysis of fecal metagenomes reveals global microbial signatures that are specific for colorectal cancer提供了一种荟萃分析的框架&#xff0c;它主要基于常用的Wilcoxon rank-sum test和Blocked Wilcoxon rank-sum test 方法计算显著性&#xff0c;再使用分…...

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …...

数字图像处理中的常用特殊矩阵及MATLAB应用

一、前言 Matlab的名称来源于“矩阵实验室&#xff08;Matrix Laboratory&#xff09;”&#xff0c;其对矩阵的操作具有先天性的优势&#xff08;特别是相对于C语言的数组来说&#xff09;。在数字图像处理中&#xff0c;为了提高编程效率&#xff0c;我们可以使用多种方式来创…...

vue侦听器(Watch)精彩案例剖析一

目录 watch介绍 监视普通数据类型 监视对象类型 watch介绍 在 Vue 中,watch主要用于监视数据的变化,并执行相应操作。一旦被监视的属性发生变化,回调函数将自动被触发。当在 Vue 中使用watch来响应数据变化时,首先要清楚,watch本质上是一个对象,且必须以对象的…...

HTTP 协议浅析

HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是应用层最重要的协议之一。它定义了客户端和服务器之间的数据传输方式&#xff0c;并成为万维网&#xff08;World Wide Web&#xff09;的基石。本文将深入解析 HTTP 协议的基础知识、工作…...

VsCode | 让空文件夹始终展开不折叠

文章目录 1 问题引入2 解决办法3 效果展示 1 问题引入 可能很多小伙伴更新VsCode或者下载新版本时候 &#xff0c;创建的文件 会出现xxx文件夹/xxx文件夹&#xff0c;看着很不舒服&#xff0c;所以该如何展开所有空文件夹呢&#xff1f; 2 解决办法 找到VsCode的设置 &…...

Centos7_Minimal安装Cannot find a valid baseurl for repo: base/7/x86_6

问题 运行yum报此问题 就是没网 解决方法 修改网络信息配置文件&#xff0c;打开配置文件&#xff0c;输入命令&#xff1a; vi /etc/sysconfig/network-scripts/ifcfg-网卡名字把ONBOOTno&#xff0c;改为ONBOOTyes 重启网卡 /etc/init.d/network restart 网路通了...

Spark_Oracle_II_Spark高效处理Oracle时间数据:通过JDBC桥接大数据与数据库的分析之旅

接前文背景&#xff0c; 当需要从关系型数据库&#xff08;如Oracle&#xff09;中读取数据时&#xff0c;Spark提供了JDBC连接功能&#xff0c;允许我们轻松地将数据从Oracle等数据库导入到Spark DataFrame中。然而&#xff0c;在处理时间字段时&#xff0c;可能会遇到一些挑战…...

力扣 459重复的子字符串

思路&#xff1a; KMP算法的核心是求next数组 next数组代表的是当前字符串最大前后缀的长度 而求重复的子字符串就是求字符串的最大前缀与最大后缀之间的子字符串 如果这个子字符串是字符串长度的约数&#xff0c;则true /** lc appleetcode.cn id459 langcpp** [459] 重复…...

MyBatis XML配置文件

目录 一、引入依赖 二、配置数据库的连接信息 三、实现持久层代码 3.1 添加mapper接口 3.2 添加UserInfoXMLMapper.xml 3.3 增删改查操作 3.3.1 增(insert) 3.3.2 删(delete) 3.3.3 改(update) 3.3.4 查(select) 本篇内容仍然衔接上篇内容&#xff0c;使用的代码及案…...

读写RDS或RData等不同格式的文件,包括CSV和TXT、Excel的常见文件格式,和SPSS、SAS、Stata、Minitab等统计软件的数据文件

R语言是数据分析和科学计算的强大工具,其丰富的函数和包使得处理各种数据格式变得相对简单。在本文中,我们将详细介绍如何使用R语言的函数命令读取和写入不同格式的文件,包括RDS或RData格式文件、常见的文本文件(如CSV和TXT)、Excel文件,和和SPSS、SAS、Stata、Minitab等…...

Android 支持的媒体格式,(二)视频支持格式

视频支持格式&#xff1a; 格式编码器解码器具体说明文件类型 容器格式H.263是是对 H.263 的支持在 Android 7.0 及更高版本中并非必需• 3GPP (.3gp) • MPEG-4 (.mp4) • Matroska (.mkv)H.264 AVC Baseline Profile (BP)Android 3.0 及以上版本是 • 3GPP (.3gp) • MPEG-4…...

密码学原理精解【8】

文章目录 概率分布哈夫曼编码实现julia官方文档建议的变量命名规范&#xff1a;julia源码 熵一、信息熵的定义二、信息量的概念三、信息熵的计算步骤四、信息熵的性质五、应用举例 哈夫曼编码&#xff08;Huffman Coding&#xff09;基本原理编码过程特点应用具体过程1. 排序概…...

2024年钉钉杯大数据竞赛A题超详细解题思路+python代码手把手保姆级运行讲解视频+问题一代码分享

初赛A&#xff1a;烟草营销案例数据分析 AB题综合难度不大&#xff0c;难度可以视作0.4个国赛&#xff0c;题量可以看作0.35个国赛题量。适合于国赛前队伍练手&#xff0c;队伍内磨合。竞赛获奖率50%&#xff0c;八月底出成绩&#xff0c;参赛人数3000队左右。本文将为大家进行…...

unity2D游戏开发01项目搭建

1新建项目 选择2d模板,设置项目名称和存储位置 在Hierarchy面板右击&#xff0c;create Empty 添加组件 在Project视图中右键新建文件夹 将图片资源拖进来&#xff08;图片资源在我的下载里面&#xff09; 点击Player 修改属性&#xff0c;修好如下 点击Sprite Editor 选择第二…...

删除的视频怎样才能恢复?详尽指南

在日常生活中&#xff0c;我们有时会不小心删除一些重要的视频文件&#xff0c;或者在整理存储空间时不慎丢失了珍贵的记忆片段。这时候&#xff0c;我们可以通过一些数据恢复工具和技巧&#xff0c;找回这些被删除的视频。本文将详细介绍几种常见且有效的视频恢复方法&#xf…...

LeetCode160 相交链表

前言 题目&#xff1a; 160. 相交链表 文档&#xff1a; 代码随想录——链表相交 编程语言&#xff1a; C 解题状态&#xff1a; 没思路… 思路 依旧是双指针法&#xff0c;很巧妙的方法&#xff0c;有点想不出来。 代码 先将两个链表末端对齐&#xff0c;然后两个指针齐头并…...

高性能响应式UI部件DevExtreme v24.1.4全新发布

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

高分辨率图像合成归一化流扩展

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 1 摘要 我们提出了STARFlow&#xff0c;一种基于归一化流的可扩展生成模型&#xff0c;它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流&#xff08;TARFlow&am…...

raid存储技术

1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划&#xff0c;涵盖存储系统的布局、数据存储策略等&#xff0c;它明确数据如何存储、管理与访问&#xff0c;为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …...