AVL树(C++实现)

news2024/11/16 5:58:25

文章目录

  • AVL树的概念
  • AVL树结点定义
  • AVL树的插入
  • AVL树的旋转
    • 左单旋
    • 右单旋
    • 左右单旋
    • 右左双旋
  • AVL树的验证
  • AVL树的性能
  • AVL树及测试完整代码

AVL树的概念

二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序,那么二叉搜索树将退化为单支树,查找元素则相当于在顺序表中搜索元素,是将复杂度为O(n),效率低下.
如果当向二叉搜索树中插入新的结点后,如果能保证每个结点的左右子树高度只差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,减少平均搜索长度.

AVL树的性质:
1: 它的左右子树AVL树.
2: 任意一个子树左右高度差都不超过1(-1 / 0 / 1 )
在这里插入图片描述
如果一棵二叉搜索树是高度平衡的如果它有N个结点,其高度可保持在O(log2N),搜索时间复杂度也为O(log2N).

AVL树结点定义

我们实现的是KV模型的AVL树,为了方便后续的操作,这里AVL树结点定义为三叉链结构,并且引入平衡因子,方便判断每棵树的平衡情况,并且还需要一个初始化列表将结点成员全部初始化.

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)
	{}
};

AVL树的插入

AVL树的插入总共有三个步骤:
1: 找到要插入结点的位置.

2:插入成功后,控制平衡,更新平衡因子.

3:当插入时parent所在子树已经不平衡了,则需要旋转.

1:找到需要插入结点的位置
我们可以利用搜索二叉树基本思想进行查找插入位置:
1: 如果插入的结点值大于当前结点值,向右遍历.

2: 如果插入的结点值小于当前结点值,向左遍历.

3:如果插入的结点值等于当前结点值,则不需要插入.

2:插入成功后,更新平衡因子:
(a): 插入成功,将当前结点的parent指向插入结点,将插入结点的_parent指向parent,建立插入结点与parent结点的联系.
(b): 向上循环更新平衡因子
更新平衡因子时,我们将cur代表新插入的结点,parent代表插入结点的父亲结点,一直向上循环更新平衡因子知道parent为空.
1:如果新增在右,_bf++,如果新增在左,_bf–.

2: 如果更新后,abs( parent->_bf ) ==1,说明parent插入之前的平衡因子是0,即插入前左右子树高度相等,插入后,parent所在树的高度发生改变,即左右子树一边高一边低,进而导致parent的平衡因子改变,需要继续向上更新(因为会对parent的祖先平衡因子造成影响)

3:如果更新后,abs(parent->_bf ) == 0, 说明parent插入之前的平衡因子是1or-1,插入之后,parent所在树的高度不变,即对祖先无影响,此时不需要往上更新平衡因子.

4: 如果更新后,abs(parent->_bf) == 2,说明parent插入之前的平衡因子是1or-1,已经达到平衡临界值,插入新结点变成2or-2.此时,已经打破平衡,parent所在子树需要旋转处理.

5: 如果更新后,parent->_bf > 2或者parent->_bf < -2,此时说明在插入之前,这个AVL树已经不平衡了,此时需要断言处理,检查之前的操作问题.

3:当插入时parent所在子树已经不平衡了,则需要旋转

当更新完平衡因子,此时parent已经不平衡了,此时分为四种情况

1: 当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋.

2: 当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋.

3:当parent的平衡因子为-2,cur的平衡因子为1时,进行左右单旋.

4: 当parent的平衡因子为2,cur的平衡因子为-1时,进行右左单旋.

插入代码如下:

