平衡二叉树(AVL树)C++

news2024/10/6 14:22:53

目录

AVL树的概念

AVL树的节点结构

AVL树的插入

更新平衡节点

代码实现

AVL树的旋转

左单旋

右单旋

左右双旋

右左双旋

AVL树的删除

AVL树的查找

AVL树的高度

AVL树的判定

AVL树的遍历


AVL树的概念

二叉排序(搜索)树,虽然可以缩短查找的效率,但是在数据接近有序的时候,二叉排序树会退化成为单支树(斜树),相当于在顺序表中查找元素,效率低下。

因此,两位俄罗斯的数学家发明了一种解决上述问题的办法,向二叉搜索树插入新的节点后,通过调整保证左右子树的高度差绝对值不大于1,从而降低树的高度,减少平均搜索长度。

平衡二叉树(Balanced Binary Tree)是一种特殊的二叉排序树,又称AVL树。

AVL树的性质:

1.AVL树是二叉排序树

2.左右子树高度之差(即平衡因子)的绝对值不超过1,即平衡因子可能为-1,0,1,

AVL树的节点结构

用KV模型定义二叉树,把节点定义成三叉链结构,因为构造的新节点左右子树都为空树,所以平衡因子设为0

平衡因子:右子树的高度-左子树的高度

template<class K,class V>
struct AVLTreeNode 
{
	//存储的键值对
	pair<K, V> _kv;

	//平衡因子(balance factor)
	int _bf;

	//存储的三叉链
	AVLTreeNode<K, V>* _parent;//方便找到父节点
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;

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

AVL树的插入

更新平衡节点

AVL树插入结点时有以下三个步骤:

1.按照二叉搜索树的插入方法,找到待插入位置。
2.找到待插入位置后,将待插入结点插入到树中。
3.更新平衡因子,如果出现不平衡,则需要进行旋转。

与二叉搜索树相比,多了更新平衡因子这个步骤。因为插入节点后树的高度可能会变化

更新父节点的平衡因子(一定要更新)

1.左边插入:parent的平衡因子--

2.右边插入:parent的平衡因子++

根据父节点的平衡因子,判断是否要更新祖先节点的平衡因子

1.如果父节点bf==0,说明父节点原来一定是-1或+1,插入的节点弥补了原来的空缺,树的高度没有改变,不用向上更新

2.如果为+-1,说明平衡因子从0变成了-1,+1,高度改变,所以要向上更新

3.如果为+-2,说明该父节点就是最小不平衡子树的根节点,不用再向上查找

 

 

代码实现

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

public:
	AVLTree()
		:_root(nullptr)
	{}

	bool Insert(const pair<K,V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;//指向当前节点的位置
		Node* parent = nullptr;//当前节点的父节点

		//找到插入位置
		while (cur)
		{
			if (cur->_kv.first < kv.first) {
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first > kv.first) {
				parent = cur;
				cur = cur->_right;
			}
			else {
				return false;
			}
		}

		cur = new Node(kv);
		//链接并更新
		if (cur->_kv.first < parent->_kv.first) {
			parent->_left = cur;
			cur->_parent = parent;
			parent->_bf--;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
			parent->_bf++;
		}

		while (parent)//最远要更新到根节点
		{
			if (parent->_bf == 0) {
				break;
			}
			else if (abs(parent->_bf) == 1)
			{
				cur = cur->_parent;
				parent = parent->_parent;//向上查找
			}
			else if(abs(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 if (parent->_bf == 2 && cur->bf == -1)
				{
					rotateRL(parent);
				}
				break;

			}
			else
			{
				//如果程序走到这里,说明在此之前就有不平衡的子树
				assert(false);
			}
		}

		return true;
	}
};

AVL树的旋转

左单旋

什么时候需要用到左旋操作呢?

当 parent 的平衡因子为 2,cur 的平衡因子为 1 时,需要进行左单旋。

并且经过左单旋后,树的高度没有发生变化,所以左单旋后无需继续往上更新平衡因子。

请添加图片描述

在这里插入图片描述

