[C++进阶]---AVL树模拟实现

news2024/12/24 2:52:43

目录

  • 1.AVL树的概念
  • 2.AVL树模拟实现
  • 2.1AVL树节点的定义
    • 2.2AVL的插入
    • 2.3AVL树的旋转
      • 2.3.1左单旋
      • 2.3.2右单旋
      • 2.3.3右左双旋
        • 2.3.3.1旋转情况分析
        • 2.3.3.2平衡因子更新分析
      • 2.3.4右左双旋
        • 2.3.4.1旋转情况分析
        • 2.3.4.2平衡因子更新分析
      • 2.3.5AVL树的验证
  • 3.AVL模拟实现源码
  • 4.总结

1.AVL树的概念

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

  • 它的左右子树都是AVL
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

在这里插入图片描述
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n) ,搜索时间复杂度O( l o g 2 n log_2 n log2n)

2.AVL树模拟实现

2.1AVL树节点的定义

struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;	//该节点的左孩子
	AVLTreeNode<K, V>* _right;	//该节点的右孩子
	AVLTreeNode<K, V>* _parent;	//该节点的双亲
	int _bf;//该节点的平衡因子

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

2.2AVL的插入

假设下面这棵树是我们最开始AVL树,接下来我们需要对其进行节点的插入,并针对不同的情况有不同的处理情况
在这里插入图片描述
1.新增节点在parent节点的左边,parent的平衡因子减一(减减)
在这里插入图片描述

2.新增节点在parent节点的右边,parent的平衡因子加一(加加)
3.更新parent平衡因子==0,说明parent所在的子树的高度不变,不会再影响祖先,不用继续沿着到root的路径继续往上更新,插入结束

在这里插入图片描述

4.更新后parent平衡因子==1 or -1,说明parent所在的子树的高度变化,会再影响祖先,需要继续沿着到root的路径继续往上更新
5.更新后parent平衡因子==2 or -2,说明parent所在的子树的高度变化且不平衡,对parent所在子树进行旋转,让子树平衡,插入结束(涉及旋转下面将分情况进行讲解)
在这里插入图片描述
6.更新到根节点,没有出现parent平衡因子==2 or -2的情况更新结束
在这里插入图片描述

2.3AVL树的旋转

旋转的时候涉及的问题:

旋转之后的树需要满足两个条件:1.保持这棵树是二叉搜索树;2.变成平衡树(AVL树),且降低这个子树的高度

2.3.1左单旋

当更新parent(30)节点的平衡因子为2cur(60)节点的平衡因子为1,造成parent(30)所在子树右边高,需要进行左单旋
在这里插入图片描述
以下是h等于0和h等于1的具象图:
在这里插入图片描述
根据上面两种右边子树比较高的情况,需要将cur的左子树链接成为parent的右子树,让parent链接成为cur的左子树。情况一:插入之前,AVL树高度为1;插入节点后,树高度为2,旋转之后,树高度为1且达到平衡,不用继续沿着到root的路径继续往上更新,插入结束;情况二:插入之前,AVL树高度为2;插入节点后,树高度为3;旋转之后,树高度为2且达到平衡,不用继续沿着到root的路径继续往上更新,插入结束。
代码如下:

void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		//curleft成为parent的右边
		parent->_right = curleft;
		//curleft可能为空
		if (curleft)
		{
			//改变curleft父指针的指向
			curleft->_parent = parent;
		}
		//parent成为cur的左边
		cur->_left = parent;
		//记录parent的父节点指针
		Node* ppnode = parent->_parent;
		//改变parent父指针的指向
		parent->_parent = cur;
		//判断parent节点是否为根节点
		if (parent == _root)
		{
			//cur成为根节点
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			//cur链接parent的父节点
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else if (ppnode->_right == parent)
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		parent->_bf = cur->_bf = 0;
	}

2.3.2右单旋

