AVL树图解(插入与删除)

news2024/11/15 23:35:19

文章目录

  • AVL树概念
    • 平衡因子
  • 旋转
    • 左单旋
    • 更新父节点与孩子节点的连接
  • 右单旋
  • 左右双旋 (先左单旋再右单旋)
  • 右左双旋 (先右单旋再左单旋)
  • 验证是否为AVL树
  • ALV树的删除操作
  • 一. 高度不变删除叶子节点和单孩子节点
    • 1.1高度不变删除叶子节点
    • 1.2删除单孩子节点
  • 二. 高度变化 - 旋转
    • 2.1 左单旋
    • 2.2 右单旋
    • 小总结:
    • 2.3左右双旋(先左旋再右旋)
    • 2.4右左双旋(先右旋再左旋)
    • 删除节点右两个孩子的情况下
  • 注意
  • 代码实现:

AVL树概念

前面我们讲了二叉树搜索树,它的特点为:左子树的值 < 根 右子树的值 > 根,同时二叉搜索树里面并不支持出现重复值。由于这样的特性呢,我们对二叉搜索树进行中序遍历可以得到一个有序数组。同时查找的效率平常状态下位 L o g N LogN LogN,但是在极端情况下会退化为单叉链,所以这就有了我们接下来要学习的内容AVL树

AVL树也叫平衡二叉搜索树,就在于它对于普通搜索二叉树的区别在于,当我们左右子树的高度差,的绝对值大于 1 的时候,就会进行一个旋转操作,使二叉树恢复平衡,举个简单栗子:

在这里插入图片描述

如上图,此时我们左子树的高度太高了,进行旋转后,左右子树高度差相等。

平衡因子

AVL树控制平衡主要观察的是平衡因子这个概念,这里我们以右子树-左子树作为我们平衡因子的计算方式。由于我们插入节点之后需要更新父亲节点的平衡因子,所以我们这里使用三叉链。

如图:

在这里插入图片描述

只有当我们的平衡因子的绝对值大于1的时候才需要进行旋转处理。同时我们的树节点定义根二叉搜索树的定义不一样,AVL树节点定义:

template<class K>
	struct AVLTreeNode
	{
		K _data;
		AVLTreeNode<K>* _left;
		AVLTreeNode<K>* _right;
		AVLTreeNode<K>* _parent; //新增父亲节点。
		int _bf; // blance factor

		AVLTreeNode(const K& key)
			:_data(key)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
		{}
	};

旋转

旋转分为以下四种情况:

  • 左单旋
  • 右单旋
  • 先左单旋再右单旋 (双旋)
  • 先右单旋再左单旋 (双旋)

左单旋

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

更新父节点与孩子节点的连接

在这里插入图片描述

这里来说明一下名称的含义:

  • pparent :parent的父节点
  • parent:需要旋转的节点

原来节点连接:parent->_parent ==pparentchild->_parent == parentchildLeft->_parent == child

现在节点连接:parent->_parent == childchild->_parent == pparentchildLeft->_parent == parent

同时还要更新childparent平衡因子:直接复制为0即可。

注意特殊情况:(旋转根节点)

在这里插入图片描述

代码实现:

		//左单旋
		void RotateL(Node* parent)
		{
			Node* pparent = parent->_parent;
			Node* child = parent->_right;
			Node* childLeft = child->_left;

			parent->_right = childLeft;
			if (childLeft) //可能为空
				childLeft->_parent = parent;

			child->_left = parent;
			

			//更新父节点的连接
			if (pparent) //如果原parent的父节点不为空
			{
				if (pparent->_left == parent)
				{
					pparent->_left = child;
				}
				else
				{
					pparent->_right = child;
				}
                //别忘了child的父节点
				child->_parent = pparent;
			}
			else //说明旋转的是根节点
			{
				child->_parent = nullptr;
				_root = child;
			}
            parent->_parent = child;
			//更新平衡因子
			child->_bf = parent->_bf = 0;
		}

右单旋

在这里插入图片描述

右单旋和左单旋类似,这里就不在多赘述。

代码实现:

