AVL树(靠平衡因子判断翻转的二叉搜索树)

之前我们在学习set和map的时候说,他们的底层数二叉搜索树,其实这是不准确的,准确的来说他应该是AVL树
那么什么事AVL树呢啊?

但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此>map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下:

在这里插入图片描述
棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1
如果一棵二叉搜索树是高度平衡的,它就是AVL树。

AVL树节点的定义

在这里插入图片描述

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||==2)的规则的时候进行旋转调整:

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)//插入比较的值是kv里面的key值
			{
				parent = cur;
				cur = cur->right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->left;
			}
			else//如果有相等的值,那么不插入直接返回false
			{
				return false;
			}
		}
		//找到了要插入的那个节点的位置 cur==nullpter
		cur = new Node(kv);//先构造一个节点
		//然后判断是插入在父节点的做还是右
		if (parent->_kv.first<kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//从新更新父节点指针

以上的插入代码和二叉搜索树一样
不一样的是之后的判断调整代码:
平衡因子的更新:
在这里插入图片描述

右单旋:

在这里插入图片描述
模型特点:
在这里插入图片描述
旋转:
在这里插入图片描述
代码书写原理:

在这里插入图片描述
在这里插入图片描述

左单旋,原理和右旋转相同

在这里插入图片描述

代码:


	//左单旋函数 : parent=2;  说明右边高  用左旋转法
	void rotateL(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;

		parent->right = subRL;
		if (subRL)//subR也可能是空,那么他就不存在_parent指针
		{
			subRL->_parent = parent;
		}
		Node* pparent = parent->_parent;//记录parent的上一个节点
		subR->left = parent;
		parent->_parent = subR;

		if (parent == _root)//如果是根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else//如果不是根,就要判断翻转的那个子树是右子树还是左子树
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->right = subR;
			}
			subR->_parent = pparent;
		}
		//翻转完成后还需要改变节点的平衡因子
		parent->_bf = 0;
		subR->_bf = 0;

	}
	//右单旋函数 : parent=-2;  说明左边高  用右旋转法
	void rotateR(Node* parent)
	{
		Node* cuL = parent->_left;
		Node* cuLR = cuL->_right;

		parent->_left = cuLR;
		Node* pparent = parent->_parent;
		cuL->right = parent;
		if (cuLR)
		{
			cuLR->_parent = parent;
		}
		parent->_parent = cuL;

		if (parent == _root)
		{
			_root = cuL;
			cul->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cuL;
			}
			else
			{
				pparent->_right = cuL;
			}
			cuL->_parent = pparent;
		}
		parent->_bf = 0;
		cuL->bf = 0;
	}

上面螚用左单旋的条件是:插入的节点是在parent的右节点的右节点上
单旋的条件是:插入的节点是在parent的左节点的左节点上
他们具有方向一致性,但是如果插入的节点不一致呢

     1、插入的节点在parent的左节点的右节点上

那么先对30(把30这个节点看做parent)进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
在这里插入图片描述
左旋和右旋的代码可以直接调用之前的,但是平衡因子是如何更新的呢?
先左旋在右旋的插入有三种情况:
在这里插入图片描述

     2、插入的节点在parent的右节点的左节点上

那么先对90(把90这个节点看做parent)进行左单旋,然后再对30进行右单旋,旋转完成后再考虑平衡因子的更新。
在这里插入图片描述
代码原理和先左旋再右旋相同

void rotateLR(Node* parent)//先左后右
	{
		Node* subL = parent->_left;
		Node* subLR = subL->right;

		rotateL(parent->_left);
		rotateR(parent->_right);

		

		int bf = subLR->_bf;
		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->bf = 1;
		}else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->bf = 0;
		}else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->bf = 0;
		}
		

	}

	void rotateRL(Node* parent)//先右后左
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		rotateR(parent->_right);
		rotateL(parent->_left);

		int bf = subRL->_bf;
		if (bf == -1)
		{
			subRL->_bf = 0;
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			ubRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == 0)
		{
			ubRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = 0;
		}

	}