当更新后,parent(30)节点的平衡因子为-2cur(60)节点的平衡因子为-1,造成parent(30)所在子树左边高,需要进行右单旋
在这里插入图片描述
以下是h等于0和h等于1的具象图:
在这里插入图片描述
情况一(h等于0): 插入之前,AVL树高度为1;插入节点后,树高度为2,旋转之后,树高度为1且达到平衡,不用继续沿着到root的路径继续往上更新,插入结束;情况二(h等于1): 插入之前,AVL树高度为2;插入节点后,树高度为3;旋转之后,树高度为2且达到平衡,不用继续沿着到root的路径继续往上更新,插入结束。
分析: 根据上面两种左边子树比较高的情况,需要将cur的右子树链接成为parent的左子树,让parent链接成为cur的左子树。局部根节点parent已经出现向左边倾斜的情况,当插入节点时导致parent所在子树左边高且不平衡,经过旋转后,cur成为新的局部根节点,cur所在子树的高度与未插入节点前的高度一致,不会对上层节点造成影响,达到平衡状态(即cur的平衡因子为0)

代码如下:

void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		//curright链接成为parent的左子树
		parent->_left = curright;
		if (curright)
		{
			//curright不为空情况进行,改变其父指针指向
			curright->_parent = parent;
		}
		//parent链接成为cur的右子树
		cur->_right = parent;
		//记录parent父指针
		Node* ppnode = parent->_parent;
		//让parent父指针指向cur
		parent->_parent = cur;
		//cur与原parent指向父节点链接
		if (parent == _root)
		{
			//parent为根节点的情况
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			//parent为局部子树根节点
			//判断parent原先为局部左子树还是局部右子树
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		//更新parent\cur的平衡因子为0
		parent->_bf = cur->_bf = 0;
	}

2.3.3右左双旋

2.3.3.1旋转情况分析

当插入节点更新平衡因子后,parent(30)节点的平衡因子为2cur(60)节点的平衡因子为-1,造成parent(30)所在子树右边高,但右子树cur节点所在子树却左边高,这时候已经parent所在的子树并不是纯粹的右边高了,所以只进行左单旋不能解决问题,需要进行右左双旋。
在这里插入图片描述
以下是h等于0和h等于1的具象图:
仅仅进行左单旋操作:
在这里插入图片描述

从上面的图可以看出,若parent所在子树不是完全右边高的情况,对其进行左单旋操作不能使AVL树达到平衡

进行右左双旋操作:
在这里插入图片描述

parent(30)所在子树右边高,但右子树cur节点所在子树却左边高,像一个折线;需要先对以cur节点为根节点的子树进行右旋操作,然后对parent节点为根节点的子树进行左旋操作,经过双旋操作后,curleft代替parent节点成为新的(局部)根节点,解决了不平衡问题,使左右子树高度一致,达到平衡状态。

2.3.3.2平衡因子更新分析

在这里插入图片描述
问题: 双旋调整后,parent\cur\curleft三个节点的位置发生了变化,这三个节点的平衡因子是否都无脑改为0呢?答案:不是的!那么该这三个节点的平衡因子应该怎么修改呢?友友们不要着急,且和我一起慢慢分析吧!
h==0(curleft->_bf ==0)的情况:
在这里插入图片描述

分析: ①当curleft的平衡因子为0时,curleft没有节点给cur\parent节点,且cur\parent节点原本也无左右子树,所以cur\parent节点的平衡因子都更新为0

h==1(curleft->_bf ==-1)的情况:
在这里插入图片描述

分析: ②当curleft的平衡因子为-1curleft没有右节点可以链接成为cur节点的左子树,以cur节点为根节点右旋之后,造成cur节点所在子树右边偏高,所以cur节点的平衡因子更新为1curleft有左节点可以链接成为parent的右子树,以parent节点为根节点左旋之后,使parent节点左右子树高度一致达到平衡状态,所以parent的平衡因子也更新为0

h==1(curleft->_bf ==-1)的情况:
在这里插入图片描述

分析: ③当curleft的平衡因子为1curleft有右节点可以链接成为cur节点的左子树,以cur节点为根节点右旋之后,使cur节点左右子树高度一致达到平衡状态,所以cur节点的平衡因子更新为0curleft有左节点链接成为parent的右子树,以parent节点为根节点左旋之后,造成parent节点所在子树左边偏高,所以parent节点的平衡因子更新为-1

代码如下:

