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

C++/数据结构:AVL树

目录

一、AVL树的概念

二、AVL树的实现

2.1节点定义

 2.2节点插入

三、AVL树的旋转

3.1新节点插入较高左子树的左侧:右单旋

3.2新节点插入较高右子树的右侧:左单旋

3.3新节点插入较高左子树的右侧---左右:先左单旋再右单旋

3.4新节点插入较高右子树的左侧---右左:先右单旋再左单旋

四、AVL树的性能


一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M. A delson- V elskii
和E.M. L andis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
$O(log_2 n)$,搜索时间复杂度logn。

二、AVL树的实现

2.1节点定义

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

 2.2节点插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性。
pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
 1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
 2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
 1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足 AVL树的性质,插入成功
 2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新
 3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}			}cur = new Node(kv);if (parent->_kv.first>cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;} cur->_parent = parent;//调整avl树的结构,处理parent的平衡因子while (parent){if (parent->left == cur){parent->bf--;}else if (parent->right == cur){parent->bf++;}//不断向上更改avl树中的bf平衡因子if (parent->_bf == 1 || parent->_bf == -1){//更新去上面的父节点的bfparent = parent->_parent;cur = 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)//左边高的右边高,先左旋再右旋{RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent)//右边高的左边高,先右旋再左旋}else{assert(false);}break;}else{assert(false);}}return true;}

三、AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

3.1新节点插入较高左子树的左侧:右单旋

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
1. 30节点的右孩子可能存在,也可能不存在。
2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树。
void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;if (subLR) subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (pparent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left == subL;}else{pparent->_right == subL;}subL->_parent = pparent;}subL->_bf = parent->_bf = 0;return true;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);  if (bf == 1)//左边高的右边高,新增节点在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增节点在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增节点直接导致出现左边高的右边高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3.2新节点插入较高右子树的右侧:左单旋

和右单旋的思路以及实现方式大相径庭。只需略微改动。

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

3.3新节点插入较高左子树的右侧---左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
考虑平衡因子的更新。
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);  if (bf == 1)//左边高的右边高,新增节点在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增节点在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增节点直接导致出现左边高的右边高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3.4新节点插入较高右子树的左侧---右左:先右单旋再左单旋

参考右左双旋

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;subRL->_bf = 0;subR->_bf = 0;}else if (bf == -1){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 0){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 0;}else{assert(false);}}
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR。
当pSubR的平衡因子为1时,执行左单旋。
当pSubR的平衡因子为-1时,执行右左双旋。
2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL。
当pSubL的平衡因子为-1是,执行右单旋。
当pSubL的平衡因子为1时,执行左右双旋。
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

四、AVL树的性能

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

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

相关文章:

C++/数据结构:AVL树

目录 一、AVL树的概念 二、AVL树的实现 2.1节点定义 2.2节点插入 三、AVL树的旋转 3.1新节点插入较高左子树的左侧&#xff1a;右单旋 3.2新节点插入较高右子树的右侧&#xff1a;左单旋 3.3新节点插入较高左子树的右侧---左右&#xff1a;先左单旋再右单旋 3.4新节点插…...

Mysql数据库_max_allowed_packet参数详解

本文目录 参数含义查看max_allowed_packet参数值修改max_allowed_packet参数值修改配置文件方式&#xff08;需要重启&#xff09;直接修改配置方式&#xff08;不需要重启&#xff09;注意事项 出现场景 参数含义 max_allowed_packet参数指的是MySQL服务端或者客户端接收一次…...

【数仓】Hadoop集群配置常用参数说明

Hadoop集群中&#xff0c;需要配置的文件主要包括四个 配置核心Hadoop参数&#xff1a; 编辑core-site.xml文件&#xff0c;设置Hadoop集群的基本参数&#xff0c;如文件系统、Hadoop临时目录等。 配置HDFS参数&#xff1a; 编辑hdfs-site.xml文件&#xff0c;设置HDFS的相关参…...

【go从入门到精通】什么是go?为什么要选择go?

go的出生&#xff1a; go语言&#xff08;或Golang&#xff09;是Google开发的开源编程语言&#xff0c;诞生于2006年1月2日下午15点4分5秒&#xff0c;于2009年11月开源&#xff0c;2012年发布go稳定版。Go语言在多核并发上拥有原生的设计优势&#xff0c;Go语言从底层原生支持…...

