C++:AVL树保姆级详解

news2024/9/20 8:13:34

目录

AVL树概念

AVL树的性质

AVL树节点定义

AVL树的构造函数和拷贝函数

构造函数

拷贝构造

AVL树的插入(灰常重要)

更新平衡因子

什么时候向上调整平衡因子

如何向上调整平衡因子

旋转

左单旋

右左单旋

右单旋

左右单旋

AVL树的验证

验证是否是二叉搜索树

这个比较简单,只需要使用一次中序遍历,打印出来如果是有序的那么就是二叉搜索树;

验证平衡因子 

AVL树的删除

AVL树的性能 

AVL树实现完整代码


AVL树概念

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

efd5c9e3993848b5b9f7655793ff7eac.png 当搜索二叉树退化成一条链的时候,搜索的时间复杂度将会变成O(N),效率就会变的很低;说到底产生这的原因就是在二叉树的高度上没有要求;AVL树的实现解决了这个问题;

AVL树的性质

1.AVL树的左右子树也是AVL树;
2.AVL数中各个节点的平衡因子(_bf)(默认为右子树高度-左子树高度)的差值的绝对值不能超过1;
解释:AVL树的节点还会存储平衡因子;平衡因子是调节AVL树高度的标准;平衡因子=右子树高度-左子树高度;因为AVL树规定就是左右子树高度差不能超过1;所以平衡因子的绝对值不能超过1;如果平衡因子是2,就需要马上进行旋转;

AVL树节点定义

为了方便多种类型的使用,所以我们通常是使用模版;

template<class T>
struct BTNode
{
	struct BTNode<T>* _parent;//父节点
	struct BTNode<T>* _left;//左孩子节点
	struct BTNode<T>* _right;//右孩子节点
	T _data;//数据值
	int _bf = 0;//平衡因子  
	//构造函数
	BTNode(T data = T())//构造函数初始化
		:_parent(nullptr), _left(nullptr), _right(nullptr), _data(data)
	{}
};

左右节点都没问题,相比二叉搜索树多了个父节点;父节点是用来维护子树和根的关系的,后面会解释;
因为新建立的节点左右子树高度差为0;所以平衡因子初始化为0;
AVL树每次插入一个新节都需要调整平衡因子,平衡因子决定是否需要发生旋转;所以_bf(平衡因子),和父节点是十分重要的; 

AVL树的构造函数和拷贝函数

首先我们先确定AVLTree的成员变量;AVL树的是一个二叉树,所以我们只需要定义一个节点指针即可;

	template<class T>
	class AVLTree
	{
		typedef BTNode<T>  node;//取别名,方便使用
	private:
		node* _root = nullptr;  
    }

构造函数

	template <class T>
	class AVLTree
	{
		typedef BTNode<T>   node;
	public:
		AVLTree()
			:_node(nullptr)
		{}
    private:
    node* _node=nullptr;
    }

构造函数只需要把_node初始化为nullptr即可; 

拷贝构造

	template <class T>
	class AVLTree
	{
		typedef BTNode<T>   node;
	public:
		AVLTree(AVLTree<T>& tree)
			:_node(nullptr)
		{
			_node = copy(tree->_node);
		}
    private:
	//拷贝二叉树
	node* copy(const node* tree)
	{
		if (tree == nullptr)return nullptr;
		node* newnode = new node(tree->_data);
		newnode->_left = copy(tree->_left);
		newnode->_right = copy(tree->_right);
		return newnode;
	}
    node* _node=nullptr;
    }

拷贝构造函数的参数是是类;而拷贝树的参数是根节点指针;所以为了更加的清晰的实现;我们可以把copy函数和拷贝构造分开;将copy定义为私有,就能实现完美封装;
注意:copy的参数是指针,不能使用缺省值;

AVL树的插入(灰常重要)

AVL树的插入是在二叉搜索树的基础上添加了平衡因子的更新以及根据平衡因子实现对应的旋转;因此前一段代码两者是相同的;