void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;//旋转过程中,curleft的bf发生变化,需要提前记录

		//先以cur(parent->_right)为根节点右旋
		//再以parent为根节点左旋
		RotateR(cur);
		RotateL(parent);

		//针对不同curleft->_bf的情况进行处理
		if (bf == 0)
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			curleft->_bf = 0;
			cur->_bf = 1;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋平衡因子更新总结分析:
分析上面三种平衡因子更新的情况,可以看出右左双旋(先右旋,再左旋)结果的本质是:curleft的右边给了cur的左边,curleft的左边给了parent的左边,cur节点和parent分别成为curleft的右左子树。

2.3.4右左双旋

2.3.4.1旋转情况分析

当插入节点更新平衡因子后,parent(90)节点的平衡因子为-2cur(30)节点的平衡因子为1,造成parent(90)所在子树左边高,但其左子树cur节点所在子树却左边高,这时候已经parent所在的子树并不是纯粹的左边高了,所以只进行右单旋不能解决问题,需要进行右左双旋。
在这里插入图片描述
以下是h等于0和h等于1的具象图:
仅仅进行右单旋操作:
在这里插入图片描述

从上面的图可以看出,若parent所在子树不是完全左边高的情况,对其进行右单旋操作不能使AVL树达到平衡

进行左右双旋操作:
在这里插入图片描述

parent(90)所在子树左边高,但右子树cur节点所在子树却右边高,像一个折线;需要先对以cur节点为根节点的子树进行左旋操作,然后对parent节点为根节点的子树进行右旋操作,经过双旋操作后,curleft代替parent节点成为新的(局部)根节点,解决了不平衡问题,使左右子树高度一致,达到平衡状态。

2.3.4.2平衡因子更新分析

h==0(curright->_bf ==0)的情况:
在这里插入图片描述

分析: ①当curright的平衡因子为0时,curright没有节点给cur\parent节点,且cur\parent节点原本也无左右子树,所以cur\parent节点的平衡因子都更新为0

h==-1(curright->_bf ==-1)的情况:
在这里插入图片描述

分析: ②当curright的平衡因子为-1curright有左节点链接cur节点的右子树,以cur节点为根节点左旋之后,使cur节点左右子树高度一致达到平衡状态,所以cur的平衡因子也更新为0curright没有右节点链接成为parent的左子树,以parent节点为根节点右旋之后,造成parent节点所在子树右边偏高,所以parent节点的平衡因子更新为1

h==1(curright->_bf ==1)的情况:
在这里插入图片描述

分析: ③当curright的平衡因子为1curright没有左节点可以链接cur节点的右子树,以cur节点为根节点左旋之后,造成parent节点所在子树左边偏高,所以cur的平衡因子也更新为-1curright有右节点可以链接成为parent的左子树,以parent节点为根节点右旋之后,使parent节点左右子树高度一致达到平衡状态,所以parent节点的平衡因子更新为0

代码如下:

void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		//先以cur(parent->_left)为根节点左旋
		//再以parent为根节点右旋
		RotateL(parent->_left);
		RotateR(parent);
		
		//针对不同curright->_bf的情况进行处理
		if (bf == 0)
		{
			curright->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			curright->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			curright->_bf = 0;
			cur->_bf = -1;
			parent->_bf = 0;
		}
	}

左右双旋平衡因子更新总结分析:
分析上面三种平衡因子更新的情况,可以看出左右双旋(先左旋,再右旋)结果的本质是:curright的左边给了cur的右边,curleft的右边给了parent的左边,cur节点和parent分别成为curleft的左右子树。

注意: ① 双旋是先左(右)旋,然后再右(左)旋,单旋转的原理是一样的,所以可以直接对前面左(右)旋函数调用,然后对右(左)旋函数调用,可增加代码复用性;②双旋平衡因子单独分析进行更新,与单旋转函数可以起到解耦合的作用。

2.3.5AVL树的验证

我们已经实现了AVL树插入操作的功能,为了保证所写的代码正确,我们可以插入数据进行检验。

