C++ AVL树底层实现原理

news2024/11/23 10:02:08

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:C++知识分享⏪

🚚代码仓库:C++高阶🚚

🌹关注我🫵带你学习更多C++知识
  🔝🔝



目录

前言

AVL 树

1.1 AVL树的概念

     1.2 AVL树节点的定义     

​编辑

1.3左旋 

 1.3右旋 

1.4双旋 


 

前言

C++ set&&map​​​​​​ 这篇从了解到使用map,map也是搜索二叉树,因为搜索二叉树会出现歪脖子树的情况,本篇就讲如何解决歪脖子树。记住口令 旋转 旋转 旋转 !!! 重要的事说三边。 搜索二叉树加入平衡因子 旋转 就成了传说之中的AVL树

AVL

1.1 AVL树的概念

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

 

这里需要强调一下:AVL树不一定有平衡因子, 还有

递归更新高度:在插入或删除节点后,AVL树会递归地更新从该节点到根节点的所有祖先节点的高度。这是必要的,因为平衡因子是基于节点的高度来计算的。通过递归更新高度,AVL树能够准确地维护每个节点的平衡因子

这里我们用平衡因子来实现,因为比较好理解!!!

     1.2 AVL树节点的定义     

 我们先把AVL节点定义出来

template<class K,class V>
struct 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)
	 {}
	

};
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root = nullptr;
};

插入代码 

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;
}
插入之后 根据下图的规则,我们更新_bf

//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
				parent->_bf--;
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
        

1.3左旋 

对上图新增插入10 这时右树的平衡因子 8 7 就变成了2 绝对值大于1,已经失衡了! 

这时就要通过旋转来调节平衡高度。

  这里强调代码不是重点 画图理解才是重点,前面有二叉树基础,代码非常好写,画图才能理清这里的关系!!!

这里我们从上面得出几个结论:

平衡因子发生变化 决定是否更新父亲节点,而爷爷节点更新也是取决于父亲节点的平衡因子是否发生变化 变了就继续往上更新,不变则不更新。

那就有3种情况:

  • parent-> bf == 1 || parent-> bf == -1 parent的子节点发生变化,继续更新。

       因为 插入之前 parent-> bf == 0 插入之后要么在左、要么在右 说明高度变了

  • parent-> bf ==2 || parent-> bf == -2 这种情况 parent的子树明显不平衡,需要旋转处理
  • parent-> bf == 0 这种情况 说明插入之前的parent这个节点是一高一低的,插入后刚好填到矮的那一边,两边子树的高度平衡不需要处理。

 那既然 parent的bf是2或者-2这两个值需要旋转处理。那什么情况左旋转?

我先说结论:右边的子树高 就左旋转。看图

图画出来就好办了,把图用代码实现。 

else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//旋转
				if (parent->_bf == 2 && cur->_bf == 1) //左旋
				{
					RotateL(parent);
				}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		subR->_left = parent;
		parent->_right = subRL;
		if(subRL)
			subRL->_parent = parent;
		Node* pparent = parent->_parent;
		parent->_parent = subR;
		if (pparent == _root)
		{
			_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;
	}

 1.3右旋 

else if (parent->_bf == -2 && cur->_bf == -1) //右旋
				{
					RotateR(parent);
				}
void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		subL->_right = parent;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		Node* pparent = parent->_parent;
		parent->_parent = subL;
		if (pparent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}

			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
		parent->_bf = subL->_bf = 0;
	}

1.4双旋 

双旋一共有两种情况:

  • 先右旋,再左旋。
  • 先左旋,再右旋 。

那什么时候先右旋,再左旋? 什么时候又先左旋,再右旋?先说结论:

  • 新增节点插入较高左子树的右侧,那么就先左单旋再右单旋。
  • 新增节点插入较高右子树的左侧,那么就先右单旋再左单旋。

先来讲解左右双旋

 

那如果我们复用之前的左右函数就会出现一个问题,parent 和sub*这两个节点的_bf设置为0 如上图 parent的_bf是1 ,subl是0。这时又有三种情况。

第一种情况就入上图所示。

第二种情况 新增节点插入到C这个子树 那么parent的_bf就是0 subl的_fd为-1。