template <class K,class V>
struct 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->first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
		    }
			else if (cur->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;//插入时,cur的parent也要维护.


		//控制平衡,如何更新平衡因子.
		while ( parent  )
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
		    }
			else
			{
				parent->_bf--;
			}

			if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 0 ) //对祖先的平衡因子没有影响,所以不需要再更新平衡因子.
			{
				break;                 
			}
			else if( abs( parent->_bf) == 2 ) //parent所在的子树已经不平衡了,需要旋转处理
			{
				if (parent->_bf == 2 && cur->_bf == 1)//parent的平衡因子等于2,且cur的平衡因子等于1的时候,需要左单旋.
				{
					RotateL(parent);      //左单旋
				}
				else if (parent->_bf == -2 && cur->_bf == -1) //parent的平衡因子等于-2,且cur的平衡因子等于-1的时候,需要右单旋.
				{
					RotateR(parent);     //右单旋
				}
				else if ( parent->_bf == -2 && cur->_bf == 1  )//符合parent的平衡因子等于-2,且cur的平衡因子等于1,采用左右双旋.
				{
					RotateLR(parent);
				}
				break;                 //例如,原来parent所在树的高度为h+2,插入新节点之后变成
				                       //h+3,可是右单旋之后又变成了h+2,此时parent高度并没有发生变化,
				                       //不必再往上更新平衡
			}
			else                             //走到这里,插入结点发现插入平衡因子的绝对值是大于2的.
			{
				assert(false);
			}	
		}

		return true;                       //插入,更新完平衡因子,即可插入成功.
	}

注意:当旋转完毕,在旋转时已经更新了平衡因子,此时parent的高度相较之前并没有发生改变,也就是说parent所在子树并不会影响祖先的平衡因子,此时可以退出,不必往上更新平衡因子了.

AVL树的旋转

左单旋

将新节点插入parent右子树较高的右侧:
左单旋的操作步骤:
1: 让parent作为subR的左子树.

2: 让subR的左子树作为parent的右子树.

3: 判断根,并相应作出调整
(a) : 如果parent是整棵树的根
如果parent是整棵树的根,旋转后subR就是这棵树的根,让_root指向subR.
(b): 如果parent是子树的根
如果parent是子树的根,此时要记录旋转前parent的parent为ppNode,将subR与
ppNode建立联系,此时又有两种情况:
(1): 如果旋转前,parent为ppNode的左子树,那么旋转后,subR就为ppNode的左子树.
(2): 如果旋转前,parent为ppNode的右子树,那么旋转后,subR就为ppNode的右子树.
4: 更新平衡因子
左单旋中,只有parent与subR的高度发生变化需要更新平衡因子,且二者的平衡因子单旋后都为0.
在这里插入图片描述
左单旋代码如下:

void RotateL(Node* parent)              //左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_right = subRL;

		if (subRL)                 //subRL的h有可能为0.
		{
			subRL->_parent = parent;
		}
		parent->_parent = subR;
		
		//判断parent是整棵树的根,还是其他树的子树.
		if (_root == parent)  //如果parent是整棵树的根
		{
			_root = subR;
			subR->_parent = nullptr;,
		}
		else
		{
			if (ppNode->_left == parent)  //如果parent在ppNode的左边
			{
				ppNode->_left = subR;
			}
			else                         //如果parent在ppNode的右边
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;  //subR的parent指向ppNode.
		}

		subR->_bf = parent->_bf = 0;              //旋转完毕答,即可更新平衡因子.
	}

注意:
当h==0时,即subRL为空时,此时不能访问subRL的父亲,所以针对它要额外进行判断.(包括右单旋)

右单旋

将新节点插入parent当前左子树较高的左侧:
右单旋的操作步骤:
1: 让parent作为subL的右子树.

2: 让subL的右子树作为parent的左子树.

3: 判断根,并相应作出调整
(a) : 如果parent是整棵树的根
如果parent是整棵树的根,旋转后subR就是这棵树的根,让_root指向subL.
(b): 如果parent是子树的根
如果parent是子树的根,此时要记录旋转前parent的parent为ppNode,将subL与
ppNode建立联系,此时又有两种情况:
(1): 如果旋转前,parent为ppNode的左子树,那么旋转后,subL就为ppNode的左子树.
(2): 如果旋转前,parent为ppNode的右子树,那么旋转后,subL就为ppNode的右子树.

4: 更新平衡因子
右单旋中,只有parent与subR的高度发生变化需要更新平衡因子,且二者的平衡因子单旋后都为0.
在这里插入图片描述
右单旋的代码如下:

void RotateR(Node* parent)    //右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_left = subLR;

		if (subLR)                 //subRL的h有可能为0.
		{
			subLR->_parent = parent;
		}
		parent->_parent = subL;

		//判断parent是整棵树的根,还是其他树的子树.
		if (_root == parent)  //如果parent是整棵树的根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)  //如果parent在ppNode的左边
			{
				ppNode->_left = subL;
			}
			else                         //如果parent在ppNode的右边
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}

		subL->_bf = parent->_bf = 0;
	}

左右单旋