使用测试AVL树平衡的函数+进行数据的插入检验

	int Height()
	{
		return Height(_root);
	}
	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 IsBalance()
	{
		return IsBalance(_root);
	}
	bool IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight=Height(root->_left);
		int rightHeight = Height(root->_right);
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << "Exception:" << root->_kv.first << "->" << root->_bf  << endl;
			assert(false);
			return false;
		}

		return abs(leftHeight - rightHeight) < 2 
			&& IsBalance(root->_left) 
			&& IsBalance(root->_right);
	}

测试1:

void test()
{
	//普通场景
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.insert(make_pair(e, e));
		cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	cout << "所有数据插入成功" << endl;
}

代码运行结果为:
在这里插入图片描述
测试2:

void test2()
{
	//插入随机数
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand()+i);
	}
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.insert(make_pair(e, e));
	}
	cout << "所有数据插入成功" << endl;
}

代码运行结果如下:
在这里插入图片描述
测试3:

void test2()
{
	//插入随机数
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand()+i);
	}
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.insert(make_pair(e, e));
	}
	cout << "Insert:" << t.IsBalance() << endl;
	cout << "所有数据插入成功" << endl;
}

代码运行结果如下:
在这里插入图片描述

3.AVL模拟实现源码

#include<iostream>
#include<assert.h>
using namespace std;
template<class K,class V>
struct AVLTreeNode
{
	//建议声明顺序与初始化顺序一样
	AVLTreeNode<K, V>* _left;	//该节点的左孩子
	AVLTreeNode<K, V>* _right;	//该节点的右孩子
	AVLTreeNode<K, V>* _parent;	//该节点的双亲
	pair<K, V> _kv;
	int _bf;//该节点的平衡因子

	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};
template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			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 (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			//parent所在子树高度不变,会影响祖先,不需要更新平衡因子,插入结束
			if (parent->_bf == 0)
			{
				break;
			}//parent所在子树高度变化,会影响祖先,继续沿着到root的路径更新平衡因子
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}//parent所在子树高度变化且不平衡,需要旋转parent所在子树
			else if (parent->_bf == 2 || parent->_bf == -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)
				{
					RotateRL(parent);//右左双旋
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);//左右双旋
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
	}
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		//curleft成为parent的右边
		parent->_right = curleft;
		//curleft可能为空
		if (curleft)
		{
			//改变curleft父指针的指向
			curleft->_parent = parent;
		}

		//parent成为cur的左边
		cur->_left = parent;
		//记录parent的父节点指针
		Node* ppnode = parent->_parent;
		//改变parent父指针的指向
		parent->_parent = cur;
		//判断parent节点是否为根节点
		if (parent == _root)
		{
			//cur成为根节点
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			//cur链接parent的父节点
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else if (ppnode->_right == parent)
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		parent->_bf = cur->_bf = 0;
	}
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		//curright链接成为parent的左子树
		parent->_left = curright;
		if (curright)
		{
			//curright不为空情况进行,改变其父指针指向
			curright->_parent = parent;
		}
		//parent链接成为cur的右子树
		cur->_right = parent;
		//记录parent父指针
		Node* ppnode = parent->_parent;
		//让parent父指针指向cur
		parent->_parent = cur;
		//cur与原parent指向父节点链接
		if (parent == _root)
		{
			//parent为根节点的情况
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			//parent为局部子树根节点
			//判断parent原先为局部左子树还是局部右子树
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		//更新parent\cur的平衡因子为0
		parent->_bf = cur->_bf = 0;
	}
	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;//旋转过程中,curleft的bf发生变化,需要提前记录

		//先以cur(parent->_right)为根节点右旋
		//再以parent为根节点左旋
		RotateR(cur);
		RotateL(parent);

		if (bf == 0)
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			curleft->_bf = 0;
			cur->_bf = 1;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		//先以cur(parent->_left)为根节点左旋
		//再以parent为根节点右旋
		RotateL(parent->_left);
		RotateR(parent);

		if (bf == 0)
		{
			curright->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			curright->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			curright->_bf = 0;
			cur->_bf = -1;
			parent->_bf = 0;
		}
	}
	int Height()
	{
		return Height(_root);
	}
	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 IsBalance()
	{
		return IsBalance(_root);
	}
	bool IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight=Height(root->_left);
		int rightHeight = Height(root->_right);
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << "Exception:" << root->_kv.first << "->" << root->_bf  << endl;
			assert(false);
			return false;
		}

		return abs(leftHeight - rightHeight) < 2 
			&& IsBalance(root->_left) 
			&& IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};

