c++——map、set底层之AVL树(动图演示旋转)

news2024/11/15 15:37:50

在上一篇博客里面我们提到了set和map的使用;这篇博客重点介绍他们的底层逻辑

c++之set和map——关联容器(非顺序容器——list、vector)


目录

文章目录

前言

一、底层结构

二、AVL 树

1.AVL树的概念

2.AVL树节点的定义

3、AVL树的插入

三、 AVL树的旋转

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

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

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

​编辑

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

 四、验证AVL树

总结



前言

我们之前学了过二叉搜索树,二这里的AVL也是一种搜索树,当我们二叉搜索树发生偏移时,搜索时间就会边长,就不树logN了,变成N;而AVL就修复了这一点。


一、底层结构

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个 共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

二、AVL 树

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$)

2.AVL树节点的定义

代码如下(示例):

template<class T>
struct AVLTreeNode
{
 AVLTreeNode(const T& data)
     : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
 , _data(data), _bf(0)
 {}
 AVLTreeNode<T>* _pLeft;   // 该节点的左孩子
 AVLTreeNode<T>* _pRight;  // 该节点的右孩子
 AVLTreeNode<T>* _pParent; // 该节点的双亲
 T _data;
 int _bf;                  // 该节点的平衡因子
};

3、AVL树的插入

AVL的插入和搜索二叉树一样,只不过我们需要处理旋转问题,控制这棵树不要一边高一边低

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

// 1. 先按照二叉搜索树的规则将节点插入到AVL树中
    // ...
    
    // 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 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->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		// 更新平衡因子
		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;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 需要旋转处理 -- 1、让这颗子树平衡 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树的旋转分为四种:

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

在插入之前我们的左侧是比较高的,也就是左节点高,以为我们定义这个平衡因子时是将左边高定义为负,右边高定义为正;

当左边高再在左边插入节点时,就会让左边更高了,原本左边高,这里的高度差为-1,当再次向左侧插入节点时,高度差就为-2了,这时我们就需要进行旋转这颗树了;如何旋转呢?

代码实现: 

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

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;

	Node* ppnode = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

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

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

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

 左单旋就和右单旋反过来,但右边高时,在右边在插入节点,那么右边较左边的高度差就是2了,这个时候就需要进行左旋转;

代码实现:

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	Node* ppnode = parent->_parent;

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

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

		subR->_parent = ppnode;
	}

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

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

1、subLR的左子树当中的结点本身就比subL的值大,因此可以作为subL的右子树。
2、subLR的右子树当中的结点本身就比parent的值小,因此可以作为parent的左子树。
3、经过步骤1/2后,subL及其子树当中结点的值都就比subLR的值小,而parent及其子树当中结点的值都就比subLR的值大,因此它们可以分别作为subLR的左右子树。

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

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

  1. 由于插入新结点后,可能并不会立即进行旋转操作,而可能是在更新其祖先结点的过程中出现了不平衡而进行的旋转操作,因此用长方形表示下面的子树。
  2. 动图中,在b子树当中新增结点,或是在c子树当中新增结点,均会引发右左双旋,动图中以在c子树当中新增结点为例。
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(parent->_right);
	RotateL(parent);

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

 四、验证AVL树

void Test_AVLTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
			{
			int x = 0;
			}
		 */

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

通过高度变化验证是否平衡

int _Height(Node* root)
{
	if (root == NULL)
		return 0;

	int leftH = _Height(root->_left);
	int rightH = _Height(root->_right);

	return leftH > rightH ? leftH + 1 : rightH + 1;
}

