AVL树的介绍与实现

前言

我们上一期介绍了二叉搜索树并做了实现,本期我们来继续学习另一个更优的树即AVL树!

本期内容介绍

什么是AVL树?

AVL树的实现

AVL树的性能分析

在正式的介绍AVL树之前,我们先来回忆一下二叉搜索树的特点左子树的值一定小于根节点的值,右子树的值一定大于根节点的值;基于他的这个特点,可以缩短查找的区间即可以提升查找的效率!但是他在有些情况下效率并不是很好。例如:当数据是有序或接近有序时,查找得需要O(N)的时间复杂度即退化成单链就和和链表一样了,效率不太好!为了解决这个问题,有人就提出了AVL树!

什么是AVL树?

为了解决二叉搜索树的弊端,在1962年来自俄罗斯的两位数学家G.M.Adelson-Velskii和E.M.Landis研究出了一种解决上述问题的方法:当向二叉树种插入新节点时,如果保每个节点的左右子树的高度的绝对值之差不超过1(如果超过了1需要内部调整)既可以降低树的高度,从而减少平均查找长度!符合这样的二叉搜索树就叫做平衡二叉搜索树即AVL树。也就是:

一颗AVL树要么是空树要么是符合下面性质的二叉搜索树:

该二叉搜索树的左右子树都是AVL树;

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

平衡因子可以是左减右,也可以右减左;这里采用后者!

所以,下面这棵二叉搜索树就是一个AVL树:

AVL树的实现

还是和以前一样先搭个架子出来,多次用到节点的开辟等操作,所以我们创建一个专门的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)//默认一个新节点是叶子节点,左右子树都为空所以高度差(平衡因子)为0
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:


private:
	Node* _root = nullptr;//这里只有一个成员可以给一个缺省值就不用写构造函数了
};

AVL树的插入

AVL树的本质还是二叉搜索树,所以插入的时候还是遵循二叉搜索树的特点的!但是多了一个平衡因子,所以他的插入是分为两步的:

1、按照二叉搜索树的规则插入

2、调节平衡因子

前者很好理解,这里主要解释一下后者:我们采用的平衡因子的方式是右子树的高度 - 左子树高度所以当新插入节点后,要更新其父先节点的平衡因子,如果插入的节点是在parent的左,平衡因子--; 如果是右平衡parent的因子++;

1、如果更新后父节点的平衡因子是0,即原先的parent左或右是有一个孩子的,现在是插入到原先没有孩子的那边了即平衡了,此时直接结束调节平衡因子;

2、如果更新parent的平衡因子后不是0,而是-1/1则需要继续向上更新!(直到cur更新到根节点,停止)

3、如果更新后父节点的平衡因子的绝对值超过了1就要旋转。(旋转后面单独介绍)

OK,举个例子:

这里咱们先暂时不管旋转是怎么旋转的,我们先把上一般的给搞出来:

bool Insert(const pair<K, V>& kv)
{
	Node* parent = nullptr;//记录插入位置的父节点
	Node* cur = _root;//当前插入节点
		
	//第一次插入
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	//不是第一次插入,寻找插入位置
	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);//找到插入位置
	//判断是parent的左子树还是右子树
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;//将当前节点的父节点设置是为parent

	//调节平衡因子 ---> bf = right - left
	while (parent)
	{
		//更新当前节点的父节点的平衡因子
		if (cur == parent->_left)//在parent的左
		{
			parent->_bf--;//bf--
		}
		else
		{
			parent->_bf++;//在parent的右, bf++
		}
			
		if (parent->_bf == 0)//如果更新完,cur的parent的bf,发现是0。说明是 -1/1 --> 0 即parent原先左或右是有一个孩子的
		{
			break;//更新结束
		}
		else if (parent->_bf == 1 || parent->_bf == -1)//如果是parent的bf是1/-1
		{
			cur = parent;
			parent = parent->_parent;//需要继续更新其祖先节点
		}
		else
		{
			//此时parent的bf是-2/2需要旋转


			break;//旋转结束跳出平衡因子的调节
		}
	}

	return true;//插入成功
}

OK,先来验证一下,目前的逻辑对不对?AVL树的本质还是搜索树,所以他的中序是有序的,所以可以通过走中序验证目前的对不对:

中序遍历

实现思路:左->中->右