 在这里插入图片描述

 步骤:

  • 先让 subR 的左子树(subRL)作为 parent 的右子树。
  • 然后让 parent 作为 subR 的左子树。
  • 接下来让 subR 作为整个子树的根。
  • 最后更新平衡因子。

 代码实现

void rotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		//1.建立subRL与parent之间的关系
		//左子树滑动
		parent->_right = subRL;
		if (subRL) {
			subRL->_parent = parent;
		}

		//2.建立subR和parent之间的关系
		//更新右子树的左子树
		subR->_left = parent;
		parent->_parent = subR;

		//3.建立pparent和subR之间的关系
		//与上一个节点建立连接
		if (parent == _root)
		{
			_root = subR;
			subR->_parent == nullptr;
		}
		else
		{
			subR->_parent = pparent;
			if (parent = pparent->_left) {
				pparent->_left = subR;
			}
			else{
				pparent->_right = subR;
			}
		}

		//更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}

右单旋

请添加图片描述

  • 先让 subL 的右子树(subLR)作为 parent 的左子树。
  • 然后让 parent 作为 subL 的右子树。
  • 接下来让 subL 作为整个子树的根。
  • 最后更新平衡因子。
void rotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;


		//滑动
		parent->_left = subLR;
		if (subLR) {
			subLR->_parent = parent;
		}

		//更新左子树的右子树
		subL->_right = parent;
		parent->_parent = subL;

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

左右双旋

(1)以30为节点进行左单旋

在这里插入图片描述

 (2)以90为节点进行右单旋

 

左右双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:

(1)当 subLR 原始平衡因子是 -1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 1、0、0

在这里插入图片描述

(2)当 subLR 原始平衡因子是 1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 0、-1、0

(3)当 subLR 原始平衡因子是 0 时(说明 subLR 为新增结点),左右双旋后 parent、subL、subLR 的平衡因子分别更新为0、0、0

可以看到,经过左右双旋后,树的高度没有发生变化,所以左右双旋后无需继续往上更新平衡因子。

代码实现:

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

		int bf = subLR->_bf;
	
		rotateL(subL);
		//parent并不为最小不平衡子树的根,所以要在旋转以后重新赋值
		rotateR(parent);
		