//插入数据(需要判断是否插入成功)
bool  insert(const T& data)
{
	if (_node == nullptr)
	{
		_node = new node(data);
		return true;
	}
	node* cur = _node;
	node* parent = _node->_parent;
	while (cur)
	{
		if (data < cur->_data)//如果数据小就走左边
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_right;
		}
		else//等于节点的值
		{
			return false; //插入失败
		}
	}
	//这个时候cur为空
	cur = new node(data);   //开辟节点
	cur->_parent = parent;
	//判断是parent的左边还是右边
	if (data < parent->_data)
	{
		parent->_left = cur;
	}
	else
		parent->_right = cur;

	//更新平衡因子
	while (parent)//直到更新到根节点
	{
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else
			parent->_bf++;
		//判断是否需要调整
		if (parent->_bf == 0)//说明不需要调整
		{
			break;
		}
		else  if (parent->_bf == 1 || parent->_bf == -1)//说明左边高
		{
			cur = parent;
			parent = parent->_parent;
		}
		else
		{
			if (parent->_bf == 2 && cur->_bf == 1)RotateL(parent);
			if (parent->_bf == 2 && cur->_bf == -1)RotateRL(parent);
			if (parent->_bf == -2 && cur->_bf == 1)RotateLR(parent);
			if (parent->_bf == -2 && cur->_bf == -1)RotateR(parent);
		}
	}
	return true;   
}

插入的位置在博客CSDN中,需要的可以看;下面来解释下什么时候更新平衡因子和如何更新平衡因子;

更新平衡因子

什么时候向上调整平衡因子

f54e26c619bd446893652e023eb65037.png

AVL树的最初形态就是高度为1和高度为2;在这两个高度中是不可能会出现向上调整的情况的;

下面我们列出继续向下插入的情况(因为二叉树具有对称性所以,我们只研究一侧规律即可)
这里我画的是右侧的情况
1806f30083ea4b2889d86de5eaf72f1a.png
总结:parent和cur的第一次更新不叫向上调整;第一次parent更新后的平衡因子数决定的了是否需要向上调整;
如果第一次更新后的parent的_bf=1或-1,说明parent子树更新前的_bf是0;插入新节点就是唯一的自孩子,parent子树的高度改变了,就会影响到parent之上的节点的平衡因子,这个时候就需要向上调整;
如果第一次更新后的parent的_bf=0;说明parent子树更新前_bf,不是1就是-1,插入的新节点是另一个叶节点的兄弟,并没有改变parent树的高度,这个时候不需要向上调整平衡因子;

如何向上调整平衡因子

逻辑:如果我(cur)是你(parent)的左子树,那么你的bf就-- ,如果我是你的右子树,那么你的bf就++(因为平衡因子的大小=右子树的高度-左子树的高度。)parent和cur每指向一个节点就会触发更新平衡因子的信号,而决定_bf怎么改变的动机就是cur是parent的左节点还是右节点;

//更新平衡因子
while (parent)//直到更新到根节点
{
	if (cur == parent->_left)//更新平衡因子的规则
	{
		parent->_bf--;
	}
	else
		parent->_bf++;
	//判断是否需要调整
	if (parent->_bf == 0)//说明不需要调整
	{
		break;
	}
	else  if (parent->_bf == 1 || parent->_bf == -1)//需要向上调整
    {
		cur = parent;/让parent和cur向上移动,触发更新平衡因子的信号
		parent = parent->_parent;
	}
}

一旦向上调整就会调整到根节点,所以循环的结束条件就是parent不为空;(根节点的parent为nullptr);

但是呢,在一路向上更新的_bf的同时,会不会出现_bf等于2的情况呢?我们来看个例子:
fed4054eb8f649b3a8f1de13b339d286.png
我们每次调整的都是parent的平衡因子,平衡因子出现时,一定是parent的_bf=2;这个时候如果要更新平衡因子,就需要对parent和cur进行旋转;旋转后面会解释;
完善下更新平衡因子代码:

	//更新平衡因子
while (parent)//直到更新到根节点
{
	if (cur == parent->_left)//更新平衡因子的规则
	{
		parent->_bf--;
	}
	else
		parent->_bf++;
	//判断是否需要调整
	if (parent->_bf == 0)//说明不需要调整
	{
		break;
	}
	else  if (parent->_bf == 1 || parent->_bf == -1)//需要向上调整
    {
		cur = parent;/让parent和cur向上移动,触发更新平衡因子的信号
		parent = parent->_parent;
	}
	else
		{
               //根据parent和cur的平衡因子旋转
			if (parent->_bf == 2 && cur->_bf == 1)RotateL(parent);
			else if (parent->_bf == 2 && cur->_bf == -1)RotateRL(parent);
			else if (parent->_bf == -2 && cur->_bf == 1)RotateLR(parent);
			else if (parent->_bf == -2 && cur->_bf == -1)RotateR(parent);
			else
				assert(false);   
            break;    
		}
	}
	return true;   
}

注意:因为旋转后就已经是平衡的,所以只要发生了任意情况的旋转后,就不需要在向上调整了;直接break; 

