数据结构(高阶)—— AVL树

news2025/7/27 4:52:58

目录

一、AVL树的基本概念

二、AVL树的结点定义

三、AVL树的插入

四、AVL树的旋转

1. 右单旋

2. 左单旋

3. 右左双旋

4. 左右双旋 

五、AVL树的验证 

六、AVL树的性能

七、源代码 


一、AVL树的基本概念

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

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

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

        我们以 右树的高度 - 左树的高度 为例进行标注,只要所有的子树满足其绝对值不超过1,那么该二叉搜索树就是AVL树;

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(logN) ,搜索时间复杂度 O(logN)

二、AVL树的结点定义

        这里将AVL树中的结点定义为三叉链结构,并在每个结点当中引入平衡因子(右子树高度-左子树高度 <= 1)。除此之外,还需编写一个构造新结点的构造函数,由于新构造结点的左右子树均为空树,所以新构造结点的平衡因子初始设置为0即可。

        注意: 给每个结点增加平衡因子并不是必须的,只是实现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;
    
    //平衡因子 (规则:右子树-左子树<=1)
	int _bf; //blance factor 平衡因子 

    //构造函数
	AVLTreeNode(const pair<k, v>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

三、AVL树的插入

        AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的插入方法,找到待插入位置并插入到树中。
  2. 更新平衡因子,如果出现不平衡,则需要进行旋转。

以上图为例:

1. 按照二叉搜索树的方式插入新节点
  • 当新插入结点的key值 比 当前结点的key值小就插入到该结点的左子树。
  • 当新插入结点的key值 比 当前结点的key值大就插入到该结点的右子树。
  • 当新插入结点的key值 和 当前结点的key值相等就插入失败。

        当新增结点插入之前,每个结点的平衡因子都已经确定好了(是满足条件的);新增结点插入以后,它会影响从根结点到其自身这条路径上的平衡因子(有可能不满足条件),所以就需要我们从新增结点的父节点开始不断向上调整平衡因子(如:新增结点->6->7->5    这条路径的平衡因子

2. 调整节点的平衡因子
规定如下:
  • 新增结点如果在parent的右边,parent的平衡因子++
  • 新增结点如果在parent的左边,parent的平衡因子−−

每更新完一个结点的平衡因子后,都需要进行以下判断:

  • 如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子。
  • 如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了。
  • 如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。

如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子:

        只有0经过−−/++ 操作后会变成-1/1,说明新增结点的插入使得parent的左子树或右子树增高了,即改变了以parent为根结点的子树的高度,从而会影响parent的父结点的平衡因子,因此需要继续往上更新平衡因子。

-------------------------------------------------------------------------------------------------------------------------

如果parent的平衡因子等于0,表明无需继续往上更新平衡因子了:

        只有-1/1经过 ++/−− 操作后会变成0,说明新增结点插入到了parent左右子树当中高度较矮的一棵子树,插入后使得parent左右子树的高度相等了,此操作并没有改变以parent为根结点的子树的高度,从而不会影响parent的父结点的平衡因子,因此无需继续往上更新平衡因子。

 -------------------------------------------------------------------------------------------------------------------------

如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理:

        此时parent结点的左右子树高度之差的绝对值已经超过1了,不满足AVL树的要求,因此需要进行旋转处理。

         通过对上面的平衡因子分析来看,我们可以发现,当parent的平衡因子为-2/2时,已经反映出对于某个节点的左右子树高度差已经超过了1,就需要进行旋转,对于旋转又分为四种旋转,我们先进行简单的分析:

        我们假设新增结点为cur,父节点为parent,更新平衡因子的操作就是通过不断调整cur和parent的位置是更新平衡因子,就有如下操作

cur = parent;             //新增结点更新到其父节点的位置
parent = parent->_parent; //父节点更新到自己父节点的位置

先简单介绍一下四种情况,后面具体介绍:

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

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

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

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

以下是基本的代码结构:

bool Insert(const pair<k, v>& kv)
{
	if (_root == nullptr)//若AVL树为空树,则插入结点直接作为根结点
	{
		_root = new Node(kv);
		return true;
	}

    //按照二叉搜索树的插入规则,先找到正确的插入点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)//待插入结点的key值小于当前结点的key值
		{
            //往该结点的左子树走
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)//待插入结点的key值大于当前结点的key值
		{
            //往该结点的右子树走
			parent = cur;
			cur = cur->_left;
		}
		else  //待插入结点的key值等于当前结点的key值
		{
			return false;   //插入失败
		}
	}
	cur = new Node(kv);//直接new一个节点插入

	if (parent->_kv.first < kv.first)//新结点的key值大于parent的key值
	{
        //插入到parent的右边
		parent->_right = cur;
		cur->_parent = parent;
	}
	else  //新结点的key值小于parent的key值
	{
        //插入到parent的左边
		parent->_left = cur;
		cur->_parent = parent;
	}

	/*
        控制平衡
	    1.更新平衡因子---更新新增结点到根结点的祖先路劲
	    2.出现异常平衡因子,那么就需要旋转平衡处理
    */

	while (parent) //最坏的情况一路更新到根结点
	{
		if (cur == parent->_left)//cur插入在parent的左边
		{
			parent->_bf--;  //parent的平衡因子--
		}
		else   //cur插入在parent的右边
		{
			parent->_bf++;  //parent的平衡因子++
		}
         //判断是否更新结束或需要进行旋转
		if (parent->_bf == 0)//更新结束(新增结点把parent左右子树矮的那一边增高了,此时左右高度一致)
		{
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)//需要继续往上更新平衡因子
		{
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)//需要进行旋转(此时parent树已经不平衡了)
		{
			//旋转处理
			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);
			}
			break;//部分子树旋转完毕后高度和插入之前一样,不会影响上一层,可以直接break

		}
		else //说明插入,更新平衡因子之前,树中平衡因子就有问题了
		{			
			assert(false);
		}
	}
	return true; //插入成功
}