第三种情况 如果h等于0那60就是新增节点,它们的_bf为0没错。

else if (parent->_bf == -2 && cur->_bf == 1) //先左再右
				{
					RotateLR(parent);
				}

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 = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋 

 

第一种情况就入上图所示。

第二种情况 新增节点插入到b这个子树 那么parent的_bf就是0 subR的_fd为1。

第三种情况 如果h等于0那60就是新增节点,它们的_bf为0没错。

else if (parent->_bf == 2 && cur->_bf == -1) //先右再左
				{
					RotateRL(parent);
				}

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 = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

代码 我是单独拆分了,直接复制可能会报错,为了大家好理解我做成模块化!

这里强调的是 其实AVL树,我们根本就不需要手撕,前人已经帮我们造好轮子了,我们自己根本就不需要自己造轮子。主要是画图理解旋转的过程。 

要看代码的完整性可以去看我的码云 

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

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

相关文章

职场成长秘籍:如何利用团队例会提升自己

在职场中&#xff0c;团队例会是一个重要的沟通和协作平台。通过团队例会&#xff0c;我们可以了解项目进展、分享工作经验、解决问题和制定工作计划。那么&#xff0c;如何利用团队例会提升自己&#xff0c;实现职场成长呢&#xff1f;本文将为您揭晓答案。 一、积极发言&…

编程新手必看,python中循环语句学习(14)

介绍&#xff1a; Python3中的循环语句主要有两种&#xff1a;for循环和while循环。 for循环&#xff1a;用于遍历序列&#xff08;如列表、元组、字符串等&#xff09;中的元素&#xff0c;执行相应的代码块。在每次循环中&#xff0c;序列中的一个元素被赋值给一个变量&#…

暴力枚举法

虽然暴力枚举法有时候效率低&#xff0c;时间复杂度高&#xff0c;但是在面对小规模数据集的时候&#xff0c;暴力枚举法往往是很好的思维利器。 B: 01 串的熵&#xff08;5分&#xff09; 问题描述 #include <iostream> #include <cmath> #include <algorithm…

数据治理项目——深铁集团数据治理规划

目录 一、前言 二、数据治理内容与主要措施 2.1 实施背景 2.2 主要举措 2.2.1 制定数据战略目标 2.2.2 绘制数据治理蓝图 2.2.3 绘制数据治理制度 2.2.4 梳理数据资产目录 三、 应用效果 3.1 数据资产可视化管理 3.2 数据标准治理 3.3 集团大数据平台优化建设 一、…

web服务器是如何运行的?tomcat基本原理

tomcat基本流程 tomcat在启动时将webapps下的每个项目中的web.xml读取&#xff0c;获取相关信息。tomcat只关心Servlet 程序、Filter 过滤器、Listener 监听器&#xff0c;不关心其他类。 tomcat接收到请求后会将请求报文转换成一个httpServletRequest对象&#xff0c;包含请求…

【CSS面试题】Flex实现九宫格

考察知识&#xff1a; flex布局 水平垂直居中的实现 初始效果 代码关键&#xff1a;给父盒子添加以下属性 flex-wrap: wrap; /* 允许换行 */justify-content: space-around; /* 主轴对齐方式 */align-content: space-around; /* 多行在侧轴上的对齐方式 */<!DOCTYPE html&…

【干货】【常用电子元器件介绍】【集成电路】(二)--集成电路的识别和检测

声明:本人水平有限,博客可能存在部分错误的地方,请广大读者谅解并向本人反馈错误。 一、 集成电路型号的识别 集成电路的型号一般都在其表面印刷(或者激光刻蚀)出来。集成电路有各种型号,其 命名也有一定的规律,一般是由前缀、数字编号、后缀组成。前缀主要为英文字母,用来…

流动人员人事档案管理信息系统

流动人员人事档案管理信息系统是一种用于管理流动人员的人事档案的信息系统。该系统可以对流动人员的基本信息、工作经历、学历教育、培训记录、奖惩记录等进行管理和统计。通过该系统&#xff0c;可以方便地查询和维护流动人员的人事档案信息&#xff0c;提高人力资源管理的效…

【MATLAB基础】频谱分析