		//各点的bf值由调整前subLR的bf值决定
		if (_bf == 0) {
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋

(1)以 90 为旋转点进行右单旋

在这里插入图片描述

(2)以 30 为旋转点进行左单旋

 

右左双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:、

(1)当 subRL 原始平衡因子是 1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 -1、0、0

 

(2)当 subRL 原始平衡因子是 -1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 0、1、0

 

(3)当 subRL 原始平衡因子是 0 时(说明 subRL为新增结点),左右双旋后 parent、subR、subRL 的平衡因子分别更新为0、0、0

 

可以看到,经过右左双旋后,树的高度没有发生变化,所以右左双旋后无需继续往上更新平衡因子。
 

代码实现:

void rotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		rotateR(subR);
		rotateL(parent);

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

	}

AVL树的删除

要进行结点的删除,首先需要在树中找到对应key值的结点,寻找待删除结点的方法和二叉搜索树相同

1.先找到待删除的结点。
2.若找到的待删除结点的左右子树均不为空,则需要使用替换法进行删除。

替换法删除指的是:让待删除结点左子树当中key值最大的结点,或待删除结点右子树当中值最小的结点代替待删除结点被删除(代码中以后者为例),然后再将待删除结点的key值以及value值都改为代替其被删除的结点的值即可。

也就是说,我们最终找到的实际被删除的结点的左右子树当中至少有一个为空树。

在找到实际需要被删除的结点后,我们先不进行实际的删除,而是先进行平衡因子的更新,不然后续更新平衡因子时特别麻烦(已经尝试过),而更新平衡因子时的规则与插入结点时的规则是相反的,更新规则如下:

1.删除的结点在parent的右边,parent的平衡因子− − 。
2.删除的结点在parent的左边,parent的平衡因子+ +。

并且每更新完一个结点的平衡因子后,都需要进行以下判断:

1.如果parent的平衡因子等于-1或者1,表明无需继续往上更新平衡因子了。
2.如果parent的平衡因子等于0,表明还需要继续往上更新平衡因子。
3.如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。

在旋转处理时,分为以下6种情况

1.当parent的平衡因子为-2,parent的左孩子的平衡因子为-1时,进行右单旋。
2.当parent的平衡因子为-2,parent的左孩子的平衡因子为1时,进行左右双旋。
3.当parent的平衡因子为-2,parent的左孩子的平衡因子为0时,也进行右单旋,但此时平衡因子调整与之前有所不同,具体看代码。
4.当parent的平衡因子为2,parent的右孩子的平衡因子为-1时,进行右左双旋。
5.当parent的平衡因子为2,parent的右孩子的平衡因子为1时,进行左单旋。
6.当parent的平衡因子为2,parent的右孩子的平衡因子为0时,也进行左单旋,但此时平衡因子调整与之前有所不同,具体看代码。

代码实现:

bool Erase(const K& key)
	{
		//用于遍历二叉树
		Node* cur = parent;
		Node* parent = nullptr;

		//用于标记实际删除的节点和父节点
		Node* deletePos = nullptr;
		Node* deleteparentPos = nullptr;

		//确定删除节点
		while (cur) {
			if (key < cur->_kv.first) {
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_kv.first) {
				parent = cur;
				cur = cur->_right;
			}
			else//遇到了待删除的节点
			{
				if (cur->_left == nullptr)//如果待删除节点的左子树为空
				{
					if (cur == _root)//如果待删除节点是根节点
					{
						_root = cur->_right;
						if (_root) {
							_root->_parent = nullptr;
						}
						delete cur;
						return true;
					}
					else
					{
						deleteparentPos = parent;
						deletePos = cur;
						break;//有祖先节点说明要更新平衡因子
					}
				}
				else if (cur->_right == nullptr)//如果待删除节点的右子树为空
				{
					if (cur == _root)//如果待删除节点是根节点
					{
						_root = cur->_left;
						if (_root) {
							_root->_parent = nullptr;
						}
						delete cur;
						return true;
					}
					else
					{
						deleteparentPos = parent;
						deletePos = cur;
						break;//有祖先节点说明要更新平衡因子
					}
				}
				else//如果待删除节点的左右子树都不为空,替换法删除
				{
					//替换法删除
					//寻找待删除节点右子树中key值最小的节点作为实际删除节点(转化为左子树为空的局面)
					Node* minparent = cur;
					Node* minright = cur->_right;
					while (minright->_left)
					{
						minparent = minright;
						minright = minright->_left;
					}
					//将待删除节点的属性更新为右子树中key值最小节点的属性
					cur->_kv.first = minright->_kv.first;
					cur->_kv.second = minright->_kv.second;

					//标记右子树中key值最小的节点为删除节点
					deleteparentPos = minparent;
					deletePos = minright;
					break;
				}
			}
		}

		//如果deletePos没有更新,说明没有找到根节点
		if (deletePos == nullptr)
		{
			return false;
		}

		//记录待删除节点及其父节点(实际删除时要用)
		cur->_kv.first = minright->_kv.first;
		Node* del = deletePos;
		Node* delparent = deleteparentPos;

		//更新平衡因子
		while (deletePos != _root)//可能要一直更新到根节点
		{
			//判断删除的是父亲的哪一边,然后更新平衡因子
			if (deletePos = deleteparentPos->_left) {
				deleteparentPos->_bf++;
			}
			else
			{
				deleteparentPos->_bf--;
			}

			//判断是否需要往上继续更新
			if (abs(deleteparentPos->_bf) == 1) {
				break;
			}
			else if (deleteparentPos->_bf == 0) //其他两种情况树的高度都会变化所以要继续往上跟新平衡因子
			{
				deletePos = deleteparentPos;
				deleteparentPos = deleteparentPos->_parent;
			}
			else if (abs(deleteparentPos->_bf) == 2)//需要进行旋转处理
			{
				//判断旋转情况
				if (deleteparentPos->_bf = -2)
				{
					if (deleteparentPos->_left->_bf == -1) {
						Node* tmp = deleteparentPos->_left;//记录右转后新的根节点
						rotateR(deleteparentPos);
						deleteparentPos = tmp;//旋转后根节点更新
					}
					else if(deleteparentPos->_left->_bf == 1) {
						Node* tmp = deleteparentPos->_left->_right;//记录右转后新的根节点
						rotateLR(deleteparentPos);
						deleteparentPos = tmp;//旋转后根节点更新
					}
					else//deleteparentPos->_left->_bf = 0
					{
						Node* tmp = deleteparentPos->_left;
						rotateR(deleteparentPos);
						deleteparentPos = tmp;
						deleteparentPos->_bf = 1;
						deleteparentPos->_right->_bf = -1;
						break;
					}
				}
				else//deleteparentPos->_bf = 2
				{
					if (deleteparentPos->_right->_bf == 1)
					{
						Node* tmp = deleteparentPos->_right->_left;
						rotateRL(deleteparentPos);
						deleteparentPos = tmp;
					}
					else if (deleteparentPos->_right->_bf == -1)
					{
						Node* tmp = deleteparentPos->_right;
						rotateL(deleteparentPos);
						deleteparentPos = tmp;
					}
					else
					{
						Node* tmp = deleteparentPos->_right;
						rotateL(deleteparentPos);
						deleteparentPos = tmp;
						deleteparentPos->_bf = -1;
						deleteparentPos->_left->_bf = 1;
						break;
					}
				}

			}
			else {
				assert(false);
			}

			//delParentPos树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子
			deletePos = deleteparentPos;
			deleteparentPos = deleteparentPos->_parent;

		}

		if (del->_left == nullptr) {
			if (del = delparent->_left) {
				delparent->_left = del->_right;
				if (del->_right) {
					del->_right->_parent = delparent;
				}
			}
			else {
				delparent->_right = del->_right;
				if (del->_right) {
					del->_right->_parent = delparent;
				}
			}
		}
		else{
			if (del == delparent->_left) {
				delparent->_left = del->_left;
				if (del->_left) {
					del->_left->_parent = delparent;
				}
			}
			else {
				delparent->_right = del->_left;
				if (del->_left) {
					del->_left->_parent = delparent;
				}
			}
		}
		delete del;
		return true;

	}

AVL树的查找

Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key) // key值大于该结点的值
			{
				cur = cur->_left; // 在该结点的右子树当中查找
			}
			else if (cur->_kv.first < key) // key值小于该结点的值
			{
				cur = cur->_right; // 在该结点的左子树当中查找
			}
			else // 当前节点的值等于key值
			{
				return cur; //返回该结点
			}
		}
		return nullptr; //查找失败
	}