四、AVL树的旋转

1. 右单旋

        从下面的抽象图可以看出树是处于平衡的一种状态,当h变化时,所得到的的树是不同的,当h=2时当然不只有这一种情况,所以我们就不可能将所有情况都画出来一一分析,对于右单旋来说,是因为插入的结点导致原本平衡的树,其左子树变高了,所以我们就要试图改变右子树的高度来解决左子树和右子树的高度差 <= 1;那么就需要对其进行右单旋的操作。

        当我们在a这棵树插入一个结点的时候,就会导致该树左子树高了,分别对应到h=0/1/2的情况,都是类似的,这样子思考下来整体就容易理解了;

我们先对h=0/1/2这三种情况在a出进行插入节点,然后来进行右单旋

新节点插入较高左子树的左侧---左左:右单旋(抽象图旋转演示)

右单旋代码如下:

void RotateR(Node* parent)
{
    //首先确定好subL/subLR的位置
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;     //让父节点指向subLR

	if (subLR)
	{
		subLR->_parent = parent;//如果subLR不为空,我们需要维护三叉链的关系,确定好subLR的父节点
	}

	Node* parentParent = parent->_parent;//记录一下父节点的父节点
	subL->_right = parent;  //让subL的右子树指向父节点
	parent->_parent = subL; //让subL作为parent的父亲

	if (parent == _root)//这里表明parent原来是根,让subL作为新的根
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else//这里表明parent是这棵树的局部子树,parent可能是某个节点的左孩子或右孩子
	{
		if (parentParent->_left == parent)//如果parent是某个节点的左孩子
		{
			parentParent->_left = subL;//让subL成为新的左孩子
		}
		else
		{
			parentParent->_right = subL;//如果parent是某个节点的右孩子
		}
		subL->_parent = parentParent;//让subL成为新的右孩子
	}

	subL->_bf = parent->_bf = 0;//更新平衡因子
}

2. 左单旋

 左单旋和右单旋如出一辙

  

 我们先对h=0/1/2这三种情况在c出进行插入节点,然后来进行左单旋

新节点插入较高右子树的右侧---右右:左单旋(抽象图旋转演示)

左单旋代码如下: 

