手撕AVL树(map和set底层结构)(1)

news2024/12/22 17:59:12

troop主页
今日鸡汤:Action may out always bring happiness;but there is no happiness without action.
行动不一定能带来快乐,但不行动一定不行
C++之路还很长

手撕AVL树

  • 一 AVL树概念
  • 二 模拟实现AVL树
    • 2.1 AVL节点的定义
  • 三 插入
    • 更新平衡因子(重点)
  • 四 旋转
    • 1.左单旋
    • 1.1 左单旋完整代码
    • 2 右单旋
    • 2.2 右单旋完整代码
    • 3 双旋一(左+右)
    • 3.2左右双旋完整代码
    • 4 双旋二(右+左)
    • 4.2 右左双旋完整代码
  • 旋转总结
  • 五 验证AVL树的正确性

一 AVL树概念

二叉搜索树虽然可以缩短查找的效率,但是当数接近有序或者二叉搜索数接近单支树,查找的效率就相当于在顺序表中查找。所以为了解决这种极端环境,,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年发明了一种解决上述问题的方法:
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树的性质

  1. 他的左右子树都是AVL树
  2. 左右子树的高度差(平衡因子)的绝对值不超过1 在这里插入图片描述
    注(以下代码中,平衡因子=|右子树高度-左子树高度|

二 模拟实现AVL树

2.1 AVL节点的定义

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance factor
	pair<K, V> _kv;

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

三 插入

我们这里AVL只写插入,插入就可以让我们很好的了解AVL的底层实现了。
AVL树也是二叉搜索树,只是在此基础上增加了平衡因子的调整。所以我们的插入就分成了两部分。

  1. 按照二叉搜索树的规则插入
  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)
			{
				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;

这个就是我们前面已经说过的二叉搜索树的插入规则。现在我们重点看如何更新平衡因子。

更新平衡因子(重点)

平衡因子更新原则:

  • cur插入在parent的左边 平衡因子减减
  • cur插入在parent的右边 平衡因子加加

思考:插入节点后会影响哪些节点的平衡因子?
会影响新插入节点的部分祖先。
是否影响爷爷节点取决于parent的高度是否有变化
首先父亲节点一定会被影响,其次重点考虑的应该是爷爷节点所受的影响。这里比较抽象我们需要借助图像来把每一种可能写出来。
第一种更新后p->_bf0
在这里插入图片描述
这种就是更新之前p的高度为1or-1,新节点插入在了比较矮的那一端,左右平衡。
第二种更新后p->_bf
1or-1
在这里插入图片描述

更新之前,p的高度平衡,cur插入在一侧,p不平衡了,这里p的高度变化爷爷节点也受到了影响,就需要向上更新。
第三种更新后p->_bf==2or-2
在这里插入图片描述
违反了AVL树的规则要进行旋转。
我们现在总结一下,什么情况下更新就结束了。

1.插入后父亲的平衡因子为0,更新结束
2.向上更新到,cur=root的位置时,更新结束
3.违反规则需要旋转,旋转之后,更新结束

		//调整平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				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)
			{
				// 旋转处理
				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;
			}
			//插入树之前这个树就不符合AVL树
			else
			{
				// 插入之前AVL树就有问题
				assert(false);
			}

这部分代码还比较简单,下面就是本篇核心(旋转)

四 旋转

旋转的目的

  • 保持搜索树规则
  • 当先树由不平衡转变为平衡
  • 降低树的高度

1.左单旋

在这里插入图片描述
根据上面的图我们写出下代码。

Node* subR=panret->_right;
Node* subRL=subR->_left;

parent->_right=subRl;
subRL->_parent=parent;

subR->_left=parent;
parent->_parent=subR;

这里还有几个细节问题需要注意
第一点:subRL可能为空,那subRL->_parent=parent;就会有问题。我们要加一个条件判断。
第二点:subR的父亲节点还没有被重新指向,这就会导致下图
在这里插入图片描述
我们就要先保存parent的父亲节点,这又有了一个新的问题,就是parent是不是根节点,所以左单旋的完整代码如下

1.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;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		parent->_bf = 0;
		subR->_bf = 0;
	}

2 右单旋

右单旋就是左旋的变形,理解好左旋,右旋就很好理解了。
在这里插入图片描述
看图写出代码,再根据左旋的注意事项,补全代码的逻辑。

2.2 右单旋完整代码

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

		subL->_bf = 0;
		parent->_bf = 0;
	}

3 双旋一(左+右)

在这里插入图片描述

注意看图,先对subL进行了左单旋,再对整棵树进行了右单旋。
注意:双旋对比单旋多了旋转结束之后平衡因子不是固定的,我们要风分情况把所有的可能性都写出来。
在这里插入图片描述
观察上图,我们发现subRL的平衡因子不同分别为:-1 1 0,这就是我们的解决方案。我们再画出旋转之后的图片。
在这里插入图片描述
分析完之后,就到了最简单的代码环节