注意:由于AVL树的根节点是私有的,类的外面是访问呢不到的,所有以下三种思路:

1、将中序设置为友元(强烈推荐)

2、提供get和set函数

3、通过子函数(推荐)

void _InOrder(const Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}

这是子函数,因为只有AVL类里面专用所以可以将它设置为私有的!

OK,就以上面介绍过的为例:

我们乱序插入到当前的AVL树中,如果中序是有序的说明我们的当前逻辑是对的!

OK,现在parent的bf==1/-1以及等于0的情况解决了,剩下的就是parent的bf是-2/2的情况了,此时就需要旋转了!我们下面来专门谈一谈旋转!

AVL树的旋转

旋转是由于parent的bf到了-2/2,此时的结构不符合AVL的平衡了;而parent为-2/2是由其孩子造成的,而孩子有两种情况即左边和右边即cur是-1/1;所以此时就有四种情况:
1、parent == -2 && cur == -1即父亲的左边高、孩子(左子树)的左边高 --> 右单旋

2、parent == 2  && cur == 1即父亲的右边高、孩子(右子树)的右边高 --> 左单旋

3、parent == -2 && cur == 1即父亲的左边高、孩子(左子树)的右边高 --> 左右双旋

4、parent == 2  && cur == -1即父亲的右边高、孩子(右子树)的左边高 --> 右左双旋

右单旋

parent的左边高,并且他的左孩子也高!

void RotateR(const Node* parent)
{
	Node* subL = parent->_left;//parent的左子树

	parent->_left = subL->_right;//将subL的又给parent的左
	if (subL->_right)//如果subL的右不为空
		subL->_right = parent;//此时subL右的父节点就是parent

	Node* ppNode = parent->_parent;//记录parent的父节点方便后面subL不是root时候的链接
	subL->_right = parent;//将parent连接到subL的右边
	parent->_parent = subL;//将parent的父节点设置为subL

	if (parent == _root)//当前的parent是根节点
	{
		_root = subL;//新的根节点就是subL
		ppNode = nullptr;//根节点的父亲为空
	}
	else//当前的parent不是根节点
	{
		if (ppNode->_left == parent)//如果parent的是ppNode的左
		{
			ppNode->_left = subL;//将subL连接到ppNode的左边
		}
		else//parent的是ppNode的右
		{
			ppNode->_right = subL;//将subL连接到ppNode的右边
		}

		subL->_parent = ppNode;//subL的父节点指向ppNode
	}

	subL->_bf = parent->_bf = 0;//右单旋后subL和parent的bf都是0
}

左单旋

parent的右边高,并且他的右孩子也高!

void RotateL(const Node* parent)
{
	Node* subR = parent->_right;//parent的右子树
	parent->_right = subR->_left;//将subR的左子树给parent的右
	if (subR->_left)//如果subR的左不为空
		subR->_left->_parent = parent;//subR的左的父亲就是parent

	Node* ppNode = parent->_parent;//记录parent的父节点方便后面subL不是root时候的链接
	subR->_left = parent;//将parent连接到subR的左
	parent->_parent = subR;//parent的父节点就是subR

	if (parent == _root)//parent是根节点
	{
		_root = subR;//此时subR就是新的根
		_root->_parent = nullptr;
	}
	else//parent不是根节点
	{
		if (ppNode->_left == parent)//parent是ppNode的左
		{
			ppNode->_left = subR;//将subR连接到ppNode的左
		}
		else//parent是ppNode的右
		{
			ppNode->_right = subR;//将subR连接到ppNode的右
		}

		subR->_parent = ppNode;//subR的父节点指向ppNode
	}

	subR->_bf = parent->_bf = 0;//左单旋后subR和parent的bf都是0
}

左右双旋

parent的左孩子高,并且他左孩子的右边高!

void RotateLR(const Node* parent)
{
	Node* subL = parent->_left;//左子树
	Node* subLR = subL->_right;//左子树的右子树

	int bf = subLR->_bf;

	RotateL(parent->_left);//先对左子树左旋
	RotateR(parent);//在对整个树进行右旋

	if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 1;
	}
	else
	{
		assert(false);//正常不可能到这,这里是防止一开始就不是AVL
	}
}

右左双旋

parent的右孩子高,他的右孩子的左边高!

