【C++】AVL树的插入、旋转

目录

  • 一、AVL树介绍
    • 1.1 概念
    • 1.2 定义
  • 二、AVL树的实现
    • 2.1 插入
    • 2.2 旋转
      • 2.2.1 左单旋
      • 2.2.2 右单旋
      • 2.2.3 左右双旋
      • 2.2.4 右左双旋

一、AVL树介绍

1.1 概念

AVL树是高度平衡的二叉搜索树,相比普通的二叉搜索树,它防止了变成单支树的情况。因为AVL树每插入一个新的节点,它都会调整使左右子树的高度差的绝对值不超过1,从而降低了树的高度,提高了搜索效率。

特点:

  • 左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 搜索时间复杂度O( l o g 2 n log_2 n log2n)
  • 有平衡因子控制高度差:右子树高度减去左子树高度

在这里插入图片描述

1.2 定义

AVL树是三叉链结构,即左孩子指针、右孩子指针和双亲指针,多了一个双亲指针方便找到上一个节点。定义它的数据域,为kv模型的类型。定义平衡因子,作用是记录当前节点的左右子树高度之差。

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

二、AVL树的实现

2.1 插入

AVL树的插入过程与二叉搜索树的插入过程是一样的,只不过在此基础上增加了调整节点的平衡因子。所以总结为两个步骤:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

没标数字的是新插入的节点

在这里插入图片描述
调整平衡因子,首先要从cur和parent入手,即新插入的节点和插入节点的上一个节点。如果插入节点cur是parent的右边,则parent的平衡因子+1;反之,则-1。整个调整过程是一个循环,因为刚开始改变的是下面的平衡因子,上面节点的平衡因子也可能会随之改变。循环条件为parent不为空,还要其他条件可以终止循环,下面细讲。

所以根据parent的平衡因子的值可以分为3步:

  1. parent的平衡因子为0
  2. parent的平衡因子为1或者-1
  3. parent的平衡因子为2或者-2

这里说明下,没有parent的平衡因子可能为3/-3的情况,如果出现了说明之前的树有问题

1️⃣parent的平衡因子为0
此情况说明原来的parent的左边或者右边存在一个节点,新插入的节点在parent没有节点的一侧,parent两边都有节点,平衡因子为0,不影响上面的节点,调整结束,跳出循环。

在这里插入图片描述

2️⃣parent的平衡因子为1或者-1
如果parent的平衡因子为1或者-1,则会影响上面的节点,所以让parent节点转换为它的上一个节点,cur转换为parent,向上更新,以次类推。每次循环还是要经过cur是parent的左边还是右边,才能确定parent的平衡因子是+1还是-1。直到cur为根节点,parent为空时循环结束。
在这里插入图片描述

3️⃣parent的平衡因子为2或者-2
当parent向上更新,到某个节点时(有可能不是根节点)它的平衡因子为2或者-2,这时违背了AVL树的规则,因此要进行处理——旋转,来改变树的高度差,使其左右之树的高度差的绝对值不超过1

下图的树插入节点后要发生旋转
在这里插入图片描述

旋转后,高度差恢复平衡,跳出循环。

总结:
AVL树插入节点要进行调整平衡因子,调整结束有3种:
1.parent为空——已经调整到根节点了
2.parent的平衡因子为0——不影响上面节点
3.旋转后——树恢复平衡状态

代码:

//插入
bool Insert(const pair<K, V>& kv)
{//如果根为空if (_root == nullptr){_root = new Node(kv);return true;}//非空Node* cur = _root;Node* parent = nullptr;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 (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0)//不需要修改,直接跳出{break;}else if (parent->_bf == 1 || parent->_bf == -1)//向上更新{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//要旋转{//旋转有4种情况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{RotateRL(parent);//右左双旋-先右后左}break;//旋转后跳出}else{assert(false);//之前树就有问题}}return true;
}

2.2 旋转

这里就开始详细分析AVL树的旋转了,旋转分为4种情况,有左单旋、右单旋、左右双旋和右左双旋。发生这4种旋转的条件不一样,下面逐个分析:

2.2.1 左单旋

整体思路:cur的左孩子变成parent的右孩子(cur的左孩子可能为空),parent变成cur的左孩子,cur连接原来parent的双亲(有可能原来的parent就是根节点,如果是,cur变成根)