01.引言 频率是单位时间内某事件重复发生的次数,用ω表示,单位是赫兹(Hz)。设m时间内某事件重复发生n次,则此事件发生的频率ω为一。又因为周期定义为重复事件发生的最小时间间隔,故频率也可以表示为周期的倒数:ωn/m,T表示周期。频率是一个很重要的概念,在工程数学中常用于分…

43.基于SpringBoot + Vue实现的前后端分离-疫苗发布和接种预约系统(项目 + 论文)

项目介绍 本次使用Java技术开发的疫苗发布和接种预约系统&#xff0c;就是运用计算机来管理疫苗接种预约信息&#xff0c;该系统是可以实现论坛管理&#xff0c;公告信息管理&#xff0c;疫苗信息管理&#xff0c;医生管理&#xff0c;医院信息管理&#xff0c;用户管理&#x…

2022年蓝桥杯省赛——星期计算

目录 题目链接&#xff1a;1.星期计算 - 蓝桥云课 (lanqiao.cn) 题目描述 思路 代码实现 BigInteger常用方法 BigDecimal常用方法 总结 题目链接&#xff1a;1.星期计算 - 蓝桥云课 (lanqiao.cn) 题目描述 一直今天是星期六&#xff0c;请问 天后是星期几。 注意用数字…

第四百五十六回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 使用方法 3. 内容总结 我们在上一章回中介绍了"overlay_tooltip用法"相关的内容&#xff0c;本章回中将介绍onBoarding包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的onBo…

AI大模型创新交汇点:当AI遇见艺术

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

HarmonyOS实战开发-图片编辑、使用 TextArea 实现多文本输入

介绍 本示例使用 TextArea 实现多文本输入&#xff0c;使用 ohos.app.ability.common 依赖系统的图库引用&#xff0c;实现在相册中获取图片&#xff0c;使用 ohos.multimedia.image 生成pixelMap&#xff0c;使用pixelMap的scale()&#xff0c;crop()&#xff0c;rotate()接口…

LinkedHashMap 是如何保证返回的顺序性的?

LinkedHashMap 源码阅读 public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>先来看一下 LinkedHashMap 的继承关系&#xff0c;它继承了 HashMap&#xff0c;并且实现了 Map 接口。 LinkedHashMap 底层是 数组 链表 的形式&#xf…

HarmonyOS实战案例:【分布式账本】

简介 Demo基于Open Harmony系统使用ETS语言进行编写&#xff0c;本Demo主要通过设备认证、分布式拉起、分布式数据管理等功能来实现。 应用效果 设备认证,获取同一个局域网内的设备ID&#xff0c;并拉起应用 添加数据并在另一台设备显示该数据 开发步骤 开发文档&#xff…

记账本|基于SSM的家庭记账本小程序设计与实现(源码+数据库+文档)

家庭记账本小程序目录 基于SSM的家庭记账本小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、小程序端&#xff1a; 2、后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大…

继承【C/C++复习版】

目录 一、什么是继承&#xff1f;怎么定义继承&#xff1f; 二、继承关系和访问限定符&#xff1f; 三、基类和派生类对象可以赋值转换吗&#xff1f; 四、什么是隐藏&#xff1f;隐藏vs重载&#xff1f; 五、派生类的默认成员函数&#xff1f; 1&#xff09;派生类构造函…

JVM基础:类的生命周期详解

JDK版本&#xff1a;jdk8 IDEA版本&#xff1a;IntelliJ IDEA 2022.1.3 文章目录 一. 生命周期概述二. 加载阶段(Loading)2.1 加载步骤2.2 查看内存中的对象 三. 连接阶段(Linking)3.1 连接之验证3.2 连接之准备3.3 连接阶段之解析 四. 初始化阶段(Initialization)4.1 单个类的…

vivado 使用基本触发器模式

使用基本触发器模式 基本触发器模式用于描述触发条件 &#xff0c; 即由参与其中的调试探针比较器组成的全局布尔公式。当“触发器模式 (Trigger Mode) ”设置为 BASIC_ONLY 或 BASIC_OR_TRIG_IN 时 &#xff0c; 即启用基本触发器模式。使用“基本触发器设置 (Basic Trig…