左右单旋主要有四个步骤:
1:在b处插入新结点.(假设在b处插入)
在这里插入图片描述
2: 以subL为旋转点左单旋.
在这里插入图片描述

3: 以subLR为旋转点右单旋.
在这里插入图片描述
4:更新平衡因子:
左右双旋中更新平衡因子根据插入新结点的位置有三种情况:

(1) 当在b处插入新结点时,即subRL->_bf 等于 1,左右双旋后,parent,subL,subLR的平衡因子分别为:0,-1,0.
在这里插入图片描述

(2)当在b处插入新结点时,即subLR等于-1,左右双旋后,parent,subL,subLR的平衡因子分别为:1,0,0
在这里插入图片描述

(3)当h==0,及subLR就为新插入的结时,左右双旋后,parent,subL,subLR的平衡因子都为0.
在这里插入图片描述
注意:
由以上更新平衡因子的情况可以看出,在插入新结点后,经过旋转后,parent的高度还是插入之前的高度,及不会影响其祖先的平衡因子,所以左右当选之后即可从更新平衡因子循环中退出.
左右双旋代码如下:

void RotateLR(Node* parent)       //左右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;          //记录subLR的平衡因子,根据该平衡因子的情况分三种
		                             //方式进行更新平衡因子.

		RotateL(parent->_left);       //左单旋
		RotateR(parent);             //右单旋

		subLR->_bf = 0;              //结点60的平衡因子一定为0.
	    if ( bf == 1  )             //在c处插入
		{
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if ( bf == -1 )      //在b处插入
		{
			parent->_bf = 1
			subL->_bf = 0;
		}
		else if( bf == 0 )       //b处自己便是新增.
		{
			parent->_bf = subL->_bf = 0;
		}
		else                   //如果都不是,那就是有错误的地方.
		{
			assert(false);
		}
	}

右左双旋

右左单旋主要分为四个步骤:
1:插入新节点(假设在b处插入)
在这里插入图片描述

2: 以subR结点右单旋.
在这里插入图片描述

3: 以subRL结点左单旋.
在这里插入图片描述

4: 更新平衡因子
右左双旋中更新平衡因子根据插入新结点的位置有三种情况:
(1) :如果在c处插入,即subRL->_bf等于1,parent,subR,subRL的平衡因子分别为:
在这里插入图片描述

(2): 如果在b处插入,即subRL->_bf等于-1,parent,subR,subRL的平衡因子分别为:
在这里插入图片描述

(3):当h==0,即subRL结点就为新增结点,此时parent,subR,subRL的平衡因子都为0.
在这里插入图片描述

右左双旋代码如下:

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

		subRL->_bf = 0;
		if( bf == 1 )
		{
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if ( bf == -1 )
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
private:
	Node* _root = nullptr;
};

AVL树的验证

因为AVL树也是二叉搜索树,我们可以采用中序遍历的方式遍历AVL树:

void InOrder()
	{
		_InOrder(_root);
	}
void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

但是中序遍历只能证明AVL树是二叉搜索树,并不能证明它具备平衡的性质,所以我们可以根据该树的平衡因子来判断这棵树是否平衡,而求平衡因子首先就要求这棵树(或者子树的高度).

获取二叉树的高度采用的是后序遍历的思想,判断AVL树的平衡必须要求得左右子树的高度差(即平衡因子),如果平衡因子的绝对值 < 2,说明概树处于平衡状态.

注意:
判断AVL树的平衡是要判断它本身以及左右子树的平衡.

int Height(Node* root)                 //求高度
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		return max(leftHT, rightHT) + 1;      //获取完这棵子树的高度,然后返回到上一层递归的地方.
	}
	bool IsBanlance()           
	{
		return _IsBanlance(_root);
	}
	bool _IsBanlance(Node* root)           //判断AVL树是否平衡.
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		int diff = rightHT - leftHT;
		return abs(diff) < 2 && _IsBanlance(root->_left) && _IsBanlance(root->_right);//要判断整棵树以及这棵树的左右子树是否平衡.
	}

AVL树的性能

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

AVL树及测试完整代码

#include <map>
#include <iostream>
#include <assert.h>
#include <algorithm>
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)
		,_bf(0)
		,_kv(kv) //走pair的构造函数
	{
	}
};