先来简单的图示分析:
在这里插入图片描述
parent是平衡因子为2的节点,cur是平衡因子为1的节点,为调整成平衡,parent要变成cur的左孩子,而在此之前,cur的左孩子要先变成parent的右孩子(上面的图节点较少,所以cur没有左孩子),最终平衡。

通过上面的例子,可以基本了解左单旋的整个过程,下面来较多节点的例子:
在这里插入图片描述
定义两个临时变量,subR指向parent的右孩子,即cur的位置(后面的操作就可以不用cur),subRL为subR的左孩子。

按照上面的步骤,subRL变成parent的右孩子,parent变成subR的左孩子,定义一个ppnode节点为原来parent的双亲,如果parent是在ppnode的左边,则ppnode的左边连接subR,;反之,则右边连接subR。如果parent原来是根节点,那么根节点就变成subR。最后修改parent和subR的平衡因子,它们的平衡因子都变成了0
在这里插入图片描述

有个问题,旋转有4种情况,怎么知道用哪种呢?其实上面的两种图示已经显示出左单旋的条件。当parent的平衡因子为2,并且cur的平衡因子为1时,要进行的旋转为左单旋。

代码:

//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//不为空if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppnode = parent->_parent;parent->_parent = subR;//处理parent如果为根if (parent == _root){_root = subR;subR->_parent = nullptr;}//不为根,处理与ppnode的连接else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}//修改平衡因子parent->_bf = 0;subR->_bf = 0;
}

2.2.2 右单旋

整体思路:cur的右孩子变成parent的左孩子,parent变成cur的右孩子,cur连接原来parent的双亲。

简单图示:
在这里插入图片描述
parent是平衡因子为-2的节点,cur是平衡因子为-1的节点,为调整成平衡,cur的右孩子要先变成parent的右孩子,parent变成cur的右孩子,然后修改平衡因子,最终平衡。

用较多节点的例子来分析:
在这里插入图片描述
定义两个临时变量,subL指向parent的左孩子,subLR指向subL的右孩子。

subLR变成parent的左孩子,parent变成subL的右孩子,定义一个ppnode节点为原来parent的双亲,步骤同左单旋。最后修改parent和subL的平衡因子,它们的平衡因子都变成了0
在这里插入图片描述
通过例子可知,发生右单旋的条件是parent的平衡因子为-2,cur的平衡因子为-1

代码:

//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//不为空if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}//修改平衡因子parent->_bf = 0;subL->_bf = 0;
}

2.2.3 左右双旋

左右双旋是先左单旋,再右单旋。左单旋的是parent的左子树,右单旋的是包括parent的当前树。既然已经有单旋了,为什么还要双旋呢?如图:
在这里插入图片描述
通过上面例子可知,如果插入的位置与前面(左单旋或者右单旋插入时的位置)不同,只有左或者右旋转不能使树变平衡,因此要进行双旋。

左右双旋的情况主要分为以下3类:
在这里插入图片描述
当插入的节点是subLR时,subLR的平衡因子为0;插入的节点在subLR的左边,subLR的平衡因子为-1,插入的节点在subLR的右边,subLR的平衡因子为1;通过图示可知,发生左右双旋的条件是parent的平衡因子为-2,cur的平衡因子为1。这3种情况经过左右双旋后,subLR的平衡因子都变成0,但是parent和subL的平衡因子是有区别的。

下面来看看3种情况的旋转:
1️⃣插入的新节点是subLR
在这里插入图片描述

2️⃣插入的新节点在subLR的左边
在这里插入图片描述

3️⃣插入的新节点在subLR的右边
在这里插入图片描述

通过上面的图发现,当插入的新节点是subLR,subLR的平衡因子为0,旋转后,subL和parent的平衡因子也为0;插入的新节点在subLR的左边,subLR的平衡因子为-1,旋转后,subL的平衡因子为0,parent的平衡因子为1;插入的新节点在subLR的右边,subLR的平衡因子为1,旋转后,subL的平衡因子为-1,parent的平衡因子为0。

代码:

//左右双旋-先左后右
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//提前保存原来的bfRotateL(subL);RotateR(parent);//旋转后,不同情况最后的bf会不一样subLR->_bf = 0;//确定的if (bf == -1){subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