AVL树的高度

采用了分治的思想

private:
void _Height(Node* root)
	{
		if (!root) {
			return 0;
		}
		int left = _Height(root->_left);
		int right = _Height(root->_right);
		
		return left > right ? left + 1 : right + 1;
	}
public:
void Height()
	{
		_Height(_root);
	}

AVL树的判定

	bool _IsBalancedTree(Node* root) 
	{
		if (!root) {
			return true;
		}

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;

		if (abs(diff) > 2) {
			cout << "wrong" << endl;
			return false;
		}
		else if (diff != root->_bf) {
			return  false;
		}

		return _IsBalancedTree(root->_left) && _IsBalancedTree(root->_right);
	}

AVL树的遍历

void _Inorder(Node* root)
	{
		if (!root) {
			return;
		}
		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}

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

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

相关文章

葡萄叶病害识别(图像连续识别和视频识别,Python代码,pyTorch框架)

葡萄叶病害识别&#xff08;图像连续识别和视频识别&#xff0c;Python代码&#xff0c;pyTorch框架&#xff09;_哔哩哔哩_bilibili 葡萄数据集 第一个文件夹为 Grape Black Measles&#xff08;葡萄黑麻疹&#xff09;病害&#xff08;3783张&#xff09; Grape Black rot葡…