template <class K,class V>
struct 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->_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;//插入时,cur的parent也要维护.


		//控制平衡,如何更新平衡因子.
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 0) //对祖先的平衡因子没有影响,所以不需要再更新平衡因子.
			{
				break;
			}
			else if (abs(parent->_bf) == 2) //parent所在的子树已经不平衡了,需要旋转处理
			{
				if (parent->_bf == 2 && cur->_bf == 1)//parent的平衡因子等于2,且cur的平衡因子等于1的时候,需要左单旋.
				{
					RotateL(parent);      //左单旋
				}
				else if (parent->_bf == -2 && cur->_bf == -1) //parent的平衡因子等于-2,且cur的平衡因子等于-1的时候,需要右单旋.
				{
					RotateR(parent);     //右单旋
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//符合parent的平衡因子等于-2,且cur的平衡因子等于1,采用左右双旋.
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;               //例如,原来parent所在树的高度为h+2,插入新节点之后变成
									   //h+3,可是右单旋之后又变成了h+2,此时parent高度并没有发生变化,
									   //不必再往上更新平衡
			}
			else                             //走到这里,插入结点发现插入平衡因子的绝对值是大于2的.
			{
				assert(false);
			}
		}

		return true;                       //插入,更新完平衡因子,即可插入成功.
	}
	void InOrder()
	{
		_InOrder(_root);
	}
	bool IsBanlance()
	{
		return _IsBanlance(_root);
	}
private:
	bool _IsBanlance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		int diff = rightHT - leftHT;
		return abs(diff) < 2 && _IsBanlance(root->_left) && _IsBanlance(root->_right); //整棵树平衡且左子树和右子树都是平衡的.
	}
	//求高度
	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHT = Height(root->_left);
		int rightHT = Height(root->_right);
		return max(leftHT, rightHT) + 1;
	}
	//遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	void RotateL(Node* parent)              //左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_right = subRL;

		if (subRL)                 //subRL的h有可能为0.
		{
			subRL->_parent = parent;
		}
		parent->_parent = subR;

		//判断parent是整棵树的根,还是其他树的子树.
		if (_root == parent)  //如果parent是整棵树的根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)  //如果parent在ppNode的左边
			{
				ppNode->_left = subR;
			}
			else                         //如果parent在ppNode的右边
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}

		subR->_bf = parent->_bf = 0;              //旋转完毕答,即可更新平衡因子.
	}
	void RotateR(Node* parent)    //右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_left = subLR;

		if (subLR)                 //subRL的h有可能为0.
		{
			subLR->_parent = parent;
		}
		parent->_parent = subL;

		//判断parent是整棵树的根,还是其他树的子树.
		if (_root == parent)  //如果parent是整棵树的根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)  //如果parent在ppNode的左边
			{
				ppNode->_left = subL;
			}
			else                         //如果parent在ppNode的右边
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}

		subL->_bf = parent->_bf = 0;
	}
	void RotateLR(Node* parent)       //左右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;          //记录subLR的平衡因子,根据该平衡因子的情况分三种
									 //方式进行更新平衡因子.

		RotateL(parent->_left);       //左单旋
		RotateR(parent);             //右单旋

		subLR->_bf = 0;              //结点60的平衡因子一定为0.
		if (bf == 1)             //在c处插入
		{
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)      //在b处插入
		{
			parent->_bf = 1;
			subL->_bf = 0;
		}
		else if (bf == 0)       //b处自己便是新增.
		{
			parent->_bf = subL->_bf = 0;
		}
		else                   //如果都不是,那就是有错误的地方.
		{
			assert(false);
		}
	}
	void  RotateRL(Node* parent)
	{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			int bf = subRL->_bf;

			RotateR(parent->_right);
			RotateL(parent);

			subRL->_bf = 0;
			if (bf == 1)
			{
				subR->_bf = 0;
				parent->_bf = -1;
			}
			else if (bf == -1)
			{
				subR->_bf = 1;
				parent->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				subR->_bf = 0;
			}
			else
			{
				assert(false);
			}
	}
	private:
		Node* _root = nullptr;
};


//测试代码1:
void TestAVLTree1()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}
	t1.InOrder();
}
//测试代码2:
//随机样式
//查AVL树的高度
void TestAVLTree2()
{
	int a[] = {4,2,6,1,3,5,15,7,16,14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}
	t1.InOrder();
	cout << "IsBanlance:" << t1.IsBanlance();
}