旋转

二叉树具有对称性,所以只研究一侧;所以下面我详细讲的都是右侧的情况,左侧的类似,后面直接给出结果;

左单旋

什么时候左单旋呢?当subR在parent的右子树上,新节点在subR的右子树上时,也就是两个节点都在上一个关键节点(parent,subR)的右子树上;所以右旋的口诀是右右(左)单旋;

下面是单旋转的图示:

b496c6dcf99a46658fa7f7f9add0e6d6.png根据图画,一目了然;
单旋的详细操作
其实从上图我们清晰的发现,关键节点只有四个(parent,subR,subRL,新节点);为了保持平衡树的特性,旋转过程中会发生节点的断裂,左旋中parent会成为subR的左孩子,而subR的左孩子会成为parent的右孩子,这也是为什么要给subRL命名的原因(这个节点是需要断裂重连的);

旋转完毕后我们需要重新更新这些个节点的平衡因子;从上图我们可以发现单旋过后2个节点的平衡因子(parent,subR)都是0;
注意subRL也可能是一个子树,所以不一定平衡因子是0;所以这个不需要管;

下面是左单旋的代码实现:

//左单旋
void RotateL(node* parent)
{
	node* subR = parent->_right;
	node* subRL = subR->_left;
	//需要记录parent的父节点
	node* pparent = parent->_parent;//旋转后需要与parent上面的节点重连,所以需要先标记parent的父节点

	//更新各节点的指向
	if (subRL)subRL->_parent = parent;
	parent->_right = subRL;

	subR->_left = parent;
	parent->_parent = subR;

	if (pparent == nullptr)
	{
		_node = subR;
		_node->_parent = nullptr;
	}
	else
	{
		if (parent == pparent->_left)
		{
			pparent->_left = subR;
			subR->_parent = pparent;
		}
		else
		{
			pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
	parent->_bf = subR->_bf = 0;
}
右左单旋

在有些情况三个关键节点并不在同一侧的子树上;可能是subR在parent的右子树上,而新节点在subR的左子树上;这中情况不可以仅仅使用右单旋完成旋转;需要先右旋,再左旋;

下面是旋转的情况图示:
e4692ee1198d48658a4ee2dc82cf341f.png

 这里的关键就是判断旋转的方式;我是根据位置判断的,8079efbbeced4d8190126b9aa2542e1e.png如果是这个样子,那就是右左旋,先对parent->_right(subR)右旋;在对subR左旋:
关键节点(parent,subR,subRL)变化过程:e493a966f2d4460a8661b66ff171a405.png

下面是代码实现:

//右左旋
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);
}

下面的右单旋和左右单旋不在详细介绍了;直接给出代码实现;

右单旋
	//右单旋
	void RotateR(node* parent)   
	{
		node* subL = parent->_left;
		node* subLR = parent->_right;
		node* pparent = parent->_parent;

		//考虑两点:谁指向我,我指向谁
		parent->_left = subLR;
		if (subLR)subLR->_parent = parent;

		parent->_parent = subL;
		subL->_right = parent;

		//判断 祖父节点
		if (pparent == nullptr)
		{
			_node = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}

		parent->_bf = subL->_bf = 0;
	}
左右单旋
//左右单旋
void RotateLR(node* parent)
{
	node* subL = parent->_left;
	node* subLR = subL->_right;
	int bf = subLR->_bf;

	//先右再左
	RotateR(subL);
	RotateL(parent);

	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);
}
void print()
{
	_print(_node);
	cout << endl;
}

到此为止,insert的实现就完成了;

AVL树的验证

要验证是否是AVL树,需要验证是否是二叉搜索树,还有平衡因子是否正确;

验证是否是二叉搜索树

这个比较简单,只需要使用一次中序遍历,打印出来如果是有序的那么就是二叉搜索树;

//打印
void _print(node* node)
{
	if (node == nullptr)return;

	_print(node->_left);
	cout << node->_data << " ";
	_print(node->_right);
}

验证平衡因子 

需要验证每个节点的平衡因子是否是等于右子树和左子树之差;以及平衡因子的绝对值是否小于2;

	bool is_balance()
	{
		return balance(_node);     
	}
private:
	bool balance(node* root)  
	{
		if (root == nullptr)return true;
		//分别求出左右子树的高度
		int left_height = _height(root->_left);   
		int right_height = _height(root->_right);
		if (right_height - left_height != root->_bf)
		{
			cout << "平衡因子异常" << endl;   
               return false;
		}
		return abs(right_height - left_height) < 2 && balance(root->_left) && balance(root->_right);           
	}

AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现学生们可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版.

AVL树的性能 

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

AVL树实现完整代码

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<vector>
#include<assert.h>   
using namespace std;

namespace bit
{
	template<class T>
	struct BTNode
	{
		struct BTNode<T>* _parent;
		struct BTNode<T>* _left;
		struct BTNode<T>* _right;
		T _data;
		int _bf = 0;//平衡因子  
		//构造函数
		BTNode(T data = T())
			:_parent(nullptr), _left(nullptr), _right(nullptr), _data(data)
		{}
	};


	template <class T>
	class AVLTree
	{
		typedef BTNode<T>   node;
	public:
		AVLTree()
			:_node(nullptr)
		{}
		AVLTree(vector<T>& arr)
		{
			for (auto x : arr)
				insert(x);
		}
		//拷贝构造
		AVLTree(AVLTree<T>& tree)
			:_node(nullptr)
		{
			_node = copy(tree->_node);
		}
		//插入数据(需要判断是否插入成功)
		bool  insert(const T& data)
		{
			if (_node == nullptr)
			{
				_node = new node(data);
				return true;
			}
			node* cur = _node;
			node* parent = _node->_parent;
			while (cur)
			{
				if (data < cur->_data)//如果数据小就走左边
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_right;
				}
				else//等于节点的值
				{
					return false; //插入失败
				}
			}
			//这个时候cur为空
			cur = new node(data);   //开辟节点
			cur->_parent = parent;
			//判断是parent的左边还是右边
			if (data < parent->_data)
			{
				parent->_left = cur;
			}
			else
				parent->_right = cur;

			//更新平衡因子
			while (parent)//直到更新到根节点
			{
				if (cur == parent->_left)
				{
					parent->_bf--;
				}
				else
					parent->_bf++;
				//判断是否需要调整
				if (parent->_bf == 0)//说明不需要调整
				{
					break;
				}
				else  if (parent->_bf == 1 || parent->_bf == -1)//说明左边高
				{
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					if (parent->_bf == 2 && cur->_bf == 1)RotateL(parent);
					else if (parent->_bf == 2 && cur->_bf == -1)RotateRL(parent);
					else if (parent->_bf == -2 && cur->_bf == 1)RotateLR(parent);
					else if (parent->_bf == -2 && cur->_bf == -1)RotateR(parent);
					else
						assert(false);   
					break;   
				}
			}
			return true;   
		}
		//左单旋
		void RotateL(node* parent)
		{
			node* subR = parent->_right;
			node* subRL = subR->_left;
			//需要记录parent的父节点
			node* pparent = parent->_parent;

			//更新各节点的指向
			if (subRL)subRL->_parent = parent;
			parent->_right = subRL;

			subR->_left = parent;
			parent->_parent = subR;

			if (pparent == nullptr)
			{
				_node = subR;
				_node->_parent = nullptr;
			}
			else
			{
				if (parent == pparent->_left)
				{
					pparent->_left = subR;
					subR->_parent = pparent;
				}
				else
				{
					pparent->_right = subR;
					subR->_parent = pparent;
				}
			}
			parent->_bf = subR->_bf = 0;
		}
		//右单旋
		void RotateR(node* parent)   
		{
			node* subL = parent->_left;
			node* subLR = parent->_right;
			node* pparent = parent->_parent;

			//考虑两点:谁指向我,我指向谁
			parent->_left = subLR;
			if (subLR)subLR->_parent = parent;

			parent->_parent = subL;
			subL->_right = parent;

			//判断 祖父节点
			if (pparent == nullptr)
			{
				_node = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (parent == pparent->_left)
				{
					pparent->_left = subL;
				}
				else
					pparent->_right = subL;
				subL->_parent = pparent;
			}

			parent->_bf = subL->_bf = 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);
		}
		//左右单旋
		void RotateLR(node* parent)
		{
			node* subL = parent->_left;
			node* subLR = subL->_right;
			int bf = subLR->_bf;

			//先右再左
			RotateR(subL);
			RotateL(parent);

			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);
		}
		void print()
		{
			_print(_node);
			cout << endl;
		}
		bool is_balance()
		{
			return balance(_node);     
		}
	private:
		bool balance(node* root)  
		{
			if (root == nullptr)return true;
			//分别求出左右子树的高度
			int left_height = _height(root->_left);   
			int right_height = _height(root->_right);
			if (right_height - left_height != root->_bf)
			{
				cout << "平衡因子异常" << endl;   
			}
			return abs(right_height - left_height) < 2 && balance(root->_left) && balance(root->_right);           
		}
		//求出二叉树的高度
		size_t _height(node* root)   
		{
			if (root == nullptr)return 0;
			int h1 = _height(root->_left);  
			int h2 = _height(root->_right);   
			return max(h1,h2)+1;            
		}
		//拷贝二叉树
		node* copy(const node* tree)
		{
			if (tree == nullptr)return nullptr;
			node* newnode = new node(tree->_data);
			newnode->_left = copy(tree->_left);
			newnode->_right = copy(tree->_right);
			return newnode;
		}
		//打印
		void _print(node* node)
		{
			if (node == nullptr)return;

			_print(node->_left);
			cout << node->_data << " ";
			_print(node->_right);
		}

		node* _node = nullptr;
	};
}