//右单旋
		void RotateR(Node* parent)
		{
			Node* pparent = parent->_parent;
			Node* child = parent->_left;
			Node* childRight = child->_right;

			parent->_left = childRight;
			if (childRight) //可能为空
				childRight->_parent = parent;

			child->_right = parent;
			

			//更新父节点的连接
			if (pparent) //如果原parent的父节点不为空
			{
				if (pparent->_left == parent)
				{
					pparent->_left = child;
				}
				else
				{
					pparent->_right = child;
				}
                //别忘了child的父节点
				child->_parent = pparent;
			}
			else //说明旋转的是根节点
			{
				child->_parent = nullptr;
				_root = child;
			}
			parent->_parent = child;
			child->_bf = parent->_bf = 0;
		}

左右双旋 (先左单旋再右单旋)

在这里插入图片描述

这时候就需要对child节点进行一次右单旋然后再对parent进行左单旋,就可以了。

在这里插入图片描述

平衡因子的更新:双旋可不是跟我们单旋一样,childparent的平衡因子都置为0就可以了。

看如下特殊情况:

在这里插入图片描述

如上图,我们看到当我们新增节点插入在childRight的左右子树的时候,结果是不一样的,但是有一个规律childRight的左子树的值会给child,而右子树的值会给我们的parent,这会带来什么影响呢?

在我们左单旋和右单旋的时候,我们都是直接把parentchild的平衡因子赋值为0,但是这里不一样,如上图,当我们新增节点为14的时候,我们14这个节点最后会给child,那么此时child左右子树平衡了,但是parent由于左子树缺失所以平衡因子为1。反之也是这样。所以这里的平衡因子我们需要特殊处理。

  • 只要新增节点为childRight的左边,parent的平衡因子我们置为1。
  • 只要新增节点为childRight的右边,chlid的平衡因子我们置为-1。

两边都没有就像我们一开始画的那一种情况,全为0。

这种双旋旋转的时候我们呢可以把他们统统画为一种抽象图:

左右双旋:

新增节点在左侧: PLeft->_bf = 0parent->_bf = 1

在这里插入图片描述

新增节点在右侧:PLeft->_bf = -1parent->_bf = 0

在这里插入图片描述

代码实现:

//先左旋再右旋
		void RotateLR(Node* parent)
		{
			Node* child = parent->_left;
			Node* childRight = child->_right;

			RotateL(child);
			RotateR(parent);

			if (childRight->_left) //左子树给child
			{
				parent->_bf = 1;
				child->_bf = 0;
			}
			else if (childRight->_right)//右子树给child
			{
				parent->_bf = 0;
				child->_bf = -1;
			}
			else //两边为空
			{
				parent->_bf = 0;
				child->_bf = 0;
			}
		}

右左双旋 (先右单旋再左单旋)

在这里插入图片描述

规律和左右双旋不同,这次我们childLeft的左子树会给我们的parent,而右子树会给child

所以我们的平衡因子处理规则为:

  • 只要新增节点为childLeft的左边,chlid的平衡因子我们置为1。
  • 只要新增节点为childLeft的右边,parent的平衡因子我们置为-1。

chileLeft左右子树都为空,parentchild的平衡因子都置为0。

这种双旋旋转的时候我们呢可以把他们统统画为一种抽象图:

右左双旋:

新增节点在右侧: PRight->_bf = 0parent->_bf = -1

在这里插入图片描述

新增节点在左侧:PRight->_bf = 1parent->_bf = 0

在这里插入图片描述

代码实现:

//先右旋再左旋
		void RotateRL(Node* parent)
		{
			Node* child = parent->_right;
			Node* childLeft = child->_left;
			int bf = childLeft->_bf;
			RotateR(child);
			RotateL(parent);

			if (bf == -1) //左子树给child
			{
				parent->_bf = 0;
				child->_bf = 1;
			}
			else if (bf == 1)//右子树给child
			{
				parent->_bf = -1;
				child->_bf = 0;
			}
			else //两边为空
			{
				parent->_bf = 0;
				child->_bf = 0;
			}
		}

验证是否为AVL树

只需要检查每个节点的左右子树高度差不超过1即可。

代码实现:

		int TreeHigh(Node* root)
		{
			if (root == nullptr)
				return 0;
			return max(TreeHigh(root->_left), TreeHigh(root->_right)) + 1;
		}
		bool _isAVLTree(Node* root)
		{
			if (root == nullptr)
				return true;
			int left = TreeHigh(root->_left);
			int right = TreeHigh(root->_right);
			int diff = left - right;
			if (abs(diff) > 1)
				return false;
			return _isAVLTree(root->_left) && _isAVLTree(root->_right);
		}

