【数据结构高阶】AVL树

news2025/1/18 8:49:19

上期博客我们讲解了set/multiset/map/multimap的使用,下面我们来深入到底层,讲解其内部结构:

目录

一、AVL树的概念

二、AVL树的实现

2.1 节点的定义

2.2 数据的插入

2.2.1 平衡因子的调整

2.2.1.1 调整平衡因子的规律

2.2.2 子树的旋转调整

2.2.2.1 左单旋

2.2.2.2 右单旋

2.2.2.3 左右双旋

2.2.2.4 右左双旋

2.3 AVL树的检查验证

2.4 测试代码

三、AVL树实现的完整代码


一、AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

● 它的左右子树都是AVL树

● 左右子树高度之差(简称平衡因子,不是每种AVL树都有平衡因子,平衡因子只是AVL树实现的一种方式)的绝对值不超过1(下图的平衡因子计算公式为:节点的右子树高度-左子树高度)

下图是一个AVL树:

二、AVL树的实现

2.1 节点的定义

这次我们直接实现K-V模型的二叉树:

template<class Key,class Val>
class AVLTreeNode
{
public:
	AVLTreeNode<Key, Val>* _left;
	AVLTreeNode<Key, Val>* _right;
	AVLTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作
	pair<Key, Val> _kv;
	int _bf;//balance factor(平衡因子)

	AVLTreeNode(pair<Key, Val> kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_bf(0)
	{}
};

2.2 数据的插入

AVL树的数据插入需要遵循二叉搜索树的规律:

template<class Key, class Val>
class AVLTree
{
public:
	typedef AVLTreeNode<Key, Val> Node;

	bool Insert(const pair<Key,Val>& kv)
	{
		Node* cur = _root, * parent = nullptr;
		while (cur)//找到合适的位置
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				cout << "插入的值重复" << endl;
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		//将插入的节点连接上二叉树
		if (parent == nullptr)
		{
			_root = cur;
		}
		else if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}

private:
	AVLTreeNode<Key, Val>* _root = nullptr;
};

但是成功插入数据后就结束了吗?在AVL树中可没有这么简单,我们需要更新其插入节点的父节点的平衡因子,来保证这课树的整体结构还是一个AVL树。

2.2.1 平衡因子的调整

下面我们来讨论一下更新父节点平衡因子的操作:

我们看到上面的二叉树,现在向其中插入一个键值为8的数据:

插入后我们发现其插入节点的父节点平衡因子需要修改,那就要修改一下:

修改其父节点后,我们发现其父节点的父节点的平衡因子也需要调整,那再调整一下吧:

但是本次调整过后,平衡因子出现了2这个数值,但是平衡因子只能是1/0/-1,说明这时该节点下的子树已经不满足AVL树的结构了,这时要对其子树进行旋转来调整该树的结构(至于怎么旋转我们在下面会详细讲解),经过旋转过后子树肯定是满足AVL树的结构的,所以就并不再检查进行旋转的节点父节点平衡因子了:

那我们再看看另一种情况,我们向下面的二叉树中插入键值为6的数据:

插入后,我们发现需要修改其父节点的平衡因子: 

修改后,我们发现其父节点的平衡因子为0,为0时就意味着子树的高度没有发生变化,我们就没有必要再向上更新其父节点的平衡因子了: 

2.2.1.1 调整平衡因子的规律

所以总结一下上述规律,我们插入节点后肯定是要调整其父节点的平衡因子的,那调整父节点的平衡因子后,什么时候要向上调整其父节点的父节点(爷爷节点)的平衡因子呢?

这里有三种情况:

当父节点的平衡因子调整过后为1或-1时,此时说明其节点所在的子树的高度发生了变化(变为1或-1前,平衡因子只可能是0,说明插入之前子树两边的高度是相同的,这时在插入一个节点会导致其中一颗子树的高度发生变化),这时需要再继续向上调整其父节点的平衡因子

当父节点的平衡因子调整过后为2或-2时,该节点的子树不平衡,需要处理这颗旋转处理子树,旋转实质是是将该节点所在的子树的高度降1,所以旋转过后不影响子树的高度变化,此时就不需要再向上更新其父节点的平衡因子了

当父节点的平衡因子调整过后为0时(变为0前,平衡因子只可能是1或-1,说明插入之前一边高,一边低,但是插入节点是在矮的那边,其子树的高度不变),说明所在的子树高度不变,不用继续向上更新

我们用代码来实现一下平衡因子的调整:

template<class Key, class Val>
class AVLTree
{
public:
	typedef AVLTreeNode<Key, Val> Node;