综上就已经把旋转的所有条件的都囊括了

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

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

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

相关文章

深度学习pytorch——多分类问题(持续更新)

回归问题 vs 分类问题&#xff08;regression vs classification&#xff09; 回归问题&#xff08;regression&#xff09; 1、回归问题的目标是使预测值等于真实值&#xff0c;即predy。 2、求解回归问题的方法是使预测值和真实值的误差最小&#xff0c;即minimize dist(p…

数据结构与算法4-冒泡排序

文章目录 1. 认识冒泡排序2. 图示2.1 图示12.2 图示2 3. 代码 1. 认识冒泡排序 双层for循环&#xff0c;每次选出最大的数“浮”到数组的最后面&#xff1b;时间复杂度O( n 2 n^2 n2)&#xff0c;空间复杂度O(1);重复地遍历待排序的数列&#xff0c;一次比较两个元素&#xff…

【吊打面试官系列】Redis篇 -Redis 集群会有写操作丢失吗?为什么?

大家好&#xff0c;我是锋哥。今天分享关于 【Redis 集群会有写操作丢失吗&#xff1f;为什么&#xff1f;】 面试题&#xff0c;希望对大家有帮助&#xff1b; Redis 集群会有写操作丢失吗&#xff1f;为什么&#xff1f; Redis 并不能保证数据的强一致性&#xff0c;这意味这…

你可敢信这是 AI 写的歌?suno 真的惊到我了!

你可敢信这是 AI 写的歌&#xff1f;suno 真的惊到我了&#xff01; AI 音乐平台 suno 横空出世&#xff0c;效果惊人&#xff0c;我赶紧试了一下&#xff0c;amazing&#xff01;&#xff01;&#xff01; suno创作 - 背叛 这是我随意创作的&#xff0c;这几天对诅咒前男友那首…

②零基础MySQL数据库-MySQL约束

作用 表在设计的时候加入约束的目的就是为了保证表中的记录完整性和有效性&#xff0c;比如用户表有些列的值&#xff08;手机号&#xff09;不能为空&#xff0c;有些列的值&#xff08;身份证号&#xff09;不能重复 分类 主键约束(primary key) PK 自增长约束(auto_increme…

Web前端全栈HTML5通向大神之路

本套课程共三大阶段&#xff0c;六大部分&#xff0c;是WEB前端、混合开发与全栈开发必须要掌握的技能&#xff0c;从基础到实践&#xff0c;是从编程小白成长为全栈大神的最佳教程&#xff01; 链接&#xff1a;https://pan.baidu.com/s/1S_8DCORz0N2ZCdtJg0gHsw?pwdtjyv 提取…

苍穹外卖-Redis部分

P49 课程介绍 Redis入门 P50 redis 一个基于内存的key-value结构数据库。&#xff08;mysql基于磁盘存储&#xff0c;二维表存储&#xff09;redis是键值对形式。 基于内存存储&#xff0c;读写性能高。一般适合存储热点数据&#xff0c;热点商品、资讯、新闻等。 我的安装…

计算方法实验2:列主元消元法和Gauss-Seidel迭代法解线性方程组

Task 即已知 y 0 0 , y 100 1 y_00,y_{100}1 y0​0,y100​1&#xff0c;解线性方程组 A y b \mathbf{A}\mathbf{y} \mathbf{b} Ayb&#xff0c;其中 A 99 99 [ − ( 2 ϵ h ) ϵ h 0 ⋯ 0 ϵ − ( 2 ϵ h ) ϵ h ⋯ 0 0 ϵ − ( 2 ϵ h ) ⋯ 0 ⋮ ⋮ ⋱ ⋱ ⋮ 0 0 ⋯…

短视频矩阵系统源头技术开发--每一次技术迭代

短视频矩阵系统源头开发3年的我们&#xff0c;肯定是需求不断的迭代更新的&#xff0c;目前我们已经迭代了3年之久&#xff0c;写技术文章已经写了短视频矩阵系统&#xff0c;写了3年了&#xff0c;开发了3年了 短视频矩阵获客系统是一种基于短视频平台的获客游戏。短视频矩阵系…