ALV树的删除操作

AVL树的删除根二叉搜索树的删除类似,还是那四种情况,同时还需要更新平衡因子。

一. 高度不变删除叶子节点和单孩子节点

1.1高度不变删除叶子节点

在这里插入图片描述

同时这里需要特殊处理一下删除根节点:

在这里插入图片描述

如果左右子树都为空,往哪边走都是一样的,所以这种情况可以和下面的情况一起处理。

注意我们的顺序:

  1. 先更新平衡因子
  2. 再删除节点

为什么要先更新呢?我们删除某个节点的时候,可能会让当前的子树高度发生变化,上述图并没有。当我们删除的节点为parent(父节点)的左子树的时候,由于我们平衡因子 = Height(右子树) - Height(左子树),所以我们parent b f bf bf(平衡因子)会加一。上图虽然平衡因子加了一,但是子树的高度并没有发生变化,所以无需继续向上更新。

1.2删除单孩子节点

在这里插入图片描述

注意我们更新平衡因子是在删除节点上一个步骤,所以当我们更新平衡因子的时候其实是看作我们parent删除了cur这个子节点。

然后再进行删除与相应的连接,这对我们的结果并不影响。

二. 高度变化 - 旋转

旋转的部分我们都把parent叫做我们需要旋转的节点,parent左边的节点称为:PLeftparent右边的节点称为:PRight,要删除的节点称为:cur

2.1 左单旋

在这里插入图片描述

同时在删除操作种有一种特殊情况需要注意一下:

在这里插入图片描述

由于我们parent的bf是2,这代表我们的右边高,所以每次我们都以PRight作为新的根,删除操作PRight可能会出现bf == 0的情况,这种时候我们呢就需要对parentPRight的平衡因子进行特殊处理了。
当我们进行左旋的时候,我们PRight的的左孩子节点赋值给parent,同时parent作为PRight的左子树,我们正常情况下,我们parentPRight都是赋值为0,这种情况下,由于parent的右子树多了一个节点,那么parent的平衡因子变为1,又因为parent是作为PRight的左子树,所以PRight的平衡因子为-1。

2.2 右单旋

在这里插入图片描述

同时右单旋跟左单旋类似,也会出现PLeft的平衡因子为0的情况,这个时候PLeft的右孩子节点给parent的左边,同时右孩子指向parent,所以parent的左孩子多了一个节点,平衡因子为-1,又因为parent作为PLeft的右孩子,所以PLeft的平衡因子为1。

小总结:

  • 删除跟我们插入的不一样再与,我们可能会遇见平衡因子为:2 和 0 或者 -2 和 0 的组合,这个时候就需要对parentPRightPLeft平衡因子进行特殊处理。
  • 之前我们插入过后旋转玩就可以直接跳出循环,不需要再进行更新平衡因子了,但是删除不是,当我们旋转完之后还要继续往上更新平衡因子。因为我们旋转其实就是为了去平衡左右子树的高度差,例如原来平衡因子为:2,这代表我们右子树比左子树要高2,那么我们去旋转完之后可能变为 1 或者 0,所以我们还需要继续往上更新,直到parent的bf为1或者遍历到根节点了。

什么时候需要更新平衡因子呢?

  1. 更新完平衡因子为0
  • 这说明原来子树的平衡因子为1或者-1,就是我们只有一个节点,当我们把这个节点删除过后,那么我们子树的高度就减小了,此时我们需要继续往上更新
  1. 更新完平衡因子为1 或者 -1
  • 这说明我们呢原来的平衡因子为0,我们删除了一个左节点或者右节点,由于子树的高度并没有发生变化,所以此时并不需要更新
  1. 更新完平衡因子为2
  • 树已经不平衡,需要旋转处理,同时旋转过后树的高度可能会发生变化,所以也要继续往上处理。

2.3左右双旋(先左旋再右旋)

在这里插入图片描述

PLright有另外的节点,这里新增节点表示有另外的节点,并不是新插入的。同时这里的平衡因子在我们插入操作时候说过了,这里就不再赘述了。