//测试3
//随机数测试,使用10000个随机数测试
void TestAVLTree3()
{
	size_t N = 10000;
	srand(time(0));
	AVLTree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();               //x接收产生的随机数.
		t1.Insert(make_pair(x, i));   //在树中插入随机数.
	}
	cout << "IsBanlance:" << t1.IsBanlance() << endl;
}

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

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

相关文章

从零手写Resnet50实战——利用 torch 识别出了虎猫和萨摩耶

大家好啊&#xff0c;我是董董灿。 自从前几天手写了一个慢速卷积之后&#xff08;从零手写Resnet50实战—手写龟速卷积&#xff09;&#xff0c;我便一口气将 Resnet50 中剩下的算法都写完了。 然后&#xff0c;暴力的&#xff0c;按照 Resnet50 的结构&#xff0c;将手写的…

【Flowable】Flowable基础表结构

1.表结构讲解 表结构创建文件&#xff1a;flowable-engine-6.3.0.jar!\org\flowable\db\create\flowable.mysql.create.engine.sql 工作流程的相关操作都是操作存储在对应的表结构中&#xff0c;为了能更好的弄清楚Flowable的实现原理和细节&#xff0c;我们有必要先弄清楚Fl…

Python边缘检测之prewitt, sobel, laplace算子

文章目录 滤波算子简介具体实现测试 滤波算子简介 ndimage中提供了卷积算法&#xff0c;并且建立在卷积之上&#xff0c;提供了三种边缘检测的滤波方案&#xff1a;prewitt, sobel以及laplace。 在convolve中列举了一个用于边缘检测的滤波算子&#xff0c;统一维度后&#xf…

es6 const的使用

1.const用来定义常量&#xff0c;赋值知乎不能再赋值&#xff0c;再次赋值会报错。 <script>//1.定义常量&#xff0c;赋值后不能再赋值&#xff0c;在赋值报错const count 1// count 2</script> ​ 2.const不能只声明不赋值&#xff0c;会报错。 <script>…

智能学习 | MATLAB实现CS-BP多变量时间序列预测(布谷鸟搜索算法优化BP神经网络)