3.2左右双旋完整代码

void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);

		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;
		}
		else
		{
			assert(false);
		}
	}

4 双旋二(右+左)

在这里插入图片描述

还是先画出一般图观察,对subR进行右旋,再对整体左旋。
下面的分析与上面的分析类似,我就把图片放在下面,供大家参考。
在这里插入图片描述

4.2 右左双旋完整代码

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

现在再去看上面的更新平衡因子的代码就比较清晰了。

旋转总结

左旋:新节点插入了较高子树的
右旋:新节点插入了较高子树的
双旋:
左+右:新节点插入了较高子树的
右+左:新节点插入了较高子树的

总之一句话:理解旋转我们一定要自己去画图,一定要自己动手,才会理解深刻。

五 验证AVL树的正确性

我们要写一个函数来判断这颗树符不符合AVL树。

	bool _IsBalance(Node* root, int& height)
	{
		if (root == nullptr)
		{
			height = 0;
			return true;
		}

		int leftHeight = 0, rightHeight = 0;
		if (!_IsBalance(root->_left, leftHeight)
			|| !_IsBalance(root->_right, rightHeight))
		{
			return false;
		}

		if (abs(rightHeight - leftHeight) >= 2)
		{
			cout << root->_kv.first << "不平衡" << endl;
			return false;
		}

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

		return true;
	}

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

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

相关文章

mysql基础1——数据存储

mysql数据存储 共有4步 1&#xff09;创建数据库 2)确认字段 3)创建数据表 4)插入数据 1&#xff09;创建数据库 从系统架构看mysql数据库系统依次是数据库服务器&#xff0c;数据库&#xff0c;数据表和数据表的行与列 安装程序-->安装了数据库服务器 所有要做的第…

1.基于Springboot对SpringEvent初步封装

一&#xff1a;前置知识 Spring Event是Spring框架提供的一种事件机制&#xff0c;用于处理组件之间的通信。在复杂的系统中&#xff0c;模块或组件之间的通信是必不可少的。Spring Event可以用于以下场景&#xff1a; 1.系统间解耦&#xff1a;模块或组件之间通过事件进行通…

什么是防抖和节流?有什么区别? 如何实现?

防抖&#xff08;Debounce&#xff09;和节流&#xff08;Throttle&#xff09;是两种常用的技术手段&#xff0c;主要用于控制某个函数在一定时间内触发的次数&#xff0c;以减少触发频率&#xff0c;提高性能并避免资源浪费。 防抖&#xff08;Debounce&#xff09;的工作原…

openEuler-23.03下载、安装

一、下载 下载地址&#xff1a;openEuler下载 | 欧拉系统ISO镜像 | openEuler社区官网 下载版本&#xff1a;openEuler-23.03-x86_64-dvd.iso 二、安装 cd /etc/sysconfig/network-scripts/ vi ifcfg-ens-33## 要修改部分 BOOTPROTOstatic## 新增部分 IPADDR192.168.1.128 …

AI觉醒派: 探索与AI协作路径,觉醒更强大的自己

AI觉醒派成立以来&#xff0c;微信社群已经壮大至几千人。许多新加入的小伙伴可能对我们还不够了解&#xff0c;今天&#xff0c;让我们通过这篇文章深入探讨我们的核心理念、梦想、以及如何借助人工智能觉醒更强大的自我。 &#x1f31f; AI觉醒派简介 AI觉醒派是一个探索人工…

深度学习入门(4)

神经网络的构建 import numpy as np import matplotlib.pyplot as plt def sigmoid(x):return 1/(1np.exp(-x)) def identity_function(x):#恒等函数return x def init_network():#进行权重和偏置的初始化&#xff0c;并保存到字典中network{}network[W1]np.array([[0.1,0.3,0…

清华大学:序列推荐模型稳定性飙升,STDP框架惊艳登场

获取本文论文原文PDF&#xff0c;请公众号留言&#xff1a;论文解读 引言&#xff1a;在线平台推荐系统的挑战与机遇 在线平台已成为我们日常生活中不可或缺的一部分&#xff0c;它们提供了丰富多样的商品和服务。然而&#xff0c;如何为用户推荐感兴趣的项目仍然是一个挑战。…

sql(ctfhub)

一.整数型注入 输入1 输入2 输入2-1&#xff0c;回显为1的结果&#xff0c;说明是数字型&#xff0c;只有数字型才可加减 判断字段数为2 查询数据库 查表 查列 显示flag内容 二.字符型注入 输入1 输入2 输入2-1&#xff0c;说明为字符型&#xff0c;不是数字型 判断闭合方式为…

新手小白,在数学建模的过程中应该怎么分工?