4.总结

①AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

② AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

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

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

相关文章

yarn或者pnpm第一次执行的时候遇到报错yarn : 无法加载文件......因为在此系统上禁止运行脚本

报错&#xff1a; yarn : 无法加载文件 C:\Users\rina2\AppData\Roaming\npm\yarn.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/http://go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 解决方案&#xff1a…

基于龙蜥(AnolisOS7.9)编译ZLMediaKit

系统安装 镜像下载 下载地址&#xff1a;https://openanolis.cn/download VirtualBox 下载地址&#xff1a;https://download.virtualbox.org/virtualbox/7.0.12/VirtualBox-7.0.12-159484-Win.exe ZLMediaKit编译 编译器和Cmake sudo yum -y install gcc sudo yum -y …

移动端机器学习框架 MDL 简介与实践

Mobile-deep-learning&#xff08;MDL&#xff09; MDL 是百度研发的可以部署在移动端的基于卷积神经网络实现的移动端框架&#xff0c;可以应用在图像识别领域。 具体应用&#xff1a;在手机百度 App 中&#xff0c;用户只需要点击自动拍开关&#xff0c;将手机对准物体&…

二、SpringFramework 介绍

2.1 Spring 和 SpringFramework概念 https://spring.io/projects 广义的 Spring&#xff1a;Spring 技术栈&#xff08;全家桶&#xff09; 广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。 经过十多年的发展&#xff0c;Spring 已经不再是一个单纯的应…

Linux实操——Tomcat安装

Tomcat安装 一、下载Tomcat包&#xff0c;并通过ftp上传到服务器二、解压安装包三、启动Tomcat四、验证是否安装完成 总结 博主最近项目上线了&#xff0c;终于能愉快的摸鱼了&#xff0c;额。。。那是不存在的&#xff0c;作为最勤奋的码农一份子&#xff0c;也是试着搭建一个…

隔离电源与非隔离式电源

开关电源 文章目录 开关电源前言一、它们之间的区别是什么&#xff1f;二、如何区分它们呢&#xff1f;三、隔离电源与非隔离电源的优缺点四、隔离电源与非隔离电源的选择总结 前言 在产品设计时&#xff0c;倘若没有考虑应用环境对电源隔离的要求&#xff0c;产品到了应用时就…

石油化工园区:安全管理工作中的挑战与措施

石油化工园区&#xff1a;安全管理工作中的挑战与措施 石油化工园区是一种设备设施多且结构复杂的工业园区&#xff0c;涉及到易燃易爆或有毒介质&#xff0c;同时园区内有大量的工作人员和工作车辆等动态参与要素。由于园区的特殊性质&#xff0c;安全管理工作显然具备较高的挑…

外网访问内网服务器使用教程

如何在任何地方都能访问自己家里的笔记本上的应用&#xff1f;如何让局域网的服务器可以被任何地方访问到&#xff1f;有很多类似的需求&#xff0c;我们可以统一用一个解决方案&#xff1a;内网穿透。内网穿透的工具及方式有很多&#xff0c;如Ngrok、Ssh、autossh、Natapp、F…

小学生台灯买什么样的合适?精选专业的学生台灯

随着时代的发展和进步&#xff0c;孩子的教育也提升了很多。但很多家长可能并不知道&#xff0c;其实现在的学生不管处小学还是初高中&#xff0c;他们的学习压力都是比我们以前大很多的。繁重的功课让他们经常用眼过度&#xff0c;这也是如今这么多孩子过早近视的主要原因。所…

妙手ERP特色功能来袭:上线Lazada包邮营销功能,全方位助力卖家高效引流!

包邮是线上消费者作出购买决策的重要因素&#xff0c;据Lazada平台调研显示&#xff1a;73%的受访者希望商品免费配送&#xff0c;有84%的消费者使用过Lazada包邮优惠券&#xff0c;其中75%的消费者对此感到满意。由此可见&#xff0c;包邮已成为打动东南亚消费者下单的主要原因…