void RotateRL(const Node* parent)
{
	Node* subR = parent->_right;//右子树
	Node* subRL = subR->_left;//右子树的左子树

	int bf = subRL->_bf;

	RotateR(parent->_right);//先对右子树右旋
	RotateL(parent);//在对整个树进行左旋

	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 1;
	}
	else
	{
		assert(false);//正常不可能到这,这里是防止一开始就不是AVL
	}
}

ok,这就是所有的情况,我们现在加上旋转来看看,为了验证是否是平衡的,我们可以写一个判断是否平衡的函数:

判断平衡

实现思路:某一个节点的左右子树的差的绝对值不可以超多1

由于AVLTree类外面访问不到根,所以我们还是写成子函数的形式:
 

bool _IsBalance(const Node* root)
{
	if (root == nullptr)//空树也平衡
		return true;

	int left = _Hight(root->_left);//求左子树的高度
	int right = _Hight(root->_right);//求右子树的高度

	if (abs(left - right) >= 2)//如果左右子树的高度差的绝对值差超过1则就是不平衡
		return false;

	if (right - left != root->_bf)//不平衡,打印出他的节点的key值
	{
		cout << root->_kv.first << endl;
		return false;
	}

	return _IsBalance(root->_left) && _IsBalance(root->_right);//左右子树都得平衡
}

int _Hight(Node* root)
{
	if (root == nullptr)//空树的个数是0
		return 0;

	return max(_Hight(root->_left), _Hight(root->_right)) + 1;//不是空返回左右子树的较大值 + 本身
}

ok ,验证一下:

OK,没有问题!我们再来把其他的完善一下:

AVL树的查找

实现思路:和二叉搜索树的一样,比根的去右边找,比根小去左边找!

Node* Find(const K& k)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < k)//插入节点的键值比当前比较的节点的键值大
		{
			cur = cur->_right;//去当前节点的右子树查找
		}
		else if (cur->_kv.first > k)//插入节点的键值比当前比较的节点的键值小
		{
			cur = cur->_left;//去当前节点的左子树查找
		}
		else
		{
			return cur;//找到了
		}
	}

	return nullptr;//没找到
}

获取高度

实现思路:左子树 + 右子树 + 1(本身)

int _Hight(Node* root)
{
	if (root == nullptr)//空树的个数是0
		return 0;

	return max(_Hight(root->_left), _Hight(root->_right)) + 1;//不是空返回左右子树的较大值 + 本身
}

获取节点个数

实现思路:左子树的节点 + 右子树的节点 + 根

int _Size(const Node* root)
{
	if (root == nullptr)//空树
		return 0;
		
	return _Size(root->_left) + _Size(root->_right) + 1;//左子树+右子树+本身
}

AVL树的性能分析

AVL树是一颗绝对平衡的二叉搜索树,其要求每个节点的左右子树差都不超过1,这样可以保证查询高效的同时复杂度不会达到和二叉搜索树的极端情况的O(N)而是在logN;但是如果对AVL树做一些结构的修改,例如:插入太多次旋转也就多了,更差的是在删除时有可能旋转到根;因此如果需要一种查询且有序的数据结构,再者数据为静态的(不会改变)AVL树是一种不错的选择,但是如果经常修改其行能就不太好了,那要是既要改变还要效率高该如何弄呢?那就是下期介绍的红黑树了!!!

全部源码

