数据结构--AVL树(平衡二叉树)

  ✅博客主页:爆打维c-CSDN博客​​​​​​ 🐾

🔹分享c、c++知识及代码 🐾

🔹Gitee代码仓库 五彩斑斓黑1 (colorful-black-1) - Gitee.com

一、AVL树是什么?(含义、性质)

1.AVL树的概念

        AVL树是最先发明的自平衡二叉树,AVL树是⼀颗高度平衡二叉搜索树,通过控制高度差去控制平衡。为什么叫它AVL树?AVL 是大学教授 G.M. Adelson-Velsky 和 E.M. Landis 名称的缩写,他们提出了平衡二叉树的概念,为了纪念他们,将 平衡二叉树 称为 AVL树。在介绍AVL树之前,我们先介绍一下二叉搜索树的性质。

二叉搜索树或者是⼀棵空树,或者是具有以下性质的⼆叉树:
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
• 它的左右⼦树也分别为⼆叉搜索树

        如图所示,这是一颗二叉搜索树,特点就是左子树上所有结点均小于等于根节点的值,而右子树上所有结点均大于等于根节点的值。

2.AVL树的性质

AVL是⼀颗空树或具备下列性质的⼆叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树的高度差的绝对值不超过1
  • 树高为O(log2N)
2.1 平衡因子(Balance Factor,简写为bf)

平衡因子(bf):结点的右子树的深度减去左子树的深度。

即: 结点的平衡因子 = 右子树的高度 - 左子树的高度 。

在 AVL树中,所有节点的平衡因子都必须满足: -1<=bf<=1;

如图所示,以结点10和根节点8为例,10:bf=1-0=1,8:bf=2-3=-1

请记住:我们通过平衡因子来控制树的平衡!

2.2 区分是否是AVL树

下面是AVL树和非AVL树对比的例图:右边的树虽然是一个二叉搜索树 但很明显不符合AVL树的性质2 即左右子树的高度差的绝对值不超过1,所以右边的树并不是AVL树,而左边符合AVL树的性质。


二、AVL的使用场景以及查找效率

1.AVL树的作用

        我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。

例如:我们按顺序将一组数据 1,2,3,4,5,6 分别插入到一颗空二叉查找树和AVL树中,插入的结果如下图:

由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(log2N)。高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因。

2.与二分查找的区别

        当我们在内存中存储和搜索数据,用AVL树就能完美满足我们的需求,它搜索速度很快,效率也很高,因为它是一个平衡二叉搜索树,高度为O(log2N) ,查找效率也为O(log2N) ,AVL树增删查改时间复杂度为:O(N)

        虽然⼆分查找也可以实现 O(log2N) 级别的查找效率,但是二分查找有两大缺陷:1. 需要存储在支持下标随机访问的结构中,并且数据要有序。2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据。

这里也就体现出了平衡⼆叉搜索树的价值。


三、如何实现一个AVL树(底层代码逻辑)

1.AVL树的结构

我们首先定义一个AVL结点的结构体和AVL树的类,代码如下