iOS应用程序签名、重签名及安装测试优化指南

iOS应用程序的签名、重签名和安装测试 ipa编译出来后&#xff0c;或者ipa进行修改后&#xff0c;需要进行重新签名才能安装到测试手机&#xff0c;或者提交app store供apple 商店审核上架。ipaguard有签名和重签名功能&#xff0c;能在windows&#xff0c;mac&#xff0c;和li…

深度学习在人体动作识别领域的应用:开源工具、数据集资源及趋动云GPU算力不可或缺

人体动作识别检测是一种通过使用计算机视觉和深度学习技术&#xff0c;对人体姿态和动作进行实时监测和分析的技术。该技术旨在从图像或视频中提取有关人体姿态、动作和行为的信息&#xff0c;以便更深入地识别和理解人的活动。 人体动作识别检测的基本步骤包括&#xff1a; 数…

第六届“泰迪杯”数据分析技能赛颁奖会圆满落幕

12月8日至10日&#xff0c;“广东省&#xff08;广州&#xff09;工业与应用数学学会 2023年年会暨数学建模颁奖大会在深圳职业技术大学留仙洞校区隆重举行。本次会议由广东省&#xff08;广州&#xff09;工业与应用数学学会主办&#xff0c;深圳职业技术大学工业训练中心承办…

快速整合EasyExcel实现Excel的上传下载

1.EasyExcel 2.Excel的上传&#xff08;读Excel&#xff09; 3.Excel的下载&#xff08;写Excel&#xff09; 4.结语 1.EasyExcel 首先&#xff0c;这里给出EasyExcel的官方文档&#xff1a;https://easyexcel.opensource.alibaba.com/ alibaba.com不用我多说了吧&#xff0c;大…

C语言实现直接插入排序

完整代码&#xff1a; #include<stdio.h>//直接插入排序&#xff0c;从小到大 //参数&#xff1a;arr[]表示待排序数组&#xff0c;len表示该数组长度 void insert_sort(int arr[],int len){//arr[i]表示待插入的那个数//tmp保存待插入那个数的值//arr[j]用来移动数据in…

Mongdb常用复杂语句(nosql)总结

➡️ ➡️ 关于 MongoDB和MongoTemplate 嵌套数据判空查询 的讨论 ⬅️ ⬅️ 在本篇文章中小名会时常维护些来不及分类的日工作常用的复杂语句&#xff1a; 1、按照表id查询 db.getCollection(TABLE_NAME).find({"_id":ObjectId("62947c8fe2a399286a7259f7&q…

【STM32】DMA直接存储器存取

1 DMA简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 可以直接访问STM32的存储器的&#xff0c;包括运行SRAM、程序存储器Flash和寄存器等等 DMA可以提供外设寄存器和存储器或者存储器和存储器之间的高速数据传输&#xff0c;无须CPU干预&#xff0c;节…

JAVA 版多商家入驻 直播带货 商城系统 B2B2C 之 鸿鹄云商B2B2C产品概述

着互联网的快速发展&#xff0c;越来越多的企业开始注重数字化转型&#xff0c;以提升自身的竞争力和运营效率。在这个背景下&#xff0c;鸿鹄云商SAAS云产品应运而生&#xff0c;为企业提供了一种简单、高效、安全的数字化解决方案。 鸿鹄云商SAAS云产品是一种基于云计算的软件…

Python 小程序之PDF文档加解密

PDF文档的加密和解密 文章目录 PDF文档的加密和解密前言一、总体构思二、使用到的库三、PDF文档的加密1.用户输入模块2.打开并读取文档数据3.遍历保存数据到新文档4.新文档进行加密5.新文档命名生成路径6.保存新加密的文档 四、PDF文档的解密1.用户输入模块2.前提准备2.文件解密…

IDEA利用插件完成properties与yml的互相转换(mac与wins通用)

步骤一、插件安装 点击屏幕左上方的IDEA&#xff0c;然后点击Preferences(相当于wins里的settings) 进入后点击Plugins&#xff0c;在插件商城中搜索并安装 Convert YAML and Properties File 这个插件 二、使用 右键选择你需要转换的配置文件&#xff0c;选择Convert YAML …