基因在各个细胞系表达情况

从CCLE下载数据得到基因在每个细胞系中的 现在从DepMap: The Cancer Dependency Map Project at Broad Institute 需要先选择Custom Downloads 就可以下载数据进行处理了&#xff1a; rm(list ls()) library(tidyverse) library(ggpubr) rt <- data.table::fread("…

基于SpringBoot校园外卖服务系统设计与实现

点赞收藏关注 → 私信领取本源代码、数据库一、项目概述 项目名称&#xff1a;基于SpringBoot校园外卖服务系统设计与实现 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 主要技术&#xff1a;SpringBootMybatisMySQL 运行环境&#xff1a;Windows7以上、JDK…

QT----给程序添加上任务栏托盘图标和退出

让我们的程序拥有任务栏托盘图标&#xff0c;实现程序后台运行&#xff0c;退出等功能 1、关闭程序保持后台 重写关闭事件,忽略点击窗口关闭 void MainWindow::closeEvent(QCloseEvent *event) {// 隐藏窗口&#xff0c;而不是真正关闭setVisible(false);// 忽略关闭事件&am…

【蓝牙协议栈】【BLE】低功耗蓝牙配对绑定过程分析(超详细)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】和【Android Bluetooth Stack】专栏会持续更新中.....敬请期待&#xff01…

Java双指针算法

参考&#xff1a; 【Java版本】常用代码模板1——基础算法 模板题参考实现 - AcWing 【Java版本】常用代码模板2——数据结构 模板题参考实现 - AcWing 题目一&#xff1a; 输入&#xff1a;abc def ghi 输出&#xff1a;abc def ghi 题解&#xff1a; public class …

☆【前后缀】【双指针】Leetcode 42. 接雨水

【前后缀】【双指针】Leetcode 42. 接雨水 解法1 前后缀分解解法2 双指针 ---------------&#x1f388;&#x1f388;42. 接雨水 题目链接&#x1f388;&#x1f388;------------------- 解法1 前后缀分解 维护一个前缀&#xff08;左侧最高&#xff09;后缀&#xff08;右侧…

基于python+vue高校门诊管理系统flask-django-php-nodejs

课题主要采用python开发语言、django框架和MySQL数据库开发技术以及基于Eclipse的编辑器。系统主要包括用户、用户充值、医生、挂号信息、检查开药、药品信息、药品入库、取药出库等功能&#xff0c;从而实现智能化的管理方式&#xff0c;提高工作效率。 语言&#xff1a;Pytho…

全流程基于GIS、python机器学习技术的地质灾害风险评价与信息化建库教程

原文链接&#xff1a;全流程基于GIS、python机器学习技术的地质灾害风险评价与信息化建库https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247598798&idx6&sn29597597dc134060576998b3302467f8&chksmfa820329cdf58a3fa73c04ac20a28fab7c7ee8fb15d0f8ac50…

python处理Excel的方法之xlrd

python处理Excel常用到的模块是xlrd。使用xlrd可以非常方便的处理Excel文档&#xff0c;下面介绍一下基本用法 打开文件 import xlrd data xlrd.open_workbook("c:\\skills.xls") 获取一个工作表 table data.sheet_by_name(uskills) #也可以 table data.sheet_by_…

测试CAN功能是否使能成功

一. 简介 上一篇文章学习了在 kernel内核源码如何使能 Linux 内核自带的 FlexCAN 驱动。通过配置kernel来实现。文章如下&#xff1a; 本文验证&#xff0c;开发板加载新生成的 zImage内核镜像文件&#xff0c;确定 CAN驱动是否已经成功使能。 二. 测试CAN驱动是否使能成功…

Go --- 编程知识点及其注意事项

new与make 二者都是用于内存分配&#xff0c;当声明的变量是引用类型时&#xff0c;不能给该变量赋值&#xff0c;因为没有分配空间。 我们可以用new和make对其进行内存分配。 首先说说new new函数定义 func new(Type) *Type传入一个类型&#xff0c;返回一个指向分配好该…