template<class K,class V>
struct AVLTreeNode{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	int _bf; //平衡因子

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

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

2. AVL树的插入

2.1插入的过程
  1. 根据值的大小(二叉搜索树的规则) 寻找空结点的位置 找到位置后开辟新空间 
  2. 新增结点以后,只会影响祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可以停⽌了,具体情况我们下⾯再详细分析。
  3. 更新平衡因⼦过程中没有出现问题,则插⼊结束
  4. 更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后本质调平衡的同时,本质降低了⼦树的⾼度,不会再影响上⼀层,所以插⼊结束。
2.2.平衡因子更新

更新原则:
• 平衡因⼦ = 右子树高度-左子树高度
• 只有⼦树⾼度变化才会影响当前结点平衡因⼦。
• 插⼊结点,会增加⾼度,所以新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在parent的左⼦树,parent平衡因⼦--
• parent所在⼦树的⾼度是否变化决定了是否会继续往上更新


更新停止条件:(三种情况)
更新后parent的平衡因子等于0,更新中parent的平衡因⼦变化为-1->0 或者 1->0,说明更新前parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会影响parent的父亲结点的平衡因子,更新结束。
更新后parent的平衡因子等于1 或 -1更新前更新中parent的平衡因⼦变化为0->1 或者 0->-1,说明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新。
更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因⼦变化为1->2 或者 -1->-2,说明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插⼊结束。

下面给出插入过程的部分代码,旋转部分在第三小点着重讲解

bool Insert(const pair<K, V>& kv) {
	//树为空 直接插入
	if (!_root) {
		_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;
	//控制平衡
	while (parent) {
		if (parent->_left == cur) {
			parent->_bf--;
		}
		else if (parent->_right == cur) {
			parent->_bf++;
		}
		else assert("balance false");

		if (parent->_bf == 0) {
			break; //此时已经达到平衡 不影响上面父节点
		}
		else if (parent->_bf == 1 || parent->_bf == -1) {
			//0-> 1 |-1 需要继续向上
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2) {
			//此时已经不符合平衡二叉搜索树的条件 需要旋转
			//....(旋转逻辑代码)
			break;
		}
		else {
			assert("balance false!");
		}

	}
	return true;
}

3.AVL树的旋转

一边子树的高度过高会导致二叉搜索树的不平衡,所以我们需要旋转来降低树高度

旋转的原则
1. 保持搜索树的规则
2. 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度
旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

3.1右单旋

当前结点cur(5)为左子树高平衡因子为-1且parent(10)的平衡因子为-2时,需要右单旋来达到平衡.

以parent为旋转点 将sublr变为parent(10)的左子树 parent(10)变为cur(5)的右子树 此时平衡

3.2左单旋

当前结点cur(15)右子树高平衡因子为1且parent(10)的平衡因子为2时,需要左单旋来达到平衡.

以parent为旋转点 将subrl变为parent(10)的右子树 parent(10)变为cur(15)的左子树 此时平衡

3.3左右双旋

当为以下情况 结点cur右子树高平衡因子为1且parent的平衡因子为-2时,需要左右双旋

先以subl为旋转点左单旋 再以parent为旋转点右单旋

3.4右左双旋

当为以下情况 结点cur左子树高平衡因子为-1且parent的平衡因子为2时,需要右左双旋

先以subr为旋转点右单旋 再以parent为旋转点左单旋

上述过程最好在草稿纸上推演一边,一般情况很多,这里只给出抽象情况,感兴趣的可以自己分析

总结

在插入的过程中,会出现一下四种情况破坏AVL树的特性,我们可以采取如下相应的旋转。

插入位置状态操作
在结点T的左结点(L)的 左子树(L) 上做了插入元素左左型右旋
在结点T的左结点(L)的 右子树(R) 上做了插入元素左右型左右旋
在结点T的右结点(R)的 右子树(R) 上做了插入元素右右型左旋
在结点T的右结点(R)的 左子树(L) 上做了插入元素右左型右左旋

注意:
T 表示 平衡因子(bf)绝对值大于1的节点。

不知道大家发现规律没,这个规则还是挺好记,下面来个图示:

插入代码最终完整版

bool Insert(const pair<K, V>& kv) {
	//树为空 直接插入
	if (!_root) {
		_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;
	//控制平衡
	while (parent) {
		if (parent->_left == cur) {
			parent->_bf--;
		}
		else if (parent->_right == cur) {
			parent->_bf++;
		}
		else assert("balance false");

		if (parent->_bf == 0) {
			break; //此时已经达到平衡 不影响上面父节点
		}
		else if (parent->_bf == 1 || parent->_bf == -1) {
			//0-> 1 |-1 需要继续向上
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2) {
			//此时已经不符合平衡二叉搜索树的条件 需要旋转
			if (parent->_bf == -2&&cur->_bf== -1) { //最左侧树不平衡 需要右旋
				RotateR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == 1) { //最右侧树不平衡 需要左旋
				RotateL(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("balance false!");
		}

	}
	return true;
}

void RotateR(Node* parent) {
	Node* pp = parent->_parent;
	Node* subl = parent->_left;
	Node* sublr = subl->_right;

	//链接过程 发生变化的可能有6个点
	parent->_left = sublr;
	parent->_parent = subl;
	subl->_right = parent;

	if (sublr) { //如果sublr存在 那么父指向p
		sublr->_parent = parent;
	}

	//改变pp以及subl的父指向
	if (pp) {
		if (pp->_left == parent) {
			pp->_left = subl;
		}
		else {
			pp->_right = subl;
		}
		subl->_parent = pp;
	}
	else { //pp为空 parent为根结点 此时subl为新根节点
		_root = subl;
		subl->_parent = nullptr;
	}
	parent->_bf = subl->_bf = 0; //旋转后 已经平衡 改变平衡因子
}
void RotateL(Node* parent) {
	Node* pp = parent->_parent;
	Node* subr = parent->_right;
	Node* subrl = subr->_left;

	parent->_right = subrl;
	parent->_parent = subr;
	subr->_left = parent;

	if (subrl) { //如果subrl存在 那么父指向p
		subrl->_parent = parent;
	}

	//改变pp以及subr的父指向
	if (pp) {
		if (pp->_left == parent) {
			pp->_left = subr;
		}
		else pp->_right = subr;
		subr->_parent = pp;
	}
	else { //pp为空 parent为根结点 此时subr为新根节点
		_root = subr;
		subr->_parent = nullptr;
	}

	parent->_bf = subr->_bf = 0; //旋转后 已经平衡 改变平衡因子
}
void RotateLR(Node* parent) {
	Node* subl = parent->_left;
	Node* sublr = subl->_right;
	int bf = sublr->_bf;
	RotateL(parent->_left);
	RotateR(parent);
	if (bf == 0) {
		subl->_bf = 0;
		parent->_bf = 0;
		sublr->_bf = 0;
	}
	else if (bf == -1) {
		subl->_bf = 0;
		parent->_bf = 1;
		sublr->_bf = 0;
	}
	else if (bf == 1) {
		subl->_bf = -1;
		parent->_bf = 0;
		sublr->_bf = 0;
	}
	else {
		assert("RotateLR false");
	}
}
void RotateRL(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(parent->_right);
	RotateL(parent);
	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert("RotateRL false");
	}
}

4.AVL树的查找

以⼆叉搜索树逻辑实现即可,搜索效率为 O(log2N)

Node* Find(const K& key) {
	/*if (!_root) {
		return false;
	}*/
	Node* cur = _root; //树为空不进入循环直接返回False
	while (cur) {
		if (cur->_kv.first < key) {
			//此时值小 往大的右走
			cur = cur->_right;
		}
		else if (cur->_kv.first > key) {
			//此时值大 往左
			cur = cur->_left;
		}
		else {
			return cur;
		}
	}
	return nullptr;
}

5.AVL树平衡检测

int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{
	// 空树也是AVL树
	if (nullptr == root)
		return true;
	// 计算pRoot结点的平衡因⼦:即pRoot左右⼦树的高度差
	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	int diff = rightHeight - leftHeight;
	// 如果计算出的平衡因⼦与pRoot的平衡因子不相等,或者
	// pRoot平衡因⼦的绝对值超过1,则⼀定不是AVL树
	if (abs(diff) >= 2)
	{
		cout << root->_kv.first << "高度差异常" << endl;
		return false;
	}
	if (root->_bf != diff)
	{
		cout << root->_kv.first << "平衡因子异常" << endl;
		return false;
	}
	// pRoot的左和右如果都是AVL树,则该树⼀定是AVL树
	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

6.参考文章

https://blog.csdn.net/xiaojin21cen/article/details/97602146


如果这篇文章对你有帮助的话,请给博主一个免费的赞鼓励一下吧~ 💓

本文仅简单介绍了有关AVL树的一些基本概念和相关代码实现,以上个人拙见,若有错误之处,希望各位能提出宝贵的建议和更正,感谢您的观看

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

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

相关文章

sunshine和moonlight串流网络丢失帧高的问题(局域网)

注&#xff1a;此贴结果仅供参考 场景环境&#xff1a;单身公寓 路由器&#xff1a;2016年的路由器 开始&#xff1a;电脑安装sunshine软件&#xff0c;手机安装moonlight软件开始串流发现网络丢失帧发现巨高 一开始怀疑就是路由器问题&#xff0c;因为是局域网&#xff0c;而…

STM32F103外部中断配置

一、外部中断 在上一节我们介绍了STM32f103的嵌套向量中断控制器&#xff0c;其中包括中断的使能、失能、中断优先级分组以及中断优先级配置等内容。 1.1 外部中断/事件控制器 在STM32f103支持的60个可屏蔽中断中&#xff0c;有一些比较特殊的中断&#xff1a; 中断编号13 EXTI…

解决SSL VPN客户端一直提示无法连接服务器的问题

近期服务器更新VPN后&#xff0c;我的win10电脑一致无法连接到VPN服务器&#xff0c; SSL VPN客户端总是提示无法连接到服务端。网上百度尝试了各种方法后&#xff0c;终于通过以下设置方式解决了问题&#xff1a; 1、首先&#xff0c;在控制面板中打开“网络和共享中心”窗口&…

从零开始:Linux 环境下的 C/C++ 编译教程

个人主页&#xff1a;chian-ocean 文章专栏 前言&#xff1a; GCC&#xff08;GNU Compiler Collection&#xff09;是一个功能强大的编译器集合&#xff0c;支持多种语言&#xff0c;包括 C 和 C。其中 gcc 用于 C 语言编译&#xff0c;g 专用于 C 编译。 Linux GCC or G的安…

小程序-基于java+SpringBoot+Vue的网上花店微信小程序设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

Transformer:一种革命性的序列到序列学习框架

目录 ​编辑 引言 Transformer模型的基本结构 1. 自注意力机制 2. 前馈神经网络 3. 位置编码 Transformer的工作原理 Transformer的应用 机器翻译 文本摘要 问答系统 文本分类 语音识别 图像识别 结论 引言 Transformer模型&#xff0c;自2017年由Vaswani等人提…

轮转数组(java)

题目描述 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,…

【vue3实现微信小程序】每日专题与分页跳转的初步实现

快速跳转&#xff1a; 我的个人博客主页&#x1f449;&#xff1a;Reuuse博客 新开专栏&#x1f449;&#xff1a;Vue3专栏 参考文献&#x1f449;&#xff1a;uniapp官网 免费图标&#x1f449;&#xff1a;阿里巴巴矢量图标库 ❀ 感谢支持&#xff01;☀ 前情提要 &#x…

【优先算法学习】双指针--结合题目讲解学习

目录 1.有效三角形的个数 1.2题目解题思路 1.3代码实现 2.和为s的两个数 2.1刷题链接-> 2.2题目解题思路 2.3代码实现 1.有效三角形的个数 1.1刷题链接-> 力扣-有效三角形的个数https://leetcode.cn/problems/valid-triangle-number/description/ 1.2题目解…

云服务器部署WebSocket项目

WebSocket是一种在单个TCP连接上进行全双工通信的协议&#xff0c;其设计的目的是在Web浏览器和Web服务器之间进行实时通信&#xff08;实时Web&#xff09; WebSocket协议的优点包括&#xff1a; 1. 更高效的网络利用率&#xff1a;与HTTP相比&#xff0c;WebSocket的握手只…

前端---HTML(一)

HTML_网络的三大基石和html普通文本标签 1.我们要访问网络&#xff0c;需不需要知道&#xff0c;网络上的东西在哪&#xff1f; 为什么我们写&#xff0c;www.baidu.com就能找到百度了呢&#xff1f; 我一拼ping www.baidu.com 就拼到了ip地址&#xff1a; [119.75.218.70]…

网络基础 - IP 隧道篇

在一个如图所示的网络环境里&#xff0c;网络 A、B 使用 IPv6&#xff0c;如果处于中间位置的网络 C 支持使用 IPv4 的话&#xff0c;网络 A 与网络 B 之间将无法直接进行通信&#xff0c;为了让它们之间正常通信&#xff0c;这时必须得采用 IP 隧道的功能&#xff0c;IP 隧道中…

1.1、Python 安装与开发环境设置指南

作为你的 Python 导师&#xff0c;我将带领你一步步完成 Python 的安装和开发环境的设置&#xff0c;让你顺利开启 Python 学习之旅。 1. Python 安装 1.1 下载 Python 安装包 首先&#xff0c;我们需要从 Python 官网下载 Python 的安装包。 打开你的浏览器&#xff0c;访…

【Redis篇】String类型命令详讲以及它的使用场景

目录 前言&#xff1a; 基本命令&#xff1a; setnx/setxx FLUSHALL mest mget 计数命令 INCR / INCRBY DECR/DECYBY INCRBYFLOAT 其他命令 APPEND GETRANGE SETRANGE STRLEN String的典型使用场景 缓存&#xff08;Cache&#xff09;功能 计数&#xff08;…

【2024】前端学习笔记19-ref和reactive使用

学习笔记 1.ref2.reactive3.总结 1.ref ref是 Vue 3 中用来创建响应式引用的一个函数&#xff0c;通常用于基本数据类型&#xff08;如字符串、数字、布尔值等&#xff09;或对象/数组的单一值。 ref特点&#xff1a; ref 可以用来创建单个响应式对象对于 ref 包裹的值&…

构造函数的相关

文章目录 一、构造函数 今天我们要来讲解类的默认成员函数之一的构造函数。 一、构造函数 构造函数是特殊的成员函数&#xff0c;需要注意的是&#xff0c;构造函数虽然名称叫构造&#xff0c;但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时&…

C嘎嘎探索篇:栈与队列的交响:C++中的结构艺术

C嘎嘎探索篇&#xff1a;栈与队列的交响&#xff1a;C中的结构艺术 前言&#xff1a; 小编在之前刚完成了C中栈和队列&#xff08;stack和queue&#xff09;的讲解&#xff0c;忘记的小伙伴可以去我上一篇文章看一眼的&#xff0c;今天小编将会带领大家吹奏栈和队列的交响&am…

Xcode15(iOS17.4)打包的项目在 iOS12 系统上启动崩溃

0x00 启动崩溃 崩溃日志&#xff0c;只有 2 行&#xff0c;看不出啥来。 0x01 默认配置 由于我开发时&#xff0c;使用的 Xcode 14.1&#xff0c;打包在另外一台电脑 Xcode 15.3 Xcode 14.1 Build Settings -> Asset Catalog Compliter - Options Xcode 15.3 Build S…

【Python爬虫实战】深入解析 Scrapy:从阻塞与非阻塞到高效爬取的实战指南

&#x1f308;个人主页&#xff1a;易辰君-CSDN博客 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html ​ 目录 前言 一、阻塞和非阻塞 &#xff08;一&#xff09;阻塞 &#xff08;二&#xff09;非阻塞 二、Scrapy的工作…

01 [51单片机 PROTEUS仿真设计]基于温度传感器的恒温控制系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 五个按键&#xff0c;分别为启动按键&#xff0c;则LCD1602显示倒计时&#xff0c;音乐播放 设置按键&#xff0c;可以设置倒计时的分秒&#xff0c;然后加减按键&#xff0c;还有最后一个暂停音乐…