#pragma once
#include <assert.h>

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)//默认一个新节点是叶子节点,左右子树都为空所以高度差(平衡因子)为0
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		Node* parent = nullptr;//记录插入位置的父节点
		Node* cur = _root;//当前插入节点

		//第一次插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		//不是第一次插入,寻找插入位置
		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);//找到插入位置
		//判断是parent的左子树还是右子树
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//将当前节点的父节点设置是为parent

		//调节平衡因子 ---> bf = right - left
		while (parent)
		{
			//更新当前节点的父节点的平衡因子
			if (cur == parent->_left)//在parent的左
			{
				parent->_bf--;//bf--
			}
			else
			{
				parent->_bf++;//在parent的右, bf++
			}

			if (parent->_bf == 0)//如果更新完,cur的parent的bf,发现是0。说明是 -1/1 --> 0 即parent原先左或右是有一个孩子的
			{
				break;//更新结束
			}
			else if (parent->_bf == 1 || parent->_bf == -1)//如果是parent的bf是1/-1   0 ---> -1/1
			{
				cur = parent;
				parent = parent->_parent;//需要继续更新其祖先节点
			}
			else//此时parent的bf是-2/2需要旋转 -1/1 --> 2/-2
			{
				if (parent->_bf == -2 && cur->_bf == -1)//父亲和孩子的左边都高,右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)//父亲和孩子的右边都高,左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//父亲的左边高,孩子的右边高,先左单旋,在右单旋
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//父亲的右边高,孩子的左边高,先右单旋,在左单旋
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);//正常情况不可能走到这里,这里是防止一开始就不是AVL树的情况
				}

				break;//旋转结束跳出平衡因子的调节
			}
		}

		return true;//插入成功
	}

	Node* Find(const K& k)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < k)//插入节点的键值比当前比较的节点的键值大
			{
				cur = cur->_right;//去当前节点的右子树查找
			}
			else if (cur->_kv.first > k)//插入节点的键值比当前比较的节点的键值小
			{
				cur = cur->_left;//去当前节点的左子树查找
			}
			else
			{
				return cur;//找到了
			}
		}

		return nullptr;//没找到
	}


	void InOrder()
	{
		return _InOrder(_root);
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Hight()
	{
		return _Hight(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

private:
	void _InOrder(const Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;//parent的左子树

		parent->_left = subL->_right;//将subL的又给parent的左
		if (subL->_right)//如果subL的右不为空
			subL->_right->_parent = parent;//此时subL右的父节点就是parent

		Node* ppNode = parent->_parent;//记录parent的父节点方便后面subL不是root时候的链接
		subL->_right = parent;//将parent连接到subL的右边
		parent->_parent = subL;//将parent的父节点设置为subL

		if (parent == _root)//当前的parent是根节点
		{
			_root = subL;//新的根节点就是subL
			_root->_parent = nullptr;//根节点的父亲为空
		}
		else//当前的parent不是根节点
		{
			if (ppNode->_left == parent)//如果parent的是ppNode的左
			{
				ppNode->_left = subL;//将subL连接到ppNode的左边
			}
			else//parent的是ppNode的右
			{
				ppNode->_right = subL;//将subL连接到ppNode的右边
			}

			subL->_parent = ppNode;//subL的父节点指向ppNode
		}

		subL->_bf = parent->_bf = 0;//右单旋后subL和parent的bf都是0
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;//parent的右子树
		parent->_right = subR->_left;//将subR的左子树给parent的右
		if (subR->_left)//如果subR的左不为空
			subR->_left->_parent = parent;//subR的左的父亲就是parent

		Node* ppNode = parent->_parent;//记录parent的父节点方便后面subL不是root时候的链接
		subR->_left = parent;//将parent连接到subR的左
		parent->_parent = subR;//parent的父节点就是subR

		if (parent == _root)//parent是根节点
		{
			_root = subR;//此时subR就是新的根
			_root->_parent = nullptr;
		}
		else//parent不是根节点
		{
			if (ppNode->_left == parent)//parent是ppNode的左
			{
				ppNode->_left = subR;//将subR连接到ppNode的左
			}
			else//parent是ppNode的右
			{
				ppNode->_right = subR;//将subR连接到ppNode的右
			}

			subR->_parent = ppNode;//subR的父节点指向ppNode
		}

		subR->_bf = parent->_bf = 0;//左单旋后subR和parent的bf都是0
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;//左子树
		Node* subLR = subL->_right;//左子树的右子树

		int bf = subLR->_bf;

		RotateL(parent->_left);//先对左子树左旋
		RotateR(parent);//在对整个树进行右旋
		
		//重新更新平衡因子
		if (bf == 0)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 1;
		}
		else
		{
			assert(false);//正常不可能到这,这里是防止一开始就不是AVL
		}
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;//右子树
		Node* subRL = subR->_left;//右子树的左子树

		int bf = subRL->_bf;

		RotateR(parent->_right);//先对右子树右旋
		RotateL(parent);//在对整个树进行左旋

		//重新更新平衡因子
		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);//正常不可能到这,这里是防止一开始就不是AVL
		}
	}

	bool _IsBalance(const Node* root)
	{
		if (root == nullptr)//空树也平衡
			return true;

		int left = _Hight(root->_left);//求左子树的高度
		int right = _Hight(root->_right);//求右子树的高度

		if (abs(left - right) >= 2)//如果左右子树的高度差的绝对值差超过1则就是不平衡
			return false;

		if (right - left != root->_bf)//不平衡,打印出他的节点的key值
		{
			cout << root->_kv.first << endl;
			return false;
		}

		return _IsBalance(root->_left) && _IsBalance(root->_right);//左右子树都得平衡
	}

	int _Hight(Node* root)
	{
		if (root == nullptr)//空树的个数是0
			return 0;

		return max(_Hight(root->_left), _Hight(root->_right)) + 1;//不是空返回左右子树的较大值 + 本身
	}

	int _Size(const Node* root)
	{
		if (root == nullptr)//空树
			return 0;
		
		return _Size(root->_left) + _Size(root->_right) + 1;//左子树+右子树+本身
	}