95816cc736af4c42a5560b8427fa03ab.jpeg

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

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

相关文章

Apollo星火计划基础速记

1 Apollo规划模块 规划根据感知预测的结果,当前定位信息以及Routing的结果,规划出一条安全行驶的轨迹送给控制模块 其中NaviPlanning主要用于高速公路的导航规划,OnLanePlanning用于城区及高速公路各种复杂车道;OpenSpacePlanning用于没有车道线的场景,主要在自主泊车和狭…

JavaDS —— 并查集

并查集概念 案例引入&#xff1a; 假设现在有三个程序设计小分队&#xff0c;分别来自广东&#xff0c;广西和海南&#xff0c;其中广东小分队人员的编号为{0&#xff0c;6&#xff0c;7&#xff0c;8} 广西小分队人员编号为{1&#xff0c;4&#xff0c;9}&#xff0c;海南小分…

关于WebZip乱码目录文件名修改

先引用一段 来描述问题&#xff1a; 在IT行业中&#xff0c;我们经常遇到与编码和字符集有关的问题&#xff0c;特别是在处理包含中文字符的文件或目录时。"WebZip乱码目录文件名修改"这个问题就是一个典型的例子&#xff0c;涉及到Webzip工具在下载包含中文路径的文…

清洁眼镜片

经常眼镜弄脏了 怎么弄都干净不了 根本无法佩戴 影响出门时间 有时在外很尴尬 其实清洁很简单 从水龙水冲洗镜片(可附上洗手液) 然后用纸巾局部点触抹干偶尔泛起小水滴 就好

计算机毕业设计SpringBoot+VUE自动灌装生产线 MES 系统设计

采用 B/S 架构&#xff0c;MES 应用软件通过 TCP/IP 协议与自动灌装生产线上的各个工作单元中的 PLC 控制器进行通信&#xff0c;查询或采集由 PLC 控制器采集的生产数据。通过 JAVA 构建的平台与数据库进行连接&#xff0c;实现灌装生产线的生产管理、订单管理、质量管理和数据…

DPDK基础入门(六):从PCIe事务的角度看包处理

PCIe PCI Express&#xff08;Peripheral Component Interconnect Express&#xff09;又称PCIe&#xff0c;它是一种高速串行通信互联标准。PCIe规范遵循开放系统互联参考模型&#xff08;OSI&#xff09;&#xff0c;自上而下分为事务传输层、数据链路层、物理层。对于特定的…

【Hot100】LeetCode—70. 爬楼梯

目录 1- 思路动规五部曲 2- 实现⭐763. 划分字母区间——题解思路 3- ACM 实现 原题链接&#xff1a;70. 爬楼梯 1- 思路 动规五部曲 1- dp 数组创建&#xff0c;确定含义 dp[i] 代表到达 楼梯 i 的方法数 2- 状态转移方程 因为一共有两种移动的方式&#xff0c;当前 dp[i] …

基于Java的垃圾分类网站系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架&#xff0c;B/S架构 工具&#xff1a;MyEclipse, Tomcat 系统展示 首页 用户管理…

Radmin-同一局域网只需IP就可以控制电脑

Radmin小编十多年前就在用&#xff0c;它是一款非常好用的局域网控制工具&#xff0c;可以完全替代Windows自带的远程桌面&#xff0c;它的安全性和便于操作性都比Windows的远程桌面好用。 Radmin还有一个好处&#xff0c;就是远程别人电脑时&#xff0c;对方那边毫无察觉&…

明明的随机数处理问题分析与解决方案