在这里插入图片描述

平衡因子更新:

  • 新增节点在左侧: PLeft->_bf = 0parent->_bf = 1

  • 新增节点在右侧:PLeft->_bf = -1parent->_bf = 0

2.4右左双旋(先右旋再左旋)

在这里插入图片描述

在这里插入图片描述

PRLeft的右子树会给PRight的左子树,PRLeft的左子树会给parent的右子树。

平衡因子更新:

  • 新增节点在右侧: PRight->_bf = 0parent->_bf = -1

  • 新增节点在左侧:PRight->_bf = 1parent->_bf = 0

删除节点右两个孩子的情况下

在这里插入图片描述

跟我们二叉搜索树删除含有两个节点情况一样,去找一个可以替代当前位置的元素,这个元素要满足:

  • 大于当前节点的左子树,小于当前节点的右子树

所以我们可以选择:左子树的最大节点 或者右子树的最小节点

这里我选择的是右子树的最小节点。找到之后,还是先更新一下平衡因子,不过这个时候我们就需要用RighfMinPRightMin来依次更新平衡因子了。

注意

这里需要注意: 我们旋转时的更新平衡因子和普通的平衡因子更新时不一样的。

普通更新:
在这里插入图片描述

旋转更新:

在这里插入图片描述

可以看到,如果我们旋转更新还是依次更新的话,那么就出错了,当我们旋转完之后,parent的平衡因子已经更新完毕,所以我们下一次要更新的节点平衡因子为parent->_parent。就是更新爷爷节点了。

代码实现:

void UpDateBFactor(Node* cur,Node* parent)
		{
			while (parent)
			{
				if (parent->_left == cur)
				{
					parent->_bf += 1;
				}
				else
				{
					parent->_bf -= 1;
				}

				if (parent->_bf == 1 || parent->_bf == -1)
					break;
				else if (parent->_bf == 0)
				{
					cur = parent;
					parent = cur->_parent;
				}
				else if (parent->_bf == 2)
				{
					if (parent->_bf == 2)
					{
						Node* PRight = parent->_right;
						if (PRight->_bf == 1)
						{
							RotateL(parent);
						}
						else if (PRight->_bf == 0)
						{
							RotateL(parent);
							parent->_bf = 1;
							PRight->_bf = -1;
							break;
						}
						else
						{
							//双旋
							RotateRL(parent);
						}
					}
					cur = parent->_parent; //父节点已经修改过了,直接跳到爷爷节点
					parent = cur->_parent;
				}
				else if (parent->_bf == -2)
				{
					Node* PLeft = parent->_left;
					if (PLeft->_bf == -1)
					{
						RotateR(parent);
					}
					else if (PLeft->_bf == 0)
					{
						RotateR(parent);
						parent->_bf = -1;
						PLeft->_bf = 1;
						break;

					}
					else
					{
						//双旋
						RotateLR(parent);
					}
                    //旋转完之后直接更新爷爷节点的平衡因子。
					cur = parent->_parent;
					parent = cur->_parent;
				}
				else
				{
                    //平衡因子出错的时候打印错误信息。
					cout << __LINE__ << " : " << " 平衡因子错误" << endl;
					exit(-1);
				}
			}
		}

		bool erase(const K& key)
		{
			if (_root == nullptr)
				return false;
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (key < cur->_data)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_data)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					if (cur->_left == nullptr)
					{
						//特殊处理删除根节点的情况
						if (parent == nullptr)
						{
							_root = _root->_right;
							return true;
						}
                        //先更新平衡因子
						UpDateBFactor(cur, parent);
                        //再删除节点
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						//特殊处理删除根节点的情况
						if (parent == nullptr)
						{
							_root = _root->_left;
							return true;
						}
						UpDateBFactor(cur, parent);
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
						delete cur;
						return true;
					}
					else
					{
						//以右孩子的最小节点作为替换节点
						Node* RightMinP = cur;
						Node* RightMin = cur->_right;
						//找到右子树的最小节点
						while (RightMin && RightMin->_left)
						{
							RightMinP = RightMin;
							RightMin = RightMin->_left;
						}

						//把替换节点的值拷贝给del(删除)节点.
						cur->_data = RightMin->_data;

						UpDateBFactor(RightMin, RightMinP);


						if (RightMin->_right)
						{
							RightMin->_right->_parent = RightMinP;
						}

						//删除此时的替换节点。
						if (RightMinP->_right == RightMin) // 说明没找到最小节点
						{
							RightMinP->_right = RightMin->_right;
						}
						else //找到最小节点
						{
							RightMinP->_left = RightMin->_right;
	
						}
						delete RightMin;
						return true;
					}
				}
			}
			return false;
		}

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

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

