18.AVL树的模拟实现

news2024/11/15 1:43:20

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

1. AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1.它的左右子树都是AVL
2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

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

image-20230320203650271

2. AVL树节点的定义

// 节点的类模板
template<class K, class V>
struct AVLTreeNode
{
    // 键对值的对象 _kv
	pair<K, V> _kv;
    // 左子树的根节点
	AVLTreeNode<K, V>* _left;
    // 右子树的根节点
	AVLTreeNode<K, V>* _right;
    // 父节点
	AVLTreeNode<K, V>* _parent;

	int _bf;  // balance factor:平衡因子

    // 节点的构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

3.insert

bool Insert(const pair<K, V>& kv)
{
    // 如果根节点_root,那么就创建新节点,并其赋值给_root
    // 返回真,表示插入成功
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

    // 将父亲节点初始化为空
	Node* parent = nullptr;
    // 将当前节点cur初始化为根节点
	Node* cur = _root;
    
    // 第一步:找到kv键值对要插入的位置
	while (cur)
	{
        // 1.cur节点对应的key值小于将要插入的key值
		if (cur->_kv.first < kv.first)
		{
            // 迭代
			parent = cur;
            // 向右子树走
			cur = cur->_right;
		}
        // 2.cur节点对应的key值大于将要插入的key值
		else if (cur->_kv.first > kv.first)
		{
            // 迭代
			parent = cur;
            // 向左走
			cur = cur->_left;
		}
		else
		{
            // 3.如果相等,根据二叉搜索树的性质(不允许出现重复的key值)
			return false;
		}
	}

    // 第二步:创建新节点,使用要插入的kv来初始化这个新节点
    // 1.parent是指向cur的,根据二叉搜索树的性质
	cur = new Node(kv);
    // 2.如果parent节点的key值小于要插入的key值,那么cur连接到parent的右子树
	if (parent->_kv.first < kv.first)
	{
        // 父节点指向cur
		parent->_right = cur;
        // cur指向父节点
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

    
	// 第三步:更新平衡因子
    // 当parent为空,也就更新到了整棵树的根节点
	while (parent) 
	{
        // 3.1 因为新添加了一个节点,所以要更新其对应parent的平衡因子
        // 平衡因子 = 右子树的高度 - 左子树的高度
        // 新增在右,parent->bf++;
        // 新增在左,parent->bf--;
        if (cur == parent->_left)
        {
            parent->_bf--;
        }
        else
        {
            parent->_bf++;
        }
        

		// 3.2 是否继续更新依据:子树的高度是否变化
        // 如下图所示
		// 情况1:更新后:parent->_bf == 0 
        // 说明插入节点之前 parent->_bf 是 1 或者 -1
		// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
        
		// 情况2、
        // 更新后:parent->_bf == 1 或 -1 
        // 说明插入节点之前是 parent->_bf == 0,两边一样高,现在插入一边更高了,parent所在子树高度变了,继续往上更新
        
		// 情况3、
        // 更新后:parent->_bf == 2 或 -2,
        // 说明之前parent->_bf == 1 或者 -1,现在插入节点严重不平衡,违反规则,就地处理--旋转
		// 旋转:
		// 1、让这颗子树左右高度差不超过1
		// 2、旋转过程中继续保持他是搜索树
		// 3、更新调整孩子节点的平衡因子
		// 4、让这颗子树的高度跟插入前保持一致
        
        
        // 3.3 迭代更新
        // 情况1:不需要继续往上更新,因此直接跳出循环就可以
		if (parent->_bf == 0)
		{
			break;
		}
        // 情况2:继续往上更新(情况2向上更新可能会变为情况3,也可能迭代到parent为空,跳出循环)
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
            // 向cur的父亲节点进行迭代
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 情况3:此时必须进行旋转了旋转
            // 3.1 当前节点cur的父节点的平衡因子为2,当前节点的平衡因子为1
			if (parent->_bf == 2 && cur->_bf == 1)
			{
				RotateL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == -1)
			{
                // 3.2
				RotateR(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;
		}
		else
		{
            // 如果不满足上面的三种情况,那么就会直接报错
			assert(false);
		}
	}

	return true;
}

image-20230321195312585

image-20240424164113178

3.1 旋转

​ 如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

左单旋

image-20230321150312986

思路:

  1. val为30的这个节点的右子树的根节点的val值必然大于30(根据二叉搜索树的性质),因此将val值为60的节点的左子树根节点链接到30的右子树

  2. 将30的根节点链接到60的左子树

  3. 这样就完成了左旋

void RotateL(Node* parent)
{
    // 第一步:
    // subR、subRL、parent的关系如上图所示
    // subR 是根节点的右子树的根节点
    // subRL 是根节点subR的左子树的根节点
    // parent 是整棵树的根节点
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

    // 1.将subR的左子树根节点连接到parent节点的右子树(完成单向连接)
	parent->_right = subRL;
    
    // 2.如果subRL不为空,则它的父指针需要指向parent整棵树的根节点(完成双向连接)
	if (subRL)
		subRL->_parent = parent; 

    // 第二步:
    // 提前保存整颗二叉树的根节点parent的父节点指针,保存为ppNode
	Node* ppNode = parent->_parent;
    
    // 将parent为根节点的树连接到subR的左子树(单向连接)
	subR->_left = parent;
    
    // 让parent的父节点的指针指向subR(完成双向连接)
	parent->_parent = subR;

    
	// 第三步:连接新树的根节点subR到ppNode
	if (ppNode == nullptr)
	{
        // 如果ppNode为空,说明parent之前的parent->parent为空
        // 此时经过旋转subR就是新树的根节点,所以根节点的父指针需要指向空
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
        //  说明parent之前不是根节点,那么就需要将其与原本的parent的上一层节点链接起来
        //  此时ppNode的左子树,或者右子树还指向parent
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}

        // subR的父节点也需要指向ppNode
		subR->_parent = ppNode;
	}
    
    // 第四步:更新平衡因子
	// 经过旋转之后,parent和subR的左右子树是平衡的,因此平衡因子为0
	parent->_bf = subR->_bf = 0;
}

右单旋

image-20230321155839673

//  思路与左单旋一致
void RotateR(Node* parent)
{
    // 第一步:
    // parent是整棵树的根节点
    // subL是parent左子树的根节点
	Node* subL = parent->_left;
    // subLR 是subL节点右子树的根节点
	Node* subLR = subL->_right;

    // 第二步:
    // 1.parent的left指针指向subLR(单向连接)
	parent->_left = subLR;
    
    // 2.subLR的父指针指向parent(完成双向连接)
	if (subLR)
	{
		subLR->_parent = parent;
	}

    // 第二步:
    // 1.保存parent的父指针为ppNode
	Node* ppNode = parent->_parent;
    // 2.subL是新树的根节点,subL的右指针,指向parent(单向连接)
	subL->_right = parent;
    // 3.parent的父指针指向subL(完成双向连接)
	parent->_parent = subL;

    
    // 第三步:将新树的根节点subL与ppNode完成双向连接
	//if (_root == parent)
	if (ppNode == nullptr)
	{
        // 如果ppNode为空,那么subL就是根节点,没有上层节点可以连接
		_root = subL;
        // 所以_root的父节点为空
		_root->_parent = nullptr;
	}
	else
	{
        // 如果ppNode->_left指向parent节点,那么ppNode左子树的根节点为subL(单向连接)
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}

        // subL的父指针指向ppNode(完成双向连接)
		subL->_parent = ppNode;
	}

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

先左单旋,再右单旋

image-20230321194942365

void RotateLR(Node* parent)
{
    // parent->_left 指向的就是左单旋的根节点
	Node* subL = parent->_left;
    // subL->_right 就是新增的节点的子树的根节点
	Node* subLR = subL->_right;
    
    // 最终判断 subL  subLR  parent的平衡因子,
    // 需要根据新增节点后的subLR->_bf(平衡因子)来判断,因此先保存,旋转后它(平衡因子)将会改变
	int bf = subLR->_bf;

    // 以parent->_left指向的节点为根节点进行左旋
	RotateL(parent->_left);
    // 以parent节点为根节点进行右旋
	RotateR(parent);

	if (bf == -1) 
	{
        // 情况1(更新平衡因子,如上图所示,如果是b新增节点,那么旋转后为情况1)
        // subLR左子树新增,经过左右双旋后的平衡因子
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}
	else if (bf == 1) 
	{
        // 情况2(更新平衡因子,如上图所示,如果是c新增节点,那么旋转后为情况2)
        // subLR右子树新增,经过左右双旋后的平衡因子
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0) 
	{
        // 情况3(更新平衡因子,如上图所示,如果h为0,val值为60的节点是新增节点,那么旋转后为情况3)
        // subLR节点本身就是新增节点,,经过左右双旋后的平衡因子
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

先右单旋,再左单旋

image-20230321211623928

// 思路与先左单旋,再右单旋一致
void RotateRL(Node* parent)
{
    // parent->_right 指向的就是右单旋的根节点
	Node* subR = parent->_right;
    // 新增节点所在子树的根节点为subR->_left
	Node* subRL = subR->_left;
    
    // 最终判断 subL  subLR  parent的平衡因子,
    // 需要根据新增节点后的subLR->_bf(平衡因子)来判断,因此先保存,旋转后它(平衡因子)将会改变
	int bf = subRL->_bf;
    
    // parent->_right 指向的就是右单旋的根节点
	RotateR(parent->_right);
    // parent是左单旋的根节点
	RotateL(parent);

	if (bf == 1)
	{
        // 情况1:b树新增节点时
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
        // 情况2:c树新增节点时
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
        // 情况3:h的高度为0时
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

4.中序遍历

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

5.AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

  1. 验证其为平衡树
  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确

平衡的验证

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


int Height(Node* root)
{
	if (root == nullptr)
		return 0;
	
    // 左子树的高度
	int lh = Height(root->_left);
    // 右子树的高度
	int rh = Height(root->_right);

    // 如果左子树的高度大于右子树的高度,那么左子树的高度+1,并返回,
    // 反之右子树的高度+1,并返回
    // 如果左右子树高度相等,那么返回0
	return lh > rh ? lh + 1 : rh + 1;
}

// 验证平衡,也就是验证左右子树的高度差的绝对值要小于2
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 <<"平衡因子异常" << endl;
		return false;
	}
	
    //  需要判断整颗树,和子树的(左右子树的)高度差的绝对值都是小于2的,因此要递归到每一个子树
	return abs(rightHeight - leftHeight) < 2
		&& IsBalance(root->_left)
		&& IsBalance(root->_right);
}

6.类模板

#pragma once
#include <assert.h>
#include <time.h>

// AVL树节点的类模板
template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf;  // balance factor

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


// AVL树的类模板
template<class K, class V>
struct AVLTree
{
    // 将AVL树节点的类模板类型定义为Node
	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;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		// 1、更新平衡因子
		while (parent) // parent为空,也就更新到根
		{
			// 新增在右,parent->bf++;
			// 新增在左,parent->bf--;
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			// 是否继续更新依据:子树的高度是否变化
			// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
			// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
			// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
			// parent所在子树高度变了,继续往上更新
			// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
			// 就地处理--旋转

			// 旋转:
			// 1、让这颗子树左右高度不超过1
			// 2、旋转过程中继续保持他是搜索树
			// 3、更新调整孩子节点的平衡因子
			// 4、让这颗子树的高度跟插入前保持一致
			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)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(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;
			}
			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* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;


		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

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

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

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		//if (_root == parent)
		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				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;

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

		if (bf == -1) // subLR左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1) // subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) // subLR自己就是新增
		{
			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(parent->_right);
		RotateL(parent);

		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 if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_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);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		
		int lh = Height(root->_left);
		int rh = Height(root->_right);

		return lh > rh ? lh + 1 : rh + 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 <<root->_kv.first<<"平衡因子异常" << endl;
			return false;
		}

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

private:
	Node* _root = nullptr;
};

7.验证用例

  • 常规场景1

{16, 3, 7, 11, 9, 26, 18, 14, 15}

  • 特殊场景2

{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}

image-20240424164158931

void TestAVLTree1()
{
	srand(time(0));
	const size_t N = 100000;
	AVLTree<int, int> t;
    
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
}

8.AVL树的性能

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

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

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

相关文章

阳光能源,创造永远:光模块的未来”:随着大数据、区块链、云计算和5G的发展,光模块成为满足不断增长的数据流量需求的关键技术

光模块的类型介绍&#xff1a; 为了适应不同的应用需求&#xff0c;不同参数和功能的光模块应运而生。光模块的分类方式及类型详见如下&#xff1a; &#x1f50e;封装形式&#x1f50d;&#xff1a; &#x1f4e3;&#x1f4e2;光模块按照封装形式来分有以下几种常见类型&a…

IPEmotion 2024 R1支持通过USB2ETH适配器连接外部调制解调器

新发布的IPEmotion 2024 R1增加了很多新功能&#xff0c;其中最重要的新功能包括&#xff1a;支持使用USB2ETH适配器连接外部调制解调器&#xff1b;用户自定义的制冷剂可在IPEmotion PC中使用&#xff1b;支持使用XML或JSON文件为IPEconverter定义复杂的转换任务。 — 创新成果…

Redis篇:缓存更新策略最佳实践

前景&#xff1a; 缓存更新是redis为了节约内存而设计出来的一个东西&#xff0c;主要是因为内存数据宝贵&#xff0c;当我们向redis插入太多数据&#xff0c;此时就可能会导致缓存中的数据过多&#xff0c;所以redis会对部分数据进行更新&#xff0c;或者把他叫为淘汰更合适&a…

mysql索引最左匹配原则的理解?(绝对牛逼)

前言 测试的时候就发现不对劲 CREATE TABLE student (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) DEFAULT NULL,cid int(11) DEFAULT NULL,PRIMARY KEY (id),KEY name_cid_INX (name,cid),KEY name_INX (name) ) ENGINEInnoDB AUTO_INCREMENT8 DEFAULT CHARSETut…

vue封装请求、合并js、合并多个js

vue封装请求、合并js、合并多个js 作为一个后端开发&#xff0c;写前端时发现&#xff0c;每次导入api接口都会有一堆代码&#xff0c;像下面这样&#xff1a; import {footprintList, footprintDelete} from /api/userApi.js import {addressList} from /api/userApi.js impor…

CPU资源控制

一、CPU资源控制定义 cgroups&#xff08;control groups&#xff09;是一个非常强大的linux内核工具&#xff0c;他不仅可以限制被namespace隔离起来的资源&#xff0c; 还可以为资源设置权重、计算使用量、操控进程启停等等。 所以cgroups&#xff08;control groups&#xf…

西圣、小米、倍思开放式耳机好用吗?详细测评对比性能王者

身为一名在数码科技领域有着丰富经验的测评师&#xff0c;我深入接触过各种开放式耳机。在众多开放式耳机品牌中&#xff0c;西圣、小米和倍思三款产品以其出色的性能和独特的设计&#xff0c;受到市场的广泛议论&#xff0c;今天我将为大家带来这三款开放式耳机的详细测评对比…

最新AI创作系统ChatGPT网站源码Midjourney-AI绘画系统,Suno-v3-AI音乐生成大模型。

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

Laravel 6 - 第十二章 控制器

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

javaScript基础3

javaScript 一.对象1.概念2.创建对象的三种方法(1).字面量创建&#xff08;利用{}&#xff09;(2)变量、属性、函数、方法的区别(3).new Object创建(4).构造函数 3.new关键字的执行过程4.遍历对象&#xff08;for..in) 二.内置对象1.了解2.math对象3.日期对象&#xff08;构造函…

挖矿木马基础知识

文章目录 一、概述二、挖矿介绍三、挖矿的收益四、挖矿木马的传播方式漏洞利用NSA武器的使用无文件挖矿利用网页挂马暴力挖矿病毒黑吃黑 五、防范建议六、学习参考 一、概述 比特币(Bitcoin)的概念最初由中本聪在 2008年11月1日提出&#xff0c;并于 2009年1月3日正式诞生。根…

Shell全套课程2小时速通从小白变高手

1.Shell概述 1.1为什么要学shell ​ 1.看懂运维人员编写的shell脚本 ​ 2.偶尔会编写一些简单的shell程序来管理集群&#xff0c;提高开发效率 1.2 Shell介于外层应用和LInux内核之间&#xff1b;用来操作Linux内核&#xff1b; Shell是一个命令行解释器&#xff0c;它接收…

算法课程笔记——如何进制转换

python特性 八、为什么负数的补码的求法是反码1 因为负数的反码加上这个负数的绝对值正好等于1111&#xff0c;在加1&#xff0c;就是10000&#xff0c;也就是四位二进数的模&#xff0c;而负数的补码是它的绝对值的同余数&#xff0c;可以通过模减去负数的绝对值得到它的补码&…

2024最新SSL证书在线申请系统源码 | 支持API接口 支持在线付费 二开优化版

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 2024最新SSL证书在线申请系统源码 | 支持API接口 支持在线付费 二开优化版 最新SSL证书在线申请系统源码 | 支持API接口 SSL证书保证网络安全的基本保障。向您介绍我们的在线生成SSL…

权威解析Spring框架九大核心功能(续篇):专业深度,不容错过

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c…

速度与激情:超高速--100G网卡篇

在数字化时代&#xff0c;信息传输的速度和效率成为了各个领域的关键。在这个快节奏的世界里&#xff0c;网络连接的快慢直接影响着工作效率、生活质量甚至是创新能力。而在网络连接技术中&#xff0c;网卡的作用举足轻重。近年来&#xff0c;随着网络技术的不断发展&#xff0…

路由引入,路由过滤,路由策略简单实验

实验要求&#xff1a; 1、按照图示配置 IP 地址&#xff0c;R1&#xff0c;R3&#xff0c;R4 上使用 1oopback 口模拟业务网段 2、R1 和 R2 运行 RIPv2&#xff0c;R2&#xff0c;R3和R4 运行 OSPF&#xff0c;各自协议内部互通 3、在 RIP 和 OSPF 间配置双向路由引入&#x…

在PostgreSQL中,如何创建一个触发器并在特定事件发生时执行自定义操作?

文章目录 解决方案示例代码1. 创建自定义函数2. 创建触发器 解释 在PostgreSQL中&#xff0c;触发器&#xff08;trigger&#xff09;是一种数据库对象&#xff0c;它能在特定的事件&#xff08;如INSERT、UPDATE或DELETE&#xff09;发生时自动执行一系列的操作。这些操作可以…

短期斩获多个访问学者邀请函|高校教师获批CSC赴伦敦大学学院

B老师申报的是2023年CSC西部/地方合作项目&#xff0c;因申报在即&#xff0c;所以时间是第一要素&#xff0c;国家定位在英国及澳大利亚。经过努力&#xff0c;我们先后获得英国布里斯托大学、伦敦大学学院及澳大利亚昆士兰大学等多个邀请函&#xff0c;最终其选择了英国伦敦大…

CSS学习(选择器、盒子模型)

1、CSS了解 CSS&#xff1a;层叠样式表&#xff0c;一种标记语言&#xff0c;用于给HTML结构设置样式。 样式&#xff1a;文字大小、背景颜色等 p标签内不能嵌套标题标签。 2、CSS编写位置 1、行内样式&#xff08;内联样式&#xff09;&#xff1a;在标签里添加样式&#…