MySQL篇—执行计划介绍(第二篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…...

nest.js使用nest-winston日志一

nest-winston文档 nest-winston - npm 参考&#xff1a;nestjs中winston日志模块使用 - 浮的blog - SegmentFault 思否 安装 cnpm install --save nest-winston winstoncnpm install winston-daily-rotate-file 在main.ts中 import { NestFactory } from nestjs/core; im…...

LeetCode刷题笔记之二叉树(四)

一、二叉搜索树的应用 1. 700【二叉搜索树中的搜索】 题目&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。代码&a…...

【MATLAB源码-第150期】基于matlab的开普勒优化算法(KOA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 开普勒优化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一个虚构的、灵感来自天文学的优化算法&#xff0c;它借鉴了开普勒行星运动定律的概念来设计。在这个构想中&#xff0c;算法模仿行星围绕太阳的…...

最佳实践:Websocket 长连接状态如何保持

WebSocket 是一种支持通过单个 TCP 连接进行全双工通信的协议&#xff0c;相较于传统的 HTTP 协议&#xff0c;它更适合需要实时交互的应用场景。此协议在现代 Web 应用中扮演着至关重要的角色&#xff0c;尤其是在需要实时更新和通信的场合下维持持久连接。本文将探讨 WebSock…...

Unity AStar寻路算法与导航

在游戏开发中&#xff0c;寻路算法是一个非常重要的部分&#xff0c;它决定了游戏中角色的移动路径。Unity作为一款流行的游戏开发引擎&#xff0c;提供了许多内置的寻路算法&#xff0c;其中最常用的就是AStar算法。AStar算法是一种基于图的搜索算法&#xff0c;通过启发式搜索…...

JavaScript最新实现城市级联操作,json格式的数据

前置知识&#xff1a; <button onclick"doSelect()">操作下拉列表</button><hr>学历&#xff1a;<select id"degree"><option value"0">--请选择学历--</option><option value"1">专科<…...

SD NAND:为车载显示器注入智能与安全的心脏

SD NAND 在车载显示器的应用 在车载显示器上&#xff0c;SD NAND&#xff08;Secure Digital NAND&#xff09;可以有多种应用&#xff0c;其中一些可能包括&#xff1a; 导航数据存储&#xff1a; SD NAND 可以用于存储地图数据、导航软件以及车载系统的相关信息。这有助于提…...

矩阵的对角化

概述 对角化矩阵是线性代数中的一个重要概念&#xff0c;它涉及将一个方阵转换成一个对角阵&#xff0c;这个对角阵与原矩阵相似&#xff0c;其主要对角线上的元素为原矩阵的特征值。这样的转换简化了很多数学问题&#xff0c;特别是线性动力系统的求解和矩阵的幂运算。下面是…...

React编写组件时,如何省略.tsx后缀

省略.tsx后缀 当tsconfig.json配置了&#xff0c;需要重启后才会生效 {"compilerOptions": {"allowJs": true,"jsx": "react-jsx",} }当进行以上配置后&#xff0c;导入组件时添加后缀&#xff0c;Eslint报错如下&#xff1a; An im…...

移动端的React项目中如何配置自适应和px转rem

创建项目 create-react-app project-name 启动项目 npm start 下载自适应和px转rem的插件 自适应的&#xff1a; npm install lib-flexible --save px转rem的&#xff1a;npm install postcss-pxtorem5.1.1 --save-dev 创建craco.config.js配置文件 在package.json中…...

TypeScript 结合 React 开发时候 , React.FunctionComponent 解释

在 TypeScript 结合 React 开发时&#xff0c;React.FC&#xff08;或 React.FunctionComponent&#xff09;是一个泛型类型&#xff0c;它用于定义函数组件的类型。这个类型定义了函数组件的结构和预期行为&#xff0c;并且提供了泛型支持&#xff0c;以便你可以指定组件 prop…...

2280. 最优标号(最小割,位运算)#困难,想不到

活动 - AcWing 给定一个无向图 G(V,E)&#xff0c;每个顶点都有一个标号&#xff0c;它是一个 [0,2^31−1] 内的整数。 不同的顶点可能会有相同的标号。 对每条边 (u,v)&#xff0c;我们定义其费用 cost(u,v) 为 u 的标号与 v 的标号的异或值。 现在我们知道一些顶点的标号…...

RestTemplate启动问题解决

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ RestTemplate启动问题解决 问题&#xff1a;在SpringCloud架构项目中配…...

Docker部署前后端服务示例

使用Docker部署js前端 1.创建Dockerfile 在项目跟目录下创建Dockerfile文件&#xff1a; # 使用nginx作为基础镜像 FROM nginx:1.19.1# 指定工作空间 WORKDIR /data/web# 将 yarn build 打包后的build文件夹添加到工作空间 ADD build build# 将项目必要文件添加到工作空间&a…...

方格分割644--2017蓝桥杯

1.用dfs解决&#xff0c;首先这题的方格图形就很像一个走迷宫的类型&#xff0c;迷宫想到dfs&#xff0c;最中心点视为起点&#xff0c;起点有两个小人在这个方格里面对称行动&#xff0c;直到走出迷宫&#xff08;一个人走出来了另一个人就也走出来了&#xff0c;而走过的点会…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...