bool _IsBalance(Node* root)
{
	if (root == NULL)
	{
		return true;
	}

	int leftH = _Height(root->_left);
	int rightH = _Height(root->_right);

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

	return abs(leftH - rightH) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

总结

假如以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为根的子树个高度降低,已经平衡,不需要再向上更新。

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

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

相关文章

Databend 开源周报第 156 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend。 支持 Share Cata…

深入解读 Scrapy 框架原理与源码

&#x1f6e0;️ Scrapy 框架原理解读 Scrapy 是一个强大的 Python 爬虫框架&#xff0c;其设计理念基于事件驱动的异步编程&#xff0c;通过高度模块化的方式实现爬虫功能。Scrapy 框架的核心组成包括爬虫&#xff08;Spider&#xff09;、调度器&#xff08;Scheduler&#…

大模型书籍推荐:《大规模语言模型:从理论到实践》

书籍简介 在当前的人工智能领域&#xff0c;大规模语言模型&#xff08;Large Language Models, LLMs&#xff09;无疑是最重要的研究方向之一。它们不仅在自然语言处理方面取得了显著成果&#xff0c;还为其他领域带来了革命性的变化。《大规模语言模型&#xff1a;从理论到实…

维语驾考这款软件有人用过吗?靠谱吗?

维语驾考是专门为维吾尔语学车考驾照的学员而准备的驾考刷题软件。书写延续维语用法自右向左书写&#xff0c;可调节字号大小&#xff0c;支持维汉双语切换。2024驾考新规题库&#xff0c;内含科目一、科目四全真题库&#xff0c;每道题配备详细解析&#xff0c;帮助学员理解透…

Openwrt配置ZeroTier,实现公网访问内网中服务器

ZeroTier注册&Openwrt初始配置 首先来到Openwrt的VPN→ZeroTier页面&#xff0c;进行一个很简单的注册 注册后去zerotier的网页管理页面进行一个很简单的创建网络 复制网络ID备用 在openwrt填写网络ID并启用。如果你需要访问内网主机勾上 自动客户端NAT 在zerotier网络管理…

电子合同签署:2024年十佳选择与评估

文章将介绍10款电子合同签署平台&#xff1a;e签宝、上上签、合同宝、腾讯电子签、合易签、Zoho Sign、Nitro Sign、Secured Signing、Signeasy、Lightico。 在当今快速发展的商业环境中&#xff0c;选择一款合适的电子合同签署平台对于保证工作效率和合同执行的安全性至关重要…

精准洞察农田生态,智慧农业物联网环境监测与数据采集系统来袭

随着智慧农业的快速发展&#xff0c;利用物联网技术实现对农田种植状态的精准监测变得愈发重要。为了确保监测的准确性、一致性和有效性&#xff0c;规范农田物联网监测设备的技术参数、部署安装以及数据对接等技术指标势在必行。 本文技术说明旨在为相关设备的选择、安装和集…

深入源码P3C-PMD:rule (4)

系列文章目录 文章目录 系列文章目录rule 的应用类别 rule rule 自定义XML rule 定义Tree 漫游错误报告生命周期 designer rule相关的代码在每个子 module 的 rule 文件夹。而且也以一些 ruleset 为范围分了文件夹&#xff0c;如下图所示&#xff1a; 对每个 rule 来说&#xf…

Model Counting 2024 Public Instance Track 1 18000s(5h)测试结果

测试求解器&#xff1a;SharpSAT-TD与SharpSATTD-CH 18000s测试结果 测试结果图 对3600s未得到结果的数据进行18000s的测试&#xff0c;48组数据&#xff0c;最终有4组在18000s&#xff08;5h&#xff09;内解出 测试数据117 SharpSAT-TD输出&#xff1a; SharpSATTD-CH输出…

2024年底前,河南建筑装饰企业资质延期资料准备要点

针对2024年底前河南建筑装饰企业资质延期的资料准备要点&#xff0c;结合当前的政策要求和实际情况&#xff0c;以下是一些关键的准备要点&#xff1a; 一、了解政策与要求 政策关注&#xff1a; 密切关注河南省住房和城乡建设厅及地方建设主管部门发布的最新政策文件、通知公…

万字长文带你入门shell编程(超详细)

一、概述 Shell 是计算机操作系统中用户与操作系统内核之间的接口层&#xff0c;它提供了一种方式让用户能够通过命令行界面&#xff08;CLI&#xff09;与操作系统交互。Shell 可以被视为一个命令解释器&#xff0c;它接收用户输入的命令&#xff0c;解析这些命令&#xff0c…

趋动科技助力中国移动新型智算中心AI算力池化商用实践

由中国通信标准化协会、中国通信学会指导&#xff0c;CCSA TC610 SDN /NFV /AI标准与产业推进委员会主办的2024年云网智联大会于4月10日-11日在北京召开。 趋动科技联合申报的“中国移动新型智算中心AI算力池化商用实践”&#xff0c;获得2023年度SDN、NFV、网络AI优秀案例征集…

欧美农场小游戏 高端链游 休闲的欧美链游农场 【玫瑰庄园】 高端中英-欧美花园链游

#农场小游戏#链游【玫瑰庄园】 高端中英-欧美花园链游 玫瑰花园一、种子&#xff1a;种子分为五种&#xff1a;白玫瑰、红玫瑰、黑玫瑰、紫罗兰、郁金香。种子通过开启盲盒获得。二、种花&#xff1a;玩家开启盲盒获得的种子&#xff0c;会直接种下&#xff0c;种子种下后&…

深入浅出消息队列----【如何保证消息不重复?】

深入浅出消息队列----【如何保证消息不重复&#xff1f;】 消息一定会重复消息幂等消费改造业务符合天然幂等写法数据库唯一索引redis 唯一判断 本文仅是文章笔记&#xff0c;整理了原文章中重要的知识点、记录了个人的看法 文章来源&#xff1a;编程导航-鱼皮【yes哥深入浅出消…

从零开始的大模型训练教程

近年来&#xff0c;随着人工智能技术的迅猛发展&#xff0c;大模型&#xff08;Large Models&#xff09;成为了业界关注的焦点。这些模型&#xff0c;尤其是那些基于Transformer架构的自然语言处理模型&#xff0c;如GPT系列、BERT等&#xff0c;在各种任务上取得了前所未有的…

git add . 警告

这些警告是因为 Git 检测到你的文件使用了不同的换行符&#xff08;LF 或 CRLF&#xff09;&#xff0c;并提示在下次 Git 操作中将会统一换行符为 CRLF。这通常发生在跨平台协作时&#xff0c;例如在 Windows 环境下编辑的文件可能使用 CRLF&#xff0c;而在类 Unix 环境&…

数据结构:基于顺序表实现通讯录系统(含源码)

目录 一、前言 二、各个功能的实现 2.1 初始化通讯录 2.2 添加通讯录数据 2.3 查找通讯录数据 2.4 删除通讯录数据 2.5 修改通讯录数据 2.6 展示通讯录数据​编辑 2.7 销毁通讯录数据 三、添加菜单和测试 四、完整源码 sxb.h sxb.c contact.h contact.c test.c 一、前…

ROS智能移动机器人实训

0.前言 1.任务 1.1.任务实训任务 1.使用/voice_aiui等语音服务完成基本的语音聊天(需唤醒词“元宝”)。 2.语音多点导航 3.语音单点导航 1.2.智能机器人仿真任务 1.3.智能机器人实物操作任务 2.目的 3&#xff0e;使用环境 4.综合项目实验 任务实训 问题 解决办法…

LinuxIO之文件系统的实现

Ext2/3/4 的layout文件系统的一致性&#xff1a; append一个文件的全流程掉电与文件系统的一致性fsck文件系统的日志ext4 mount选项文件系统的debug和dumpCopy On Write 文件系统&#xff1a; btrfs 预备知识&#xff1a;数据库里的transaction(事务)有什么特性&#xff1f; …