相关文章

基于JAVA的企业财务管理系统设计与实现

点击下载源码 基于JAVA的企业财务管理系统设计与实现 摘要 对于企业集来说,财务管理的地位很重要。随着计算机和网络在企业中的广泛应用&#xff0c;企业发展速度在不断加快&#xff0c;在这种市场竞争冲击下企业财务管理系统必须优先发展&#xff0c;这样才能保证在竞争中处…

第30届哈尔滨种博会提质升级,10月28-30日移师长春全新亮相!

紧扣农业新质生产力发展需要&#xff0c;持续推动区域种业发展和农业振兴&#xff0c;第30届哈尔滨种业博览会暨第19届哈尔滨农资博览会/北方现代农业设施设备展将于10月28-30日在长春东北亚国际博览中心举办。展会扎根东北&#xff0c;全面辐射北方地区&#xff0c;被东北地区…

50 IRF检测MAD-BFD

IRF 检测MAD-BFD IRF配置思路 网络括谱图 主 Ten-GigabitEthernet 1/0/49 Ten-GigabitEthernet 1/0/50 Ten-GigabitEthernet 1/0/51 备 Ten-GigabitEthernet 2/0/49 Ten-GigabitEthernet 2/0/50 Ten-GigabitEthernet 2/0/51 1 利用console线进入设备的命令行页…

C语言初阶(11)

1.结构体定义 结构体就是一群数据类型的集合体。这些数据类型被称为成员变量。结构的成员可以是标量、数组、指针&#xff0c;甚至是其他结构体。 2.结构体的声明和结构体变量命名与初始化 结构体声明由以下结构组成 struct stu {char name[12];int age; }; 结构体命名有两…

计算机毕业设计选题推荐-生活垃圾治理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

解决卸载360安全卫士,报错“需要来自administrators权限才能对此文件夹进行更改”

卸载360卫士报错&#xff0c;见图&#xff1a; 解决过程&#xff1a; cmd 输入msconfig引导&#xff0c;勾上安全引导后确定立即重启进入安全模式然后进去360位置&#xff0c;进入文件夹后把所有都删除了 可能遇到的问题&#xff1a; 有个.dll的文件显示被其他进程访问删不掉…

Win11不在C盘安装WSL2(Linux环境),安装Nvidia驱动和默认使用Win11的网络代理服务

众所周知&#xff0c;WSL 2 为 Windows 用户提供了一个强大、高效且灵活的 Linux 环境&#xff0c;特别适合开发者使用。它结合了 Windows 和 Linux 的优点&#xff0c;为用户提供了更加全面和高效的工作环境。但缺点也很明显&#xff0c;那就是默认安装在本来空间就不富裕的C盘…

[一本通提高数位动态规划]恨7不成妻--题解--胎教级教学

[一本通提高数位动态规划]恨7不成妻--题解--胎教级教学 1前言2问题3化繁为简--对于方案数的求解&#xff08;1&#xff09;子问题的分解&#xff08;2&#xff09;数位dp-part1状态设置--利用约束条件推状态&#xff08;3&#xff09;数位dp-part2状态转移&#xff08;4&#x…

【leetcode详解】正方形中的最多点数【中等】(C++思路精析)

思路精析&#xff1a; 自定义结构体解读&#xff1a; 一个点是否在题给正方形中&#xff0c;只取决于其横纵坐标的最大值&#xff0c;记为dis 沟通二位数组points和字符串s的桥梁&#xff0c;就是这个点的序号&#xff0c;记为idx 由此自定义结构体&#xff0c;储存dis 和i…

JAVA中List不能创建实例。结合ArrayList的理解。

今天在使用List的时候&#xff0c;我以为List是一个父类&#xff0c;ArrayList继承自List&#xff0c;所以我想着干脆直接就创建一个List实例。结果发现程序报错了。 后来我查看了List源码&#xff0c;和ArrayList源码&#xff0c;我发现。List是一个接口&#xff0c;而ArrayL…