private:
	Node* _root = nullptr;//这里只有一个成员可以给一个缺省值就不用写构造函数了
};

ok,本期分享就到这里,好兄弟。我们下期再见!

结束语:不要因为被人的三言两语就打破你原本的深思熟虑!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nnUNet保姆级使用教程!从环境配置到训练与推理(新手必看)

文章目录 写在前面nnUNet是什么&#xff1f;一、配置虚拟环境二、安装nnUNet框架1.安装nnUNet这一步我遇到的两个问题&#xff1a; 2.安装隐藏层hiddenlayer&#xff08;可选&#xff09; 三、数据集准备nnUNet对于你要训练的数据是有严格要求的&#xff0c;这第一点就体现在我…

pushowl | 运用分销裂变模式实现业绩快速增长

一、公司简介 PushOwl公司是一家专注于为电子商务商店提供在线营销工具的印度初创企业。以下是对PushOwl公司的详细介绍&#xff1a; 基本信息&#xff1a; 所属公司&#xff1a;Creatorbox Softwares Private Limited 成立日期&#xff1a;2018年 所属地&#xff1a;印度 …

103.网络游戏逆向分析与漏洞攻防-ui界面的设计-加速功能的开关设计

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

RDMA (1)

RDMA是什么 Remote Direct Memory Access(RDMA)是用来给有高速需求的应用释放网络消耗的。 RDMA在网络的两个应用之间进行低延迟,高吞吐的内存对内存的直接数据通信。 InfiniBand需要部署独立的协议。 RoCE(RDMA over Converged Ethernet),也是由InfiniBand Trade Associat…

【吊打面试官系列】Java高并发篇 - Java 线程数过多会造成什么异常?

大家好&#xff0c;我是锋哥。今天分享关于 【Java 线程数过多会造成什么异常&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Java 线程数过多会造成什么异常&#xff1f; 1、线程的生命周期开销非常高 1000道 互联网大厂Java工程师 精选面试题-Java资源分享…

postman教程-12-保存请求至Collections

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了Postman管理环境的方法&#xff0c;本小节我们讲解一下Postman保存请求至Collections集合的方法。 1、创建Collection 在保存Request请求之前&#xff0c;先创建一个Collection(集合)&#…

【网络编程开发】1.网络结构 2.IP地址与端口号 3.字节序

网络编程开发 两台计算机要互相传送文件需解决很多问题&#xff1a; 必须有一条传送数据的通路。发起方必须激活通路。要告诉网络如何识别接收方。发起方要清楚对方是否已开机&#xff0c;且与网络连接正常。发起方要清楚对方是否准备好接收和存储文件。若文件格式不兼容&…

藏品名称:龙凤呈祥摆件

藏品名称:龙凤呈祥摆件 规格:重约 14.3Kg 藏品类别:杂项 此器身布满繁缠纹饰。器表为一轮红日出于东方,照耀辽阔江海。红日旁边有两点黄金凸显其尊贵。一神龙首尾相接有祥云伴随,大山脚下栖息着一只凤凰与神龙遥相呼应。龙身和龙尾交接处有花和花蕊,花开富贵象征着吉祥。整个…

Esxi的安装问题处理: Failed to verify signatures of the following vib(s)

前言 在安装esxi的时候报错 如下图&#xff1a; 自己在安装过程中遇到点问题 Failed to verify signatures of the following vib(s) 一番查找&#xff0c;只要在bios里面关闭 Security boot 就可以解决 Prepping an ESXi 6.7 host for Secure Boot – Mike Foley

9.抽象类和接口

抽象类 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类 比如&#xff1a; 我…