	bool Insert(const pair<Key,Val>& kv)
	{
		Node* cur = _root, * parent = nullptr;
		while (cur)//找到合适的位置
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				cout << "插入的值重复" << endl;
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		//将插入的节点连接上二叉树
		if (parent == nullptr)
		{
			_root = cur;
		}
		else if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//向上调整各节点的平衡因子
		while (parent)
		{
			if (parent->_left == cur)
			{
				--parent->_bf;//更新平衡因子
			}
			else
			{
				++parent->_bf;//更新平衡因子
			}
			//根据父节点的平衡因子的情况决定师傅继续向上调整各节点的平衡因子
			if (parent->_bf == 1 || parent->_bf == -1)//平衡因子为1或1继续向上更新
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//平衡因子为2或-2,树的结构不平衡,旋转parent节点所在子树
			{
				//旋转parent所在的子树
			}
			else if (parent->_bf == 0)//平衡因子为0,插入结束
			{
				break;
			}
			else//出现其他的情况说明树的根据有问题,报错退出
			{
				cout << "树的结构出错了" << endl;
				assert(0);
				exit(-1);
			}
		}
		return true;
	}

private:
	AVLTreeNode<Key, Val>* _root = nullptr;
};

2.2.2 子树的旋转调整

当某个节点的平衡因子变为2或-2时,我们需要对其所在的子树进行旋转调整

下面我们分情况来讨论:

2.2.2.1 左单旋

下面的图代表的是一棵AVL树,其中52和60表示的两个节点,A、B、C分别为高度为h的AVL子树:

下面向C子树中插入数据使其高度发生变化:

现在键值为52的节点平衡因子变为2,下面我们要将这棵子树旋转:

我们可以看到旋转的过程是这样的:

B子树变成52的右子树->52变成60的左子树->60变成整颗树的根

上面这种需要旋转的树的根节点平衡因子为2,并且其根节点右孩子节点的平衡因子为1;这种情况下的旋转我们将其称为左单旋

下面我们就用代码实现一下左单旋:

void RotateL(Node* parent)//左单旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* pparent = parent->_parent;
	parent->_right = subRL;//更新parent的右节点
	if (subRL)//防止该节点为空
	{
		subRL->_parent = parent;//更新subRL的父节点
	}
	parent->_parent = subR;//更新parent的父节点
	subR->_left = parent;//subR的左子树置为parent
	subR->_parent = pparent;//更新subR的父节点
	if (pparent == nullptr)//旋转的是整棵树
	{
		_root = subR;//更新根节点
	}
	else//将旋转后的子树链接上整个二叉树
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subR;
		}
		else
		{
			pparent->_right = subR;
		}
	}
	subR->_bf = parent->_bf = 0;//更新平衡因子
}

下图是画出了代码所表示的节点: 

2.2.2.2 右单旋

再来看到另一种情况:

 下面向子树A中插入数据使其高度发生变化:

现在键值为60的节点平衡因子变为-2,下面我们要将这棵子树旋转: 

我们可以看到旋转的过程是这样的:

B子树变成60的左子树->60变成52的右子树->52变成整颗树的根

上面这种需要旋转的树的根节点平衡因子为-2,并且其根节点左孩子节点的平衡因子为-1;这种情况下的旋转我们将其称为右单旋

下面我们用代码实现一下右单旋:

void RotateR(Node* parent)//右单旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* pparent = parent->_parent;
	parent->_left = subLR;//更新parent的左节点
	if (subLR)//防止该节点为空
	{
		subLR->_parent = parent;//更新subLR的父节点
	}
	parent->_parent = subL;//更新parent的父节点
	subL->_right = parent;//subL的右子树置为parent
	subL->_parent = pparent;//更新subL的父节点
	if (pparent == nullptr)//旋转的是整棵树
	{
		_root = subL;//更新根节点
	}
	else//将旋转后的子树链接上整个二叉树
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subL;
		}
		else
		{
			pparent->_right = subL;
		}
	}
	subL->_bf = parent->_bf = 0;//更新平衡因子
}

下图画出了代码所表示的节点: 

2.2.2.3 左右双旋

接着来看到稍微复杂一点的情况,下面的图代表的是一棵AVL树,其中99、66和88表示的是三个节点,A、D分别为高度为h的AVL子树,B、C分别为高度为h-1的AVL子树:

现在我们向B子树中插入数据使其高度发生变化:

现在键值为99的节点平衡因子变为-2,下面我们要将这棵子树旋转: 

下面先将66所在子树进行左单旋:

再让99所在子树右单旋:

上面这种需要旋转的树的根节点平衡因子为-2,并且其根节点左孩子节点的平衡因子为1;这种情况下的两次旋转我们将其称为左右双旋

但是我们要注意的是,当新增节点在C子树上时,通过左右双旋调整后的结果又不一样:

 

我们可以看到因为新增节点的位置发生了变化,最终导致了各节点的平衡因子出现了差异

除了这两种情况,还有一种当h为0的极端情况:

所以我们在用代码实现的时要注意