108页石油石化5G智慧炼化厂整体方案PPT

导读&#xff1a;原文《108页石油石化5G智慧炼化厂整体方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。以下是部分内容&#xff0c; P P T 喜欢文章&#xf…

【BUG事务内消息发送】事务内消息发送,事务还未结束,消息发送已被消费,查无数据怎么解决?

问题描述 在一个事务内完成插入操作&#xff0c;通过MQ异步通知其他微服务进行事件处理。 由于是在事务内发送&#xff0c;其他服务消费消息&#xff0c;查询数据时还不存在如何解决呢&#xff1f; 解决方案 通过spring-tx包的TransactionSynchronizationManager事务管理器解…

管理与领导-58]:IT基层管理者 - 扩展技能 - 1 - 时间管理 -5- 持续改进— 时间管理的好习惯

前言&#xff1a; 对于大多数管理者而言&#xff0c;提高效能并不能一步到位&#xff0c;需要不断的实践、总结、持续的改进和优化&#xff0c;最终达到较高的效能&#xff0c;持续学习、持续改进是管理者一项终身精进的能力&#xff01;&#xff01;&#xff01;养成时刻进行…

程序员赚钱的六种方式:技术实力和市场分析能力是关键

随着互联网的普及和发展&#xff0c;越来越多的人开始在网上寻找商机&#xff0c;其中程序员作为一个高技能的群体&#xff0c;可以利用自身的编程技术在网上获得收益。以下是一些程序员可以利用自己的编程技术在网上赚钱的途径&#xff1a; 开发软件&#xff1a;程序员可以利…

Spring Boot(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot 前后端分离)【五】

&#x1f600;前言 本篇博文是关于Spring Boot(Vue3ElementPlusAxiosMyBatisPlusSpring Boot 前后端分离)【五】&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章…

本地组策略编辑器找不到怎么解决?| 解决windows home 版本隐藏本地组策略编辑器的问题 | 简单的介绍本地组策略编辑器

一般的 Windows 非家庭系统中&#xff0c;本地组策略编辑器不会被隐藏&#xff0c;但在某些特定情况下&#xff0c;可能会受到限制或不可用。如果你无法访问本地组策略编辑器&#xff0c;并且认为应该可以访问&#xff0c;请确保你拥有管理员权限&#xff0c;并检查是否有任何系…

golang-bufio 缓冲读

缓冲 IO 计算机中我们常听到这样的两种程序优化方式&#xff1a; 以时间换空间以空间换时间 今天要来看的缓冲IO就是典型的以空间换时间&#xff0c;它的基本原理见上图。简单的解释就是&#xff1a;程序不再直接去读取底层的数据源&#xff0c;而是通过一个缓冲区来进行读取…

SPI3+DMA外设驱动-TFTLCD初始化

前言 &#xff08;1&#xff09;本系列是基于STM32的项目笔记&#xff0c;内容涵盖了STM32各种外设的使用&#xff0c;由浅入深。 &#xff08;2&#xff09;小编使用的单片机是STM32F105RCT6&#xff0c;项目笔记基于小编的实际项目&#xff0c;但是博客中的内容适用于各种单片…

算法leetcode|74. 搜索二维矩阵(rust重拳出击)

文章目录 74. 搜索二维矩阵&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 74. 搜索二维矩阵&#xff1a; 给你一个满足下述两条属性的…

新手将最简单的springboot部署上tomcat出现的意外问题