大家知道&#xff0c;数学建模竞赛是需要一个团队的三个人在三天或四天的时间内&#xff0c;完成模型建立&#xff0c;编程实现和论文写作的任务&#xff0c;对许多第一次参加建模或者建模经验比较欠缺的团队来说&#xff0c;是时间紧任务重的&#xff0c;那么怎么办呢&#xf…

Elasticsearch:简化 KNN 搜索

作者&#xff1a;来自 Elastic Panagiotis Bailis 在这篇博客文章中&#xff0c;我们将深入探讨我们为了使 KNN 搜索的入门体验变得更加简单而做出的努力&#xff01; 向量搜索 向量搜索通过在 Elasticsearch 中引入一种新的专有的 KNN 搜索类型&#xff0c;已经可以使用一段…

对接浦发银行支付(八)-- 对账接口

一、背景 本文不是要讲述支付服务的对账模块具体怎么做&#xff0c;仅是介绍如何对接浦发银行的对账接口。 也就是说&#xff0c;本文限读取到对账文件的内容&#xff0c;不会进一步去讲述如何与支付平台进行对账。 如果要获取商户的对账单&#xff0c;需要遵循以下步骤&…

数据分析_数据分析思维(1)

数据分析_数据分析思维(1) 这篇文章具体的给大家介绍数据分析中最为核心的技术之一: 数据分析思维的相关内容。 一、数据分析的三种核心思维 作为新手数据分析师或数据运营, 在面对数据异常的时候, 好多小伙伴都会出现: “好像是A引起的”, “好像也和B渠道有关”, “也可能…

江苏瑞达环保科技股份有限公司| 邀您参加2024全国水科技大会暨技术装备成果展览会

—— 展位号:A18 —— 江苏瑞达环保科技股份有限公司是一家致力于环境保护和可持续发展的高新技术企业&#xff0c;专注于环境治理技术研发和环保节能装备制造,为工业企业提供可靠的工程解决方案。2023年&#xff0c;瑞达科技被认定为江苏省省级专精特新企业。 瑞达科技成立于2…

rCore-Turorial-Book第三课(计算机启动流程和程序内存布局与编译流程探索)

本节任务&#xff1a;梳理程序在操作系统中被编译运行的全流程&#xff0c;大体了解我们在没有操作系统的情况下&#xff0c;我们会面对那些困难 重点 1. 计算机组成基础 面对的困难&#xff1a;没有操作系统&#xff0c;我们必须直面硬件资源&#xff0c;管理起他们并为应用程…

本地环境通过ssh通道连接服务器数据库,实现本地客户端和代码可以访问数据库

使用方法&#xff1a; ssh -p 搭建隧道的端口 -fNL 本地端口:远程ip:远程端口号 搭建隧道的账号搭建隧道的ip 可以增加参数-v,输出更多的信息 ssh -p 搭建隧道的端口 -fNL 本地端口:远程ip:远程端口号 -v 搭建隧道的账号搭建隧道的ip 有时候&#xff0c;测试环境的数据库不允许…

YOLOv8-PySide --- 基于 ultralytics 8.1.0 发行版优化 | 代码已开源

YOLOv8-PySide — 基于 ultralytics 8.1.0 发行版优化 Github 项目地址&#xff1a;https://github.com/WangQvQ/Ultralytics-PySide6 BiliBili视频地址&#xff1a;https://www.bilibili.com/video 页面效果 如何使用 pip install ultralytics8.1.0 or git clone --branch v…

如何判别三角形和求10 个整数中最大值?

分享每日小题&#xff0c;不断进步&#xff0c;今天的你也要加油哦&#xff01;接下来请看题------> 一、已知三条边a&#xff0c;b&#xff0c;c能否构成三角形&#xff0c;如果能构成三角形&#xff0c;判断三角形的类型&#xff08;等边三角形、等腰三角形或普通三角形 …

通过Docker新建并使用MySQL数据库

1. 安装Docker 确保您的系统上已经安装了Docker。可以通过以下命令检查Docker是否安装并运行&#xff1a; systemctl status docker如果没有安装或运行&#xff0c;请按照官方文档进行安装和启动。 2. 拉取MySQL镜像 从Docker Hub拉取MySQL官方镜像。这里以MySQL 5.7版本为…

(回溯)记忆化搜索和dp

动态规划的核心就是 状态的定义和状态的转移 灵神 的 回溯改递归思路 首先很多动态规划问题都可以采用 回溯 的思想 回溯主要思想就是把 一个大问题分解成小问题 比如 采用子集类回溯问题中的核心思想-> 选或不选 或者 选哪个 记忆化搜索之后 我们可以发现 每个新节点依…

【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法)

目录 UDP协议 UDP协议的报文结构及注意事项 UDP报文结构中的校验和字段 1. 校验和主要校验的内容 2. UDP校验和的实现方式 3. CRC&#xff08;循环冗余校验&#xff09;算法 4. MD5&#xff08;Message Digest Algorithm 5&#xff09; UDP协议 上一篇文章提过&#xf…