void RotateLR(Node* parent)//左右双旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int subLR_bf = subLR->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据
	RotateL(subL);//调用左单旋
	RotateR(parent);//调用右单旋
	if (subLR_bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (subLR_bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (subLR_bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

2.2.2.4 右左双旋

最后我们只剩一种情况了:需要旋转的树的根节点平衡因子为-2,并且其根节点右孩子节点的平衡因子为-1

下面我们来分析:

我们向上面这棵树中的C子树插入数据使其高度发生变化:

现在键值为66的节点平衡因子变为2,下面我们要将这棵子树旋转:

下面先将99所在子树进行右单旋:

再将66所在子树进行左单旋:

这样先右旋再左旋的情况我们将其称做:右左双旋

和左右双旋一样新增节点的位置发生变化,会导致各节点的平衡因子出现差异,当新增节点在B子树上时,通过左右双旋调整后的结果又不一样:

另外还有h为0的特殊情况:

好了,分析到这里,使用代码实现也就不是问题了:

void RotateRL(Node* parent)//右左双旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int subRL_bf = subRL->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据
	RotateR(subR);//调用右单旋
	RotateL(parent);//调用左单旋
	if (subRL_bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (subRL_bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (subRL_bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

2.3 AVL树的检查验证

下面我们来实现一个函数来检查自己所创建的AVL树是否符合规则:

int _CountHeight(Node* root)//计算树的高度
{
	if (root == nullptr)
		return 0;
	int leftnum = _CountHeight(root->_left);
	int rightnum = _CountHeight(root->_right);
	return leftnum > rightnum ? leftnum + 1 : rightnum + 1;
}
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftH = _CountHeight(root->_left);
	int rightH = _CountHeight(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);//检查左右子树高度之差的绝对值是否小于2
}
bool IsBalance()//检查是否为AVL树
{
	return _IsBalance(_root);
}

2.4 测试代码

void Test_AVLTree()
{
	const size_t N = 5000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
	cout << t.CountHeight() << endl;
}

运行效果:

三、AVL树实现的完整代码

#pragma once
#include<iostream>
#include<cassert>

using namespace std;

template<class Key,class Val>
class AVLTreeNode
{
public:
	AVLTreeNode<Key, Val>* _left;
	AVLTreeNode<Key, Val>* _right;
	AVLTreeNode<Key, Val>* _parent;//多一个指针指向其父节点,方便我们的后续操作
	pair<Key, Val> _kv;
	int _bf;//balance factor(平衡因子)

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


template<class Key, class Val>
class AVLTree
{
	typedef AVLTreeNode<Key, Val> Node;

public:
	bool Insert(const pair<Key,Val>& kv)
	{
		Node* cur = _root, * parent = nullptr;
		while (cur)//找到合适的位置
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				cout << "插入的值重复" << endl;
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		//将插入的节点连接上二叉树
		if (parent == nullptr)
		{
			_root = cur;
		}
		else if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//向上调整各节点的平衡因子
		while (parent)
		{
			if (parent->_left == cur)
			{
				--parent->_bf;//更新平衡因子
			}
			else
			{
				++parent->_bf;//更新平衡因子
			}
			//根据父节点的平衡因子的情况决定师傅继续向上调整各节点的平衡因子
			if (parent->_bf == 1 || parent->_bf == -1)//平衡因子为1或1继续向上更新
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//平衡因子为2或-2,树的结构不平衡,旋转parent节点所在子树
			{
				//旋转parent所在的子树
				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 if (parent->_bf == 0)//平衡因子为0,插入结束
			{
				break;
			}
			else//出现其他的情况说明树的根据有问题,报错退出
			{
				cout << "树的结构出错了" << endl;
				assert(0);
				exit(-1);
			}
		}
		return true;
	}

	void InOrder()//中序遍历
	{
		_InOrder(_root);
		cout << endl;
	}

	int CountHeight()//计算树的高度
	{
		return _CountHeight(_root);
	}

	bool IsBalance()//检查是否为AVL树
	{
		return _IsBalance(_root);
	}

private:
	void RotateL(Node* parent)//左单旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;
		parent->_right = subRL;//更新parent的右节点
		if (subRL)//防止该节点为空
		{
			subRL->_parent = parent;//更新subRL的父节点
		}
		parent->_parent = subR;//更新parent的父节点
		subR->_left = parent;//subR的左子树置为parent
		subR->_parent = pparent;//更新subR的父节点
		if (pparent == nullptr)//旋转的是整棵树
		{
			_root = subR;//更新根节点
		}
		else//将旋转后的子树链接上整个二叉树
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
		}
		subR->_bf = parent->_bf = 0;//更新平衡因子
	}

	void RotateR(Node* parent)//右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;
		parent->_left = subLR;//更新parent的左节点
		if (subLR)//防止该节点为空
		{
			subLR->_parent = parent;//更新subLR的父节点
		}
		parent->_parent = subL;//更新parent的父节点
		subL->_right = parent;//subL的右子树置为parent
		subL->_parent = pparent;//更新subL的父节点
		if (pparent == nullptr)//旋转的是整棵树
		{
			_root = subL;//更新根节点
		}
		else//将旋转后的子树链接上整个二叉树
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
		}
		subL->_bf = parent->_bf = 0;//更新平衡因子
	}

	void RotateLR(Node* parent)//左右双旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int subLR_bf = subLR->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据
		RotateL(subL);//调用左单旋
		RotateR(parent);//调用右单旋
		if (subLR_bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if(subLR_bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (subLR_bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateRL(Node* parent)//右左双旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int subRL_bf = subRL->_bf;//记录该节点的平衡因子来做旋转结束后的修改平衡因子的依据
		RotateR(subR);//调用右单旋
		RotateL(parent);//调用左单旋
		if (subRL_bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (subRL_bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (subRL_bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void _InOrder(Node* root)
	{
		if (root == NULL)//如果是空树就直接结束
		{
			return;
		}
		_InOrder(root->_left);//先递归遍历其左子树
		cout << root->_kv.first << " ";//再遍历其根节点
		_InOrder(root->_right);//最后递归遍历其右子树
	}

	int _CountHeight(Node* root)//计算树的高度
	{
		if (root == nullptr)
			return 0;
		int leftnum = _CountHeight(root->_left);
		int rightnum = _CountHeight(root->_right);
		return leftnum > rightnum ? leftnum + 1 : rightnum + 1;
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftH = _CountHeight(root->_left);
		int rightH = _CountHeight(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);//检查左右子树高度之差的绝对值是否小于2
	}
private:
	AVLTreeNode<Key, Val>* _root = nullptr;
};

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

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

相关文章

YOLOv5改进 | 添加ECA注意力机制 + 更换主干网络之ShuffleNetV2

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本文给大家介绍一种轻量化部署改进方式&#xff0c;即在主干网络中添加ECA注意力机制和更换主干网络之ShuffleNetV2&#xff0c;希望大家学习之后&#xff0c;能够彻底理解其改进流程及方法~&#xff01;&#x1f308; 目…

分享77个焦点幻灯JS特效,总有一款适合您

分享77个焦点幻灯JS特效&#xff0c;总有一款适合您 77个焦点幻灯JS特效下载链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知识付费甚欢喜&…

sourceTree的下载和安装

sourceTree的下载和安装 一、概述 SourceTree 是一款免费的 Git 和 Hg 客户端管理工具&#xff0c;支持 Git 项目的创建、克隆、提交、push、pull 和合并等操作。它拥有一个精美简洁的界面&#xff0c;大大简化了开发者与代码库之间的 Git 操作方式&#xff0c;这对于不熟悉 …

WebGL笔记:矩阵缩放的数学原理和实现

矩阵缩放的数学原理 和平移一样&#xff0c;以同样的原理&#xff0c;也可以理解缩放矩阵让向量OA基于原点进行缩放 x方向上缩放&#xff1a;sxy方向上缩放&#xff1a;syz方向上缩放&#xff1a;sz 最终得到向量OB 矩阵缩放的应用 比如我要让顶点在x轴向缩放2&#xff0c;y轴…

SCAU:分期还款(加强版)

分期还款(加强版) Time Limit:1000MS Memory Limit:65535K 题型: 编程题 语言: G;GCC;VC 描述 从银行贷款金额为d&#xff0c;准备每月还款额为p&#xff0c;月利率为r。请编写程序输入这三个数值&#xff0c;计算并输出多少个月能够还清贷款&#xff0c;输出时保留1位小…

java学习part32StringBuffer和StringBuilder

Java中的值传递和引用传递&#xff08;详解&#xff09; - 知乎 (zhihu.com) 146-常用类与基础API-StringBuffer与StringBuilder的源码分析、常用方法_哔哩哔哩_bilibili 1. 2.扩容机制 不够用&#xff1a;长度为 原长度*22&#xff1b;如果还不够&#xff0c;那么就扩容到目…

C++笔试训练day_1

文章目录 选择题编程题 选择题 编程题 #include <iostream> #include <algorithm> #include <vector>using namespace std;int main() {int n 0;cin >> n;vector<int> v;v.resize(3 * n);int x 0;for(int i 0; i < v.size(); i){cin >&…

ES-ELSER 如何在内网中离线导入ES官方的稀疏向量模型(国内网络环境下操作方法)

ES官方训练了稀疏向量模型&#xff0c;用来支持语义检索。&#xff08;目前该模型只支持英文&#xff09; 最好是以离线的方式安装。在线的方式&#xff0c;在国内下载也麻烦&#xff0c;下载速度也慢。还不如用离线的方式。对于一般的生产环境&#xff0c;基本上也是网络隔离的…

初识Linux:保姆级教学,让你一秒记住Linux中的常用指令!

文章目录 前言一、LInux的背景及发展史二、Linux下的基本指令1、ls指令2、pwd指令3、cd指令4、touch指令5、mkdir指令&#xff08;重要&#xff09;6、tree指令7、rmdir指令和rm指令&#xff08;重要&#xff09;8、man指令&#xff08;重要&#xff09;9、cp指令&#xff08;重…

[进程控制]模拟实现命令行解释器shell

文章目录 1.字符串切割函数2.chdir()接口3.模拟实现shell 1.字符串切割函数 2.chdir()接口 3.模拟实现shell 模拟实现的shell下删除: ctrlbackspace模拟实现下table/上下左右箭头无法使用[demo] #include <stdio.h> #include <stdlib.h> #include <string.h&g…

nodejs介绍

nodejs官网支持的各种库api https://nodejs.org/docs/latest-v21.x/api/http.html nodejs包括vp8引擎和内置的基本库如fs,path,http,querystring等&#xff0c;也可以用npm按转第三方库 npm是nodejs环境的包管理工具&#xff0c;可以为这个环境安装卸载各种包。 npm install pk…

Git Bash环境下用perl脚本获取uuid值

在Linux环境下&#xff0c;比如在ubuntu就直接有uuidgen命令直接获取uuid值。在Windows环境下常用的git bash中没有对应的命令&#xff0c;略有不便。这里用脚本写一个uuidgen&#xff0c;模拟Linux环境下的uuidgen命令。 #! /usr/bin/perl use v5.14; use Win32;sub uuidGen {…

frp 配置内网访问

frp介绍 frp 是一个开源、简洁易用、高性能的内网穿透软件&#xff0c;支持 tcp, udp, http, https 等协议。frp 项目官网是 https://github.com/fatedier/frp 下载地址&#xff1a; https://github.com/fatedier/frp/releases frp工作原理 服务端运行&#xff0c;监听一个…

坚鹏:中国工商银行数字化时代银行厅堂客户体验设计培训

中国工商银行围绕“数字生态、数字资产、数字技术、数字基建、数字基因”五维布局&#xff0c;深入推进数字化转型&#xff0c;加快形成体系化、生态化实施路径&#xff0c;促进科技与业务加速融合&#xff0c;以“数字工行”建设推动“GBC”&#xff08;政务、企业、个人&…

目标检测算法改进系列之添加SCConv空间和通道重构卷积

SCConv-空间和通道重构卷积 SCConv&#xff08;空间和通道重构卷积&#xff09;的高效卷积模块&#xff0c;以减少卷积神经网络&#xff08;CNN&#xff09;中的空间和通道冗余。SCConv旨在通过优化特征提取过程&#xff0c;减少计算资源消耗并提高网络性能。该模块包括两个单…

【小沐学Python】Python实现Web服务器(Flask+celery,生产者-消费者)

文章目录 1、简介2、安装和下载2.1 flask2.2 celery2.3 redis 3、功能开发3.1 创建异步任务的方法3.1.1 使用默认的参数3.1.2 指定相关参数3.1.3 自定义Task基类 3.2 调用异步任务的方法3.2.1 app.send_task3.2.2 Task.delay3.2.3 Task.apply_async 3.3 获取任务结果和状态 4、…

Spring cloud - gateway

什么是Spring Cloud Gateway 先去看下官网的解释&#xff1a; This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 6, Spring Boot 3 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way t…

javaweb mybatis(手动jar包)

基础&#xff1a;https://blog.csdn.net/qq_67832732/article/details/134764134 条件查询 在映射文件的SQL配置中配置参数 使用parameterType来指定参数类型 使用#{参数名}来接收参数的值 parameterType"string" 表示sql语句需要一个参数&#xff0c;类型为字符…

计算UDP报文CRC校验的总结

概述 因公司项目需求&#xff0c;遇到需要发送带UDP/IP头数据包的功能&#xff0c;经过多次尝试顺利完成&#xff0c;博文记录以备忘。 环境信息 操作系统 ARM64平台的中标麒麟Kylin V10 工具 tcpdump、wireshark、vscode 原理 请查看大佬的博文 UDP伪包头定义&#x…

2023年【G1工业锅炉司炉】考试试题及G1工业锅炉司炉模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【G1工业锅炉司炉】考试试题及G1工业锅炉司炉模拟考试题库&#xff0c;包含G1工业锅炉司炉考试试题答案和解析及G1工业锅炉司炉模拟考试题库练习。安全生产模拟考试一点通结合国家G1工业锅炉司炉考试最新大纲及…