智能学习 | MATLAB实现CS-BP多变量时间序列预测(布谷鸟搜索算法优化BP神经网络) 目录 智能学习 | MATLAB实现CS-BP多变量时间序列预测(布谷鸟搜索算法优化BP神经网络)预测效果基本介绍程序设计参考资料预测效果 基本介绍 MATLAB实现CS-BP多变量时间序列预测(布谷鸟搜索算法…

chatGPT衣食住行10种场景系列教程(01)chatGPT热点事件+开发利器

导读 时隔5个多月&#xff0c;chatGPT可谓是一日千里&#xff0c;越演越火&#xff0c;携带着AIGC行业一起飞了起来&#xff0c;那么在短短5个月当中有那些值得我们关注的事件&#xff1f;有那些好玩的场景&#xff1f;以及有那些chatGPT好用的工具&#xff1f;本文都将一一告…

大数据时代必备技能,学好数据可视化

互联网时代&#xff0c;都在强调数据分析的重要性&#xff0c;但是干巴巴的数据没人爱看&#xff0c;老板们对“简单直观地看数据”的需求愈发强烈。随着大数据建设的如火如荼&#xff0c;别讲底层技术和算法牛逼&#xff0c;最终的效率提升、业绩提升要通过数据展示出来&#…

vba:消息框基础,msgbox

常量常量值说明vbOKOnly0只显示“确定”按钮&#xff08;缺省值&#xff09;VbOKCancel1显示“确定”和“取消”按钮VbAbortRetryIgnore2显示“终止”、“重试”和“忽略” 按钮VbYesNoCancel3显示“是”、“否”和“取消”按钮VbYesNo4显示“是”和“否”按钮VbRetryCancel5显…

Python爬虫实战——获取电影影评

Python爬虫实战——获取电影影评 前言第三方库的安装示例代码效果演示结尾 前言 使用Python爬取指定电影的影评&#xff0c; 注意&#xff1a;本文仅用于学习交流&#xff0c;禁止用于盈利或侵权行为。 操作系统&#xff1a;windows10 家庭版 开发环境&#xff1a;Pycharm Co…

Linux 服务简单优化

硬件优化 处理器&#xff1a;核心数、主频、制程工艺、线程数、缓存等 核心数&#xff1a;1、2、4、6、8、12、24、32等 主频&#xff1a;2.0GHz、2.3GHz等等 制程工艺&#xff1a;22nm、14nm、10nm等等 线程数&#xff1a;1、2 缓存&#xff1a;L1、L2、L3 建议&#xff1a;尽…

OpenHarmony 3.2 Release特性更新简析

1.ArkUI 组件能力增强 支持XComponent控件&#xff0c;可用于EGL/OpenGL ES和媒体数据写入&#xff0c;并在XComponent组件显示&#xff1b;通过XComponent组件&#xff0c;配合NDK能力&#xff0c;构建C/ArkTS混合开发能力&#xff0c;支持游戏、媒体应用开发支持AbilityCom…

前端学习:HTML内联框架

目录 一、HTML Iframe 二、添加iframe的语法 三、Iframe设置高度和宽度 ​编辑 四、Iframe删除边框 五、使用iframe作为链接的目标 六、补充 一、HTML Iframe iframe用于在网页内显示网页。 二、添加iframe的语法 <iframe src"URL"></iframe> 提示…

“计数”排序

目录 一、什么是计数排序&#xff1f;二、如何实现计数排序&#xff1f;三、适用场景四、时间复杂度和空间复杂度 一、什么是计数排序&#xff1f; 计数排序&#xff0c;是通过统计每一个数字出现的次数&#xff0c;并把它映射到与它自己本身数值相同的下标处&#xff0c;再遍…

HoloLens2场景理解,识别平面信息

因为可用的资料比较少,就记录下吧,大家也可以少走弯路,节省时间。 场景理解,通俗的讲,可以识别空间当中的墙面、地板、天花板、平台等. 场景理解&#xff08;Scene Understanding&#xff09;是指 HoloLens2 通过深度传感器、摄像头和计算机视觉算法等技术&#xff0c;能够对…

Centos安装Nvidia驱动解决内核版本不匹配问题

Centos安装Nvidia驱动解决内核版本不匹配问题 问题分析尝试解决 写程序三分钟&#xff0c;配环境三小时&#xff0c;尤其是在一台全新机器/重装系统后。。。 已经解决的&#xff1a; 禁用nouveau驱动并重启电脑&#xff08;参考这篇博客&#xff09;缺少cc&#xff0c;手动yum…

C++---状态压缩dp---炮兵阵地(每日一道算法2023.4.17)

注意事项&#xff1a; 本题为"状态压缩dp—蒙德里安的梦想"和"状态压缩dp—小国王"和"状态压缩dp—玉米田"的近似题&#xff0c;建议先阅读这三篇文章并理解。 题目&#xff1a; 司令部的将军们打算在 NM 的网格地图上部署他们的炮兵部队。 一个…

Pytorch中的仿射变换(F.affine_grid)

目录 1、平移操作实现 2、缩放操作 3、旋转操作 4、转置操作 在pytorch框架中&#xff0c; F.affine_grid 与 F.grid_sample&#xff08;torch.nn.functional as F&#xff09;联合使用来对图像进行变形。 F.affine_grid 根据形变参数产生sampling grid&#xff0c;F.grid_…

深入浅出openGauss的执行器基础

目录 火山模型 Tuple 数据结构设计 条件计算 Expr 和 Var 示例1 filter 示例2 join 示例3 index scan & index only scan 火山模型 执行器各个算子解耦合的基础。对于每个算子来说&#xff0c;只有三步&#xff1a; 1、向自己的孩子拿一个 tuple。即调用孩子节点…

C++初阶之缺省参数

目录 前言 缺省参数 1.缺省参数的概念 2.缺省参数的分类 全缺省参数 半缺省参数 前言 今天小编继续给大家带来C的内容&#xff0c;那么今天小编给大家讲解的就是有关C中缺省参数的介绍。 缺省参数 1.缺省参数的概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省…

分布式互联网——Web3.0

文章目录 前言一、什么是 Web3.0?二、Web3.0 技术1.分布式账本技术(DLT)2. 区块链3. 智能合约4. 加密算法2.操作 三、Web3.0 的未来发展 前言 Web3.0&#xff0c;也被称为“分布式互联网”&#xff0c;是当前互联网的下一代版本。它是一种未来的互联网&#xff0c;它没有中心…