//左单选
void RotateL(Node* parent)
{
    //首先确定好subR/subRL的位置
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL; //让父节点指向subRL

	if (subRL)
	{
		subRL->_parent = parent;//如果subRL不为空,我们需要维护三叉链的关系,确定好subRL的父节点
	}

	Node* parentParent = parent->_parent;//记录一下父节点的父节点
	subR->_left = parent;   //让subR的左子树指向父节点
	parent->_parent = subR; //让subR作为parent的父亲

	if (parent == _root)//这里表明parent原来是根,让subR作为新的根
	{
		_root = subR;		
		subR->_parent = nullptr;
	}
	else //这里表明parent是这棵树的局部子树,parent可能是某个节点的左孩子或右孩子
	{
		if (parentParent->_left == parent)//如果parent是某个节点的左孩子
		{
			parentParent->_left = subR; //让subR成为新的左孩子
		}
		else  //如果parent是某个节点的右孩子
		{
			parentParent->_right = subR;
		}
		subR->_parent = parentParent; //让subR成为新的右孩子
	}
	subR->_bf = parent->_bf = 0;//更新平衡因子
}

3. 右左双旋

        从下面给出的抽象图可以看出,当h=0/1/2时,和左单旋给出的图是一样的,只不过刚刚是在90的右侧插入的(即:新节点插入较高右子树的右侧---右右:左单旋),那我们在90的左侧插入一个新结点(即:新节点插入较高右子树的左侧---左右:先右单旋再左单旋),此时单旋是解决不了问题的。

为什么一个单旋解决不了问题?

        从上面给出的旋转图可以看出,我们将90的右子树给到30的右边,然后将30给到90的左边,然后更新平衡因子,按照正常更新完毕后的状态,平衡因子应该都是0,但是此图还是一种不平衡的状态,所以单旋解决不了问题。

 我们先对h=0/1/2这三种情况在90左侧进行插入节点,然后来进行右左双旋

 新节点插入较高右子树的左侧---左右:先右单旋再左单旋(抽象图旋转演示)

 旋转步骤:

  1. 以subR为旋转点进行右单旋。
  2. 以parent为旋转点进行左单旋。
  3. 更新平衡因子。

右左双旋代码如下:

//右左双旋
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(subR);   //1、以subR为轴进行右单旋
	RotateL(parent); //2、以parent为轴进行左单旋

    //3、更新平衡因子
	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 if (bf == 0)
	{
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);//在旋转前树的平衡因子就有问题
	}
}

4. 左右双旋 

左右双旋和右左双旋如出一辙,这里就不做过多的演示

 左右双旋代码如下:

//左右双旋
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RotateL(subL);//1、以subL为旋转点进行左单旋
	RotateR(parent); //2、以parent为旋转点进行右单旋

    //更新平衡因子
	if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false); //在旋转前树的平衡因子就有问题
	}
}

五、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);
}

        对于中序遍历只能保证它是二叉搜索树,要保证该二叉搜索树是否平衡还需要判断其平衡因子是否正确;

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

bool _IsBalance(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	//对当前树进行检查
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

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

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

六、AVL树的性能

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

七、源代码 

AVLTree.h

#pragma once

#include <iostream>
#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; //blance factor 平衡因子 (规则:右子树-左子树<=1)
	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:
	AVLTree()
		:_root(nullptr)
	{}
	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;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//控制平衡
		//1.更新平衡因子---更新新增结点到根结点的祖先路劲
		//2.出现异常平衡因子,那么就需要旋转平衡处理
		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 || parent->_bf == -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);
				}
				break;//部分子树旋转完毕后高度和插入之前一样,不会影响上一层,可以直接break

			}
			else//说明插入,更新平衡因子之前,树中平衡因子就有问题了
			{			
				assert(false);
			}
		}
			return true;
	}
	//左单选
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)//这里表明原来是根
		{
			_root = subR;		
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
		subR->_bf = parent->_bf = 0;
	}

	//由单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* parentParent = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;
		if (parent == _root)//这里表明原来是根
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}
		subL->_bf = parent->_bf = 0;
	}
	//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(subL);
		RotateR(parent);
		if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(parent);
		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 if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	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);
	}

	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 << root->_kv.first << "现在是:" << root->_bf << endl;
			cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
			return false;
		}
		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	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;
	}


private:
	Node* _root;
};