2.2.4 右左双旋

右左双旋是先右单旋,再左单旋。右单旋的是parent的右子树,左单旋的是包括parent的当前树。

右左双旋也分为3类:
在这里插入图片描述
当插入的节点是subRL时,subRL的平衡因子为0;插入的节点在subRL的左边,subRL的平衡因子为-1;插入的节点在subRL的右边,subRL的平衡因子为1;通过图示可知,发生右左双旋的条件是parent的平衡因子为2,cur的平衡因子为-1。这3种情况经过右左双旋后,subRL的平衡因子固定都变成了0,但是parent和subL的平衡因子有区别。

下面是3种情况的旋转:
1️⃣插入的新节点是subRL
在这里插入图片描述

2️⃣插入的新节点在subRL的左边
在这里插入图片描述

3️⃣插入的新节点在subRL的右边
在这里插入图片描述

插入的新节点是subRL,subRL的平衡因子为0,旋转后,subR和parent的平衡因子也为0;插入的新节点在subRL的左边,subRL的平衡因子为-1,旋转后,subR的平衡因子为1,parent的平衡因子为0;插入的新节点在subRL的右边,subRL的平衡因子为1,旋转后,subR的平衡因子为0,parent的平衡因子为-1。

代码:

//右左双旋-先右后左
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == -1){subR->_bf = 1;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

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

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

相关文章

云服务器2核4G配置,阿里云和腾讯云哪个便宜?性能更好?

租用2核4G服务器费用多少&#xff1f;2核4G云服务器多少钱一年&#xff1f;1个月费用多少&#xff1f;阿里云2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年&#xff1b;腾讯云轻量2核4G服务器5M带宽165元一年、252元15个月、540元三…

C语言快速入门之字符函数和字符串函数

一.字符分类函数和字符转换函数 C语言中有一系列的函数专门做字符分类的&#xff0c;就是区分一个字符是属于什么类型的&#xff0c;头文件是 #include <ctype.h> 以下是具体函数&#xff1a; 这些函数的使用方法类似&#xff0c;我们写出一些代码来举例。 例如&…

解决ChatGPT发送消息没有反应

ChatGPT发消息没反应 今天照常使用ChatGPT来帮忙码代码&#xff0c;结果发现发出去的消息完全没有反应&#xff0c;即不给我处理&#xff0c;也没有抱任何的错误&#xff0c;按浏览器刷新&#xff0c;看起来很正常&#xff0c;可以查看历史对话&#xff0c;但是再次尝试还是一…

基于springboot+vue的线上教育系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 三、库表设计 四、论文 前言 现在大家的生活方式正在被计算机的发展慢慢改变着&#xff0c;学习方式也逐渐由书本走向荧幕,我认为这并不是不能避免的,但说实话,现在的生活方式与以往相比有太大的改变&#xff0c;人们的娱乐方式不仅仅…

2023年第三届中国高校大数据挑战赛第二场赛题C:用户对博物馆评论的情感分析(附上代码与详细视频讲解)

问题重述&#xff1a; 博物馆是公共文化服务体系的重要组成部分。国家文物局发布&#xff0c; 2021 年我国新增备案博物馆 395 家&#xff0c;备案博物馆总数达 6183 家&#xff0c;排名全球前列&#xff1b;5605 家博物馆实现免费开放&#xff0c;占比达 90%以上&#xff1b;…

Dynamic Wallpaper v17.4 mac版 动态视频壁纸 兼容 M1/M2

Dynamic Wallpaper Engine 是一款适用于 Mac 电脑的视频动态壁纸&#xff0c; 告别单调的静态壁纸&#xff0c;拥抱活泼的动态壁纸。内置在线视频素材库&#xff0c;一键下载应用&#xff0c;也可导入本地视频&#xff0c;同时可以将视频设置为您的电脑屏保。 应用介绍 Dynam…

mysql 事务 及 Spring事务 初论

1.事务概述 事务是一种机制&#xff0c;用以维护数据库确保数据的完整性和一致性。。事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。MySQL中主要使用INN…

“遥感新纪元:GPT技术引领地球观测的智慧革新“

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本文重点介绍ChatGPT在遥感中的应用&#xff0c;人工智能…

设计模式八:观察者模式

文章目录 1、观察者模式2、示例3、spring中的观察者模式3.1 spring观察者模式的使用3.2 spring观察者模式原理解析 1、观察者模式 观察者模式&#xff08;Observer Design Pattern&#xff09;,也叫做发布订阅模式&#xff08;Publish-Subscribe Design Pattern&#xff09;、模…

适合上班族的副业:steam游戏搬砖1天3小时,月入8K

互联网新时代&#xff0c;做副业的人越来越多。如果能充分利用下班后的时间&#xff0c;还真能赚到不少钱。steam游戏搬砖项目就是这样一个非常适合上班的副业&#xff0c;只要用心去操作&#xff0c;一个月至少收入两三千&#xff0c;多的轻松上万。 steam游戏搬砖项目其实做的…

2.4 RK3399项目开发实录-使用 SD 卡升级固件(物联技术666)

通过百度网盘分享的文件&#xff1a;嵌入式物联网单片… 链接:https://pan.baidu.com/s/1Zi9hj41p_dSskPOhIUnu9Q?pwd8qo1 提取码:8qo1 复制这段内容打开「百度网盘APP 即可获取」 本文主要介绍了如何将实现使用MicroSD卡&#xff0c;更新主板上的固件。但也仅限于固件小于4G大…

RabbitMQ - 01 - 快速入门

目录 界面总览 创建队列 选择默认交换机 发布消息 查看消息 通过实现以下目标快速入门 界面总览 RabbitMQ Management 界面总览 通道: 传输消息的通道 路由: 接收和路由(分发)消息 队列: 存储消息 消息队列的流程: 生产者将消息发送给路由,路由分发消息到各个队列存储…

超级实用导出各种excel,简单粗暴,无需多言

1、加入准备的工具类 package com.ly.cloud.utils.exportUtil;import java.util.Map;public interface TemplateRenderer {Writable render(Map<String, Object> dataSource) throws Throwable;}package com.ly.cloud.utils.exportUtil;import java.util.Map;public int…

Python之禅——跟老吕学Python编程

Python之禅——跟老吕学Python编程 Python之禅1.**Beautiful is better than ugly.**2.**Explicit is better than implicit.**3.**Simple is better than complex.**4.**Complex is better than complicated.**5.**Flat is better than nested.**6.**Spare is better than den…

【Tailwind + Vue3】100行代码手写一个客服组件

【Tailwind Vue3】100行代码手写一个客服组件 通常在官网页面上&#xff0c;都会有一个在右下角的客服小组件&#xff0c;有几个按钮&#xff0c;显示电话&#xff0c;微信等信息&#xff1a; 主要有以下几个难点&#xff1a; 动态类名绑定&#xff1a; 在迭代生成的每个工具…

AI绘画怎么用?详细教程在这里!

AI绘画是一种利用人工智能技术来创作艺术作品的方式。以下是一个详细的AI绘画的详细教程&#xff0c;介绍AI绘画怎么用? 1. 选择合适的AI绘画工具&#xff1a;市面上有许多AI绘画工具供用户选择&#xff0c;如建e网AI、DeepArt、DALL-E等。用户可以根据自己的需求和兴趣&#…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 1、线条折线曲面

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 代码: import pandas as pd import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D from matplotlib.colors import ListedColor…

Tab组件的编写与动态日期的函数封装

src\components\Tab\Icon.vue 底部导航栏子组件。 <template><router-link :to"path" class"tab-icon"><i class"icon">{{iconText}}</i><p class"text"><slot>{{ tabText }}</slot></…

2024.3.12 C++

1、自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height) 定义公有成员函数初始化函数:void init(int w, int h)更改宽度的函数:set w(int w)更改高度的函数:set h(int h)输出该矩形的周长和面积函数:void show() #include <iostream>using nam…

高项-项目整合管理

项目整合管理的目标 资源分配平衡竞争性需求研究各种备选方法裁剪过程以实现项目目标管理各个项目管理知识域之间的依赖关系 项目整合管理的过程 制定项目章程制定项目管理计划指导与管理项目工作管理项目知识监控项目工作实施整体变更控制结束项目或阶段 七个过程和五大过…