mac中dyld[99014]: Library not loaded: @rpath/libmysqlclient.24.dylib解决方法

将需要的库做个软链即可 sudo ln -s -f /usr/local/mysql-9.0.1-macos14-arm64/lib/libmysqlclient.24.dylib /usr/local/mysql/lib/libmysqlclient.24.dylib 再执行就不会报这个错误了&#xff0c;报的下一个需要的库

去噪扩散恢复模型

去噪扩散恢复模型 Bahjat Kawar 计算机科学系 以色列海法理工学院 bahjat.kawarcs.technion.ac.il Michael Elad 计算机科学系 以色列海法理工学院 eladcs.technion.ac.il Stefano Ermon 计算机科学系 美国加利福尼亚州斯坦福大学 ermoncs.stanford.edu …

ROS2 Linux Mint 22 安装教程

前言&#xff1a; 本教程在Linux系统上使用。 一、linux安装 移动硬盘安装linux&#xff1a;[LinuxToGo教程]把ubuntu装进移动固态&#xff0c;随时随用以下是我建议安装linux mint版本的清单&#xff1a; 图吧工具箱&#xff1a;https://www.tbtool.cn/linux mint: https://…

YAML基础语言深度解析

引言 YAML&#xff08;YAML Aint Markup Language&#xff0c;即YAML不是一种标记语言&#xff09;是一种直观、易于阅读的数据序列化格式&#xff0c;常用于配置文件、数据交换和程序间的通信。其设计目标是易于人类阅读和编写&#xff0c;同时也便于机器解析和生成。在本文中…

英伟达A100 GPU的核心数,Tesla系列

目录 GeForce RTX 什么意思 英伟达A100 GPU的核心数 A100概述 NVIDIA GPU GeForce系列(消费级) Quadro系列(专业级) Tesla系列(数据中心和AI) AMD GPU Radeon系列(消费级) 注意 GeForce RTX 什么意思 GeForce RTX是英伟达(NVIDIA)公司旗下的一个高端显卡系…

VS2019 新建项目里没有CUDA选项

问题 在Visual Studio 2019安装之前&#xff0c;先安装了CUDA Toolkit。在使用Visual Studio 2019创建新项目的时候&#xff0c;发现新建项目里没有CUDA的选项。 这时候有两种办法&#xff0c;一种是把CUDA Toolkit卸载重装&#xff0c;重装的时候&#xff0c;CUDA会自己在Visu…

LLM之本地部署GraphRAG(GLM-4+Xinference的embedding模型)(附带ollma部署方式)

前言 有空再写 微软开源的GraphRAG默认是使用openai的接口的&#xff08;GPT的接口那是要money的&#xff09;&#xff0c;于是就研究了如何使用开源模型本地部署。 源码地址&#xff1a;https://github.com/microsoft/graphrag 操作文档&#xff1a;https://microsoft.git…

springboot+vue+mybatis音乐网站的设计+PPT+论文+讲解+售后

听音乐看似是一件休闲娱乐的事情,但是如果你选择合适的音乐,多听一些有益的音乐,会有很多不一样的效果。音乐可以让身体放轻松,好的音乐可以纾解压力, 首先我们想到的是。可以舒缓心情。这一点是没错的&#xff0c;在一个人心情不好的时候&#xff0c;或者压抑的时候&#xff…

HDU1097——A hard puzzle,HDU1098——Ignatius‘s puzzle,HDU1099——Lottery

目录 HDU1097——A hard puzzle 题目描述 运行代码 代码思路 HDU1098——Ignatiuss puzzle 题目描述 ​编辑​编辑运行代码 代码思路 HDU1099——Lottery 题目描述 运行代码 代码思路 HDU1097——A hard puzzle 题目描述 Problem - 1097 运行代码 #include <…

openhasp入门--介绍

**OpenHASP** 是一个开源的智能家居控制器&#xff0c;使用ESP32&#xff0c;采用配置方式生成图形界面&#xff0c;免去写代码 1. 高度可定制性&#xff1a;用户可以根据自己的需求和偏好&#xff0c;灵活配置和定制各种自动化规则和场景。例如&#xff0c;您可以设置在特定时…