void test1()
{
	AVLTree<int, int> t;
	//int a[] = { 5,4,3,2,1,0 };
	//int a[] = { 16,3,7,11,9,26,18,14,15 };
	int a[] = { 4,2,6,1,3,5,15,7,16,14 };
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
		cout << "Insert" << e << ":" <<  t.IsBalance() << endl;
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

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

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

相关文章

CXL 2.0 Device配置空间寄存器组成

目录 1 配置空间 1.1 PCI Power Management Capability Structure 1.2 PCI Express Capability Structure 2 扩展配置空间 2.1 PCIe DVSEC for CXL Device 2.2 GPF DVSEC for CXL Devices 2.3 PCIe DVSEC for Flex Bus Port 2.4 Register Locator DVSEC CXL设备配置空间…

ThinkPHP架构

文章目录一、架构总览1.1、有关常用的一些概念入口文件应用模块控制器操作模型视图驱动行为命名空间【全限定类名】1.补充二、生命周期三、入口文件四、URL访问五、模块设计六、命明空间七、自动加载八、Traits引入九、API友好一、架构总览 ThinkPHP5.0应用基于MVC&#xff08;…

前后端分页插件

PageHelper 是一个 MyBatis 的分页插件,支持多种数据库,可查看官网&#xff0c;负责将已经写好的 SQL 语句&#xff0c;进行SQL分页加工。无需你自己去封装以及关心 SQL 分页等问题&#xff0c;支持多种分页方式,如从第0或第一页开始, 使用很方便。 添加依赖 <dependency&…

线代 | 【提神醒脑】自用笔记串联二 —— 向量组 · 线性方程组 · 特征值与特征向量

本文总结参考于 kira 2023 线代提神醒脑技巧班。 笔记均为自用整理。加油!ヾ(◍∇◍)ノ゙ 四、向量组 4.1、向量组的线性相关性 ----------------------------------------------------------------------------------------------------------…

Linux 软链接 与 硬链接 的区别

Linux 软链接 与 硬链接 的区别 1、概念 ​  链接文件&#xff1a;是 Linux 操作系统中的一种文件&#xff0c;主要用于解决文件的共享使用问题&#xff0c;而链接的方式分为两种——软链接和硬链接。 ​  inode&#xff1a;是文件系统中存储文件元信息&#xff08;文件的…

Auddly Music Server的编译和安装

本文始于 2021 年 11 月&#xff0c;已经忘记了是什么原因一直没发&#xff0c;这次基本上全部重写了一遍&#xff0c;除了官方的图&#xff0c;所有图片都是重新截取的&#xff1b; 什么是 auddly &#xff1f; auddly 是一款自托管音乐流应用程序。 什么是 auddly-server &am…

模拟实现ATM系统——Java

目录 一、内容简介 二、基本流程 三、具体步骤 1.定义Account类 2.菜单栏 3.账户注册 (1)根据卡号查询账户信息 (2)生成随机卡号 (3)注册账户 4.账户登录 (1)验证码 (2)登录 5.账户展示界面 6.用户操作 (1)查询账户 (2)存款 (3)取款 (4)转账 (5)修改密码 …

旋转的骰子(二)

1.动画——旋转的骰子 上一次我们做了一个旋转的骰子(参看第2讲),这次我们想要点击按钮,让骰子旋转到特定的点数停下来! 2.分析需求——庖丁解牛 a.立方体特定的朝向

LiveData源码分析

先放整理流程图&#xff1a; 1.postValue调2次只触发1次&#xff1f; postValue本质是把新值保存到LiveData的mPendingData成员变量里&#xff0c;版本号1&#xff0c;把执行Runnable post到主线程&#xff0c;在主线程setValue。 多次调用会更新mPendingData的值&#xff0c…

opencv的极线几何

一、理论介绍 当我们使用针孔相机拍摄图像时&#xff0c;我们会丢失一个重要的信息&#xff0c;即图像的深度。一个解决方案如我们的眼睛的方式使用两个相机&#xff08;两只眼睛&#xff09;&#xff0c;这就是所谓的立体视觉。 PO1O2为极平面&#xff0c;l1和l2为极线,e1和…

基于webrtc的数据传输研究总结

什么是webrtc WebRTC (Web Real-Time Communications) 是一项实时通讯技术&#xff0c;它允许网络应用或者站点&#xff0c;在不借助中间媒介的情况下&#xff0c;建立浏览器之间点对点&#xff08;Peer-to-Peer&#xff09;的连接&#xff0c;实现视频流和&#xff08;或&…

最新阿里云ECS服务器S6/C6/G6/N4/R6/sn2ne/sn1ne/se1ne处理器CPU性能详解

阿里云ECS服务器S6/C6/G6/N4/R6/sn2ne/sn1ne/se1ne处理器CPU性能怎么样&#xff1f;阿里云服务器优惠活动机型有云服务器S6、计算型C6、通用型G6、内存型R6、云服务器N4、云服务器sn2ne、云服务器sn1ne、云服务器se1ne处理器CPU性能详解及使用场景说明。 1、阿里云服务器活动机…

全局唯一ID

文章目录前言MongoDB ObjectIdTwitter SnowflakeUUID前言 基于数据库设置其实初始值&#xff0c;以及增量步长。基于ZK,Redis,改良雪花集中式服务生成&#xff0c;远程调用获取id。基于并行空间划分&#xff0c;Snowflake&#xff08;8Byte字节64bit位&#xff09;&#xff0c…

供应化学试剂mPEG-Azide,mPEG-N3,甲氧基-聚乙二醇-叠氮,CAS:89485-61-0

1、名称 英文&#xff1a;mPEG-Azide&#xff0c;mPEG-N3 中文&#xff1a;甲氧基-聚乙二醇-叠氮 2、CAS编号&#xff1a;89485-61-0 3、所属分类&#xff1a;Azide PEG Methoxy PEG 4、分子量&#xff1a;可定制&#xff0c;甲氧基-聚乙二醇-叠氮 5k、甲氧基-PEG-叠氮 10…

Higress 实战: 30行代码写一个 Wasm Go 插件

前言 在11月15号的直播 《Higress 开源背后的发展历程和上手 Demo 演示》中&#xff0c;为大家演示了 Higress 的 Wasm 插件如何面向 Ingress 资源进行配置生效&#xff0c;本文对当天的 Demo 进行一个回顾&#xff0c;并说明背后的原理机制。 本文中 Demo 运行的前提&#x…

PPOCR车牌定位模型推理后处理优化研究

综述 最近在研究基于PPOCR算法的车牌识别&#xff08;LPR&#xff09;&#xff0c;部署模型后发现之前关于OCR文本定位的后处理策略在车牌识别中存在定位精度不够高&#xff0c;文本框偏移的问题&#xff0c;如&#xff1a; 经分析发现是之前的OCR后处理策略存在一定局限&am…

java刷题day 03

选择题&#xff1a; 解析&#xff1a; 父类private的成员变量&#xff0c;根据权限修饰符的访问控制范围&#xff0c;只有在类内部才能被访问&#xff0c;就算是他的子类&#xff0c;也不能访问。这里如果将Person p new Child();改成 Person p new Person();代码依然无法通过…

乐趣国学—品读《弟子规》中的“余力学文”之道

文章目录余力学文不力行 但学文 长浮华 成何人但力行 不学文 任己见 昧理真读书法 有三到 心眼口 信皆要方读此 勿慕彼 此未终 彼勿起宽为限 紧用功 工夫到 滞塞通心有疑 随札记 就人问 求确义房室清 墙壁净 几案洁 笔砚正墨磨偏 心不端 字不敬 心先病列典籍 有定处 读看毕 还原…

信号完整性测试,关于SMA装配的细节,很多人都忽视了

作者 | 萧隐君&#xff0c;仿真秀专栏作者 SMA转接头是射频微波、天线和高速电路测试经常用到的一种连接器&#xff0c;应用非常广泛&#xff0c;种类也很多。在信号完整性的测试夹具中&#xff0c;2.92mm的SMA用的较多&#xff0c;它的带宽可以到40GHz&#xff0c;对于25Gbps…

【全网独家,收藏吧】10年全部《信息资源管理》真题整理,第2章 信息化规划与组织

文章目录&#x1f525; 11 年 4 月《信息资源管理》真卷选择题名词解释综合分析题&#x1f525; 11 年 7 月《信息资源管理》真卷选择题名词解释题简答题⭐ 12 年 4 月《信息资源管理》真卷选择题简答题⭐ 12 年 7 月《信息资源管理》真卷选择题简答题⭐ 13 年 4 月《信息资源管…