明明的随机数处理问题分析与解决方案 引言问题描述解决方案数据结构设计具体步骤伪代码C语言实现详细解释读取输入去重操作排序操作输出结果复杂度分析引言 明明生成了N个1到500之间的随机整数,我们需要对这些整数进行处理,删去重复的数字,然后进行排序并输出结果。本文将详…

【JavaScript】LeetCode:16-20

文章目录 16 无重复字符的最长字串17 找到字符串中所有字母异位词18 和为K的子数组19 滑动窗口最大值20 最小覆盖字串 16 无重复字符的最长字串 滑动窗口 哈希表这里用哈希集合Set()实现。左指针i&#xff0c;右指针j&#xff0c;从头遍历数组&#xff0c;若j指针指向的元素不…

jmeter性能测试HTML测试报告生成详解

作用&#xff1a;jmeter支持生成HTML测试报告&#xff0c;方便查看测试计划中获得图表和统计信息 命令&#xff1a; jmeter -n -t [jmx file] -l [result file] -e -o [html report folder] 示例&#xff1a;jmeter -n -t login.jmx -l result.jtl -e -o ./report jmx文件&a…

玛雅Maya2024下载安装教程影视三维3D设计教程百度网盘分享链接地址

玛雅Maya下载安装教程影视三维3D设计教程百度网盘分享链接地址,玛雅是一款3d设计软件。Maya 是由 Autodesk 公司开发的专业三维计算机图形软件。主要应用于影视特效制作、游戏开发、广告设计和工业设计等领域。在影视特效中&#xff0c;可创建逼真特效场景和角色动画&#xff1…

【Day09-IO-字符流其它流】

IO流 IO流-字符流 字节流&#xff1a;适合复制文件等&#xff0c;不适合读写文本文件 字符流&#xff1a;适合读写文本文件内容 FileReader&#xff08;文件字符输入流&#xff09; 作用&#xff1a;以内存为基准&#xff0c;可以把文件中的数据以字符的形式读入到内存中来。 …

husky 工具配置代码检查工作流:提交代码至仓库前做代码检查

提示&#xff1a;这篇博客以我前两篇博客作为先修知识&#xff0c;请大家先去看看我前两篇博客 博客指路&#xff1a;前端 ESlint 代码规范及修复代码规范错误-CSDN博客前端 Vue3 项目开发—— ESLint & prettier 配置代码风格-CSDN博客 husky 工具配置代码检查工作流的作…

高并发内存池(二):​整体框架的介绍与ThreadCache的实现

目录 整体框架介绍 ThreadCache的主体框架 自由链表-FreeList 内存对齐-RoundUp 计算桶位置-Index 基础版 进阶版 线程局部存储 __declspec(thread) 关键字 实现线程无锁 申请内存-Allocate 释放内存-Deallocate 从中心缓存中申请内存 整体框架介绍 高并发内存池…

变量数据类型 Day3

1. 变量 1.1 变量的概念 变量是计算机内存中的一块存储单元&#xff0c;是存储数据的基本单元变量的组成包括&#xff1a;数据类型、变量名、值&#xff0c;后文会具体描述变量的本质作用就是去记录数据的&#xff0c;比如说记录一个人的身高、体重、年龄&#xff0c;就需要去…

【微处理器系统原理和应用设计第十讲】外部中断之开发键控灯亮灭功能

一、基础知识 外部设备所产生的信号通过EXIT触发中断。 1、与中断相关的主要寄存器 EXTI共设有6个寄存器&#xff0c;分别为中断屏蔽寄存器&#xff08;IMR&#xff09;&#xff0c;事件屏蔽寄存器&#xff08;EMR&#xff09;&#xff0c;上升沿触发选择寄存器&#xff08;…

Ubuntu | 安装 Truffle 框架(安装缓慢)

目录 预备工作具体步骤Step1&#xff1a;安装 nvma. 官方方式&#xff08;可能失败&#xff09;b. 压缩包安装方式 Step2&#xff1a;安装 node.js 和 npmStep3&#xff1a;安装 Truffle 参考博客 前言&#xff1a;昨天安装 Truffle 框架&#xff0c;结果缓冲条转了一晚上都没安…

利士策分享,如何平衡物质追求与心理健康?

利士策分享&#xff0c;如何平衡物质追求与心理健康? 在快节奏的现代社会&#xff0c;物质追求与心理健康仿佛成了人们生活中不可或缺的两极。 一方面&#xff0c;科技的飞速发展和经济的繁荣让我们拥有了前所未有的物质享受&#xff1b; 另一方面&#xff0c;高压的工作环…