Linux网络-自定义协议、序列化和反序列化、网络计算服务器的实现和Windows端客户端

文章目录 前言一、自定义协议传结构体对象 序列化和反序列化什么是序列化&#xff1f;反序列化 二、计算器服务端&#xff08;线程池版本&#xff09;1.main.cc2.Socket.hpp3.protocol.hpp4.Calculator.hpp5.serverCal.hpp6.threadPool.hpp7.Task.hpp8. log.hpp 客户端Windows客…

PS插件一键轻松搞定电商产品摄影图!

在电商行业中&#xff0c;一张高质量的产品摄影图往往能够吸引更多潜在消费者的目光&#xff0c;从而增加产品的销量。然而&#xff0c;对于许多电商卖家和摄影师来说&#xff0c;后期处理产品图片却是一个既耗时又费力的工作。 最近我发现一款PS插件可以一键生成电商产品摄影…

品牌舆情监测系统是什么?怎么监测?

品牌形象与口碑对于企业的重要性不言而喻&#xff0c;品牌舆情监测系统应运而生&#xff0c;成为企业守护品牌声誉的利器。品牌舆情监测系统是什么&#xff1f;怎么选择合适的舆情监测系统&#xff1f;接下来伯乐网络传媒就给大家分享一下。 一、品牌舆情监测系统的应用价值 1…

【一百零四】【算法分析与设计】【模板】二维差分,2132. 用邮票贴满网格图,LCP 74. 最强祝福力场,二位差分,差分思想,记录变化值,离散化技巧

【模板】二维差分 描述 给你一个n行m列的矩阵&#xff0c;下标从1开始。 接下来有q次操作&#xff0c;每次操作输入5个参数x1, y1, x2, y2, k 表示把以(x1, y1)为左上角,(x2,y2)为右下角的子矩阵的每个元素都加上k&#xff0c; 请输出操作后的矩阵。 输入描述&#xff1a; 第一…

读书-《蛤蟆先生去看心理医生》

书名蛤蟆先生去看心理医生作者罗伯特戴博德状态阅读中简介该书借用《柳林风声》的故事主角蛤蟆先生&#xff0c;讲述了他接受心理咨询的故事。作者通过陷入抑郁的蛤蟆先生和心理咨询师苍鹭的互动&#xff0c;探索蛤蟆先生爱炫耀、自卑性格和抑郁情绪的来源&#xff0c;指出童年…

二叉树的OJ题

1.二叉树的前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array must be malloced, assume caller calls free().*/int TreeeSize(struct Tre…

通过血清拉曼光谱进行COVID-19的高效初步筛查

通过血清拉曼光谱进行COVID-19的高效初步筛查 原创 小王搬运工 时序课堂 2024-06-04 20:04 四川 论文地址&#xff1a;https://analyticalsciencejournals.onlinelibrary.wiley.com/doi/full/10.1002/jrs.6080 论文源码&#xff1a;无 期刊&#xff1a;JOURNAL OF RAMAN SPE…

制作自己的 ButterKnife(使用 AutoService 和 APT 注解处理器在编译期生成 Java 代码)

ButterKnife 开发过 Android 的肯定都知道曾经有这么一个库&#xff0c;它能够让你不用再写 findViewById 这样的代码&#xff0c;这就是大名鼎鼎的 ButterKnife&#xff08;https://github.com/JakeWharton/butterknife&#xff09;。虽然现在这个库已经不再维护&#xff0c;…

C语言基础学习之链表与共同体

数组: 数据结构---操作时候的特点&#xff1a; 优势&#xff1a;随机访问(存取)方便 不足&#xff1a;插入数据删除数据不方便 链式数据结构--链表 struct stu sl; // s1struct stu s2; // s2struct stu s3; //s3 s1-->s2-->s3 特点: 优势:增力和删除数据方便劣势…

2024年手机能做的赚钱软件有哪些?整理了八个手机能做的正规赚钱软件分享

在这个指尖滑动的时代&#xff0c;手机不仅仅是通讯工具&#xff0c;更是我们探索财富的钥匙。你是否曾幻想过&#xff0c;躺在沙发上&#xff0c;轻轻一滑&#xff0c;就能让钱包鼓起来&#xff1f; 今天&#xff0c;就让我们一起来探索那些隐藏在手机里的赚钱秘笈&#xff0c…