现阶段springboot部署到tomcat的文章一抓一大把且都相同,便贴一个地址以展示流程: SpringBoot打war包部署Tomcat(最全)_spring boot war 部署tomcat_聊Java的博客-CSDN博客 那么就说一下我出现的问题: 在完整复现流程且确认代码无误的情况下,部署到tomcat,此时问题出现了:启动…

vue中html引入使用<%= BASE_URL %>变量

首先使用src相对路径引入 注意&#xff1a; js 文件放在public文件下 不要放在assets静态资源文件下 否则 可能会报错 GET http://192.168.0.113:8080/src/assets/js/websockets.js net::ERR_ABORTED 500 (Internal Server Error) 正确使用如下&#xff1a;eg // html中引…

Midjourney学习(二)参数的基础

prompt的组成 prompt 可以由三部分组成&#xff0c; 第一部分是垫图部分&#xff0c;也就是一张网络图片 第二部分是文本描述内容 第三部分则是参数 参数列表 --aspect <value> 或者 --ar <value> 控制画面的比例&#xff0c;横竖比例 --version <value> -…

复数的四则运算(java版)

复数的四则运算&#xff08;java版&#xff09; 目录 复数的四则运算&#xff08;java版&#xff09;介绍复数的四则运算实现思路代码1、封装复数类2、测试复数类3、代码测试结果 介绍 复数&#xff0c;为实数的延伸&#xff0c;它使任一多项式方程都有根。复数当中有个“虚数单…

攻击与防御实战经验分享:分析真实的攻击事件和入侵行为,讨论防御方法和实践经验

章节 1: 前言 作为IT领域的从业者&#xff0c;我们时刻都面临着网络安全的挑战。攻击者不断寻找漏洞&#xff0c;而防御者则需要时刻保持警惕&#xff0c;采取最佳实践来保护系统和数据。在本文中&#xff0c;我们将分享一些真实的攻击事件和入侵行为&#xff0c;并探讨针对这…

JVM 垃圾收集

垃圾收集 分代理论Java 堆的内存分区不同分代收集垃圾收集算法 分代理论 弱分代假说&#xff1a;绝大多数对象都是朝生夕灭&#xff0c;即绝大多数对象都是用完很快需要销毁的。强分代假说&#xff1a;熬过多次垃圾收集过程的对象就越难以消亡&#xff0c;即如果对象经过多次垃…

6. vue-element-admin 二次开发避坑指南

vue-element-admin 二次开发避坑指南 1.1 前言1.1.1 切换标签时未保存页面的操作内容1.1.2 markdown 样式乱码1.1.3 修改默认尺寸1.1.4 当后端服务器宕机情况下页面加载层一直转圈无法停止&#xff0c;只能关闭页面1.1.5 隐藏齿轮 1.1 前言 上一篇博文&#xff0c;我们分享了vu…

HarmonyOS Codelab 优秀样例——购物应用,体验一次开发多端部署魅力

一. 样例介绍 本篇Codelab基于自适应布局和响应式布局&#xff0c;实现购物应用在手机、折叠屏、平板不同屏幕尺寸设备上按不同设计显示。通过三层工程结构组织代码&#xff0c;实现一次开发&#xff0c;多端部署 。 手机运行效果如图所示&#xff1a; 折叠屏运行效果图&#x…

Thymeleaf解析表达式

首先springThymeleaf必过的点 org.springframework.web.servlet.DispatcherServlet#doDispatch那么先跟入handle()方法 然后跟进handleInternal方法 可以看到mav的获取方法&#xff0c;继续跟进invokeHandlerMethod 继续跟进invokeAndHandle 这里判断returnvalue是否有值…

智能工厂移动式作业轻薄加固三防平板数据采集终端

在这个高度自动化和数字化的环境中&#xff0c;数据采集变得尤为重要。为了满足这个需求&#xff0c;工业三防平板数据采集终端应运而生。工业三防平板数据采集终端采用了轻量级高强度镁合金材质&#xff0c;这使得它在保持轻薄的同时具有更强的坚固性。这种材质还具有耐磨防损…