C++:平衡搜索二叉树(AVL)

news2024/12/24 9:44:17

hello,各位小伙伴,本篇文章跟大家一起学习《C++:平衡搜索二叉树(AVL)》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !

文章目录

    • :maple_leaf:AVL树
    • :maple_leaf:AVL树节点的定义
      • :leaves:关于pair
    • :maple_leaf:AVL树的插入
    • :maple_leaf:AVL树的旋转
    • :maple_leaf:验证AVL是否平衡
    • :maple_leaf:AVL树的Find和Erase
    • :maple_leaf:AVL树的性能
    • :maple_leaf:AVL树实现的总代码

🍁AVL树

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

AVL树是具有以下性质的搜索二叉树:

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

那么可以得出:假设一颗AVL树有n个节点,那么其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

🍁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)
	{}
};
  1. 由于要保证左右子树高度之差的绝对值不超过1(-1/0/1),所以引用了平衡因子_bf来维护,平衡因子的计算为右子树高度减去左子树高度
  2. 因为AVL树会对节点进行旋转,所以引入了父节点指针_parent来维护

🍃关于pair

在C++中,pair是一个模板类,用于将两个值(通常是不同类型的值)组合成一个单元,称为键-值对。pair允许我们将两个值一起存储、传递和操作,非常适合需要成对操作的场景。

基本用法:
要使用pair,首先需要包含 <utility> 头文件,因为pair定义在这个头文件中。

但是在某些编译环境中,尤其是较新版本的C++标准中,pair可能会隐式地包含在一些其他标准头文件中,例如 <iostream>或 <map>。这种情况下,您可能会发现在不包含 <utility> 头文件的情况下也能使用pair。

#include <utility>
#include <iostream>

int main() {
    // 创建一个键-值对
    std::pair<int, std::string> student(1, "Alice");

    // 访问键和值
    std::cout << "ID: " << student.first << ", Name: " << student.second << std::endl;

    // 修改键和值
    student.first = 2;
    student.second = "Bob";

    std::cout << "ID: " << student.first << ", Name: " << student.second << std::endl;

    return 0;
}

pair还提供了成员函数用于访问其成员:

  • first:访问第一个元素(键)
  • second:访问第二个元素(值)

使用示例:
pair在STL中广泛使用,特别是在关联容器中(如map和multimap)存储键值对。例如,使用pair可以方便地在map中插入元素:

#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> studentMap;

    // 插入键-值对
    studentMap.insert(std::make_pair(1, "Alice"));
    studentMap.insert(std::make_pair(2, "Bob"));
    studentMap.insert(std::make_pair(3, "Charlie"));

    // 遍历并打印所有键-值对
    for (const auto& pair : studentMap) {
        std::cout << "ID: " << pair.first << ", Name: " << pair.second << std::endl;
    }

    return 0;
}

🍁AVL树的插入

AVL树本质上就是搜索二叉树,所以插入的规则和搜索二叉树是一样的,只不过多了一个步骤:调整平衡因子

先看代码:

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;

// 上述操作都与搜索二叉树基本一致

// 调整平衡因子
	while (parent)
	{
		if (cur == parent->_left)
		{
		// 如果插入的位置为父节点的左边,则parent->_bf--
			parent->_bf--;
		}
		else
		{
		// 如果插入的位置为父节点的右边,则parent->_bf++
			parent->_bf++;
		}
		
		if (parent->_bf == 0)
		{
		// 如果该结点_bf的值为0,则无需继续向上调整,直接break
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
		// 如果该结点_bf的值为1或者-1,则继续向上调整
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 如果该结点_bf的值为2或者-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)
			{
			// 双旋操作
				RotateRL(parent);
			}
			else
			{
			// 双旋操作
				RotateLR(parent);
			}
			break;
		}
		else
		{
			assert(0);
		}
	}

	return true;
}

🍁AVL树的旋转

当插入新节点后,AVL树不再平衡,就要进行旋转操作,AVL树的旋转分四种情况:

  1. 新节点插入较高左子树的左侧:右单旋
    在这里插入图片描述
    右旋实现代码:
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;

	// 关键点:当parentParent == nullptr,就要注意根节点的改变
	if (parentParent == nullptr)// 更改根节点
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (parent == parentParent->_left)
		{
			parentParent->_left = subL;
		}
		else
		{
			parentParent->_right = subL;
		}

		subL->_parent = parentParent;
	}
	parent->_bf = subL->_bf = 0;// 调整平衡因子
}
  1. 新节点插入较高右子树的右侧:左单旋
    那么左旋道理也是一样,直接看代码:
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 (parentParent == nullptr)// 更改根节点
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == parentParent->_left)
		{
			parentParent->_left = subR;
		}
		else
		{
			parentParent->_right = subR;
		}

		subR->_parent = parentParent;
	}

	parent->_bf = subR->_bf = 0;// 调整平衡因子
}
  1. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
    在这里插入图片描述

实现代码:

void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;// 记录旋转前的平衡因子

	RotateL(parent->_left);
	RotateR(parent);
	
	// 但是调整平衡因子就有点麻烦了
	// 3种情况
	if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == -1)
	{
		subL->_bf = -1;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
    实现代码:
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);
	}
}

总结:
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
  • 当subR的平衡因子为1时,执行左单旋
  • 当subR的平衡因子为-1时,执行右左双旋
  1. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
  • 当subL的平衡因子为-1是,执行右单旋
  • 当subL的平衡因子为1时,执行左右双旋
    旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。

🍁验证AVL是否平衡

int _Height(Node* pRoot)
{
	if (pRoot == nullptr)
	{
		return 0;
	}

	return _Height(pRoot->_left) > _Height(pRoot->_right) ? _Height(pRoot->_left) + 1 : _Height(pRoot->_right) + 1;
}

bool _IsBalanceTree(Node* pRoot)
{
	// 空树也是AVL树
	if (nullptr == pRoot) return true;

	// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
	int leftHeight = _Height(pRoot->_left);
	int rightHeight = _Height(pRoot->_right);
	int diff = rightHeight - leftHeight;
	// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
	// pRoot平衡因子的绝对值超过1,则一定不是AVL树
	if (diff != pRoot->_bf || (diff > 1 || diff < -1))
		return false;
	// pRoot的左和右如果都是AVL树,则该树一定是AVL树
	return _IsBalanceTree(pRoot->_left) && _IsBalanceTree(pRoot ->_right);

void test()
{
	cout<< _IsBalanceTree(_root) << endl;
}
}

创建好AVL树后,直接在主函数调用test()就可以了

🍁AVL树的Find和Erase

Find和搜索二叉树没什么区别:

Node* Find(const pair<K,V> kv)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			cur = cur->_left;
		}
		else
			return cur;
	}
	return nullptr;
}

Erase就有点麻烦了,删除后要保证平衡
举个例子:
在这里插入图片描述
要比25小,而且要比10大,很显然只需要寻找?节点左子树最大节点和右子树最小节点即可,那么我们选择左子树最大节点15
在这里插入图片描述
实现代码:
注意:下列代码并没有实现删除后平衡因子的调整!!!

bool Erase(const pair<K, V>kv)
{
	Node* cur = _root;
	Node* parent = nullptr;

	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// 找到后进行删除操作
		{	// 1、0个孩子
			if (cur->_left == nullptr)
			{
				if (parent == nullptr)
				{
					_root = cur->_right;
					return true;
				}

				if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
				
				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				if (parent == nullptr)
				{
					_root = cur->_left;
					return true;
				}

				if (parent->_left == cur)
					parent->_left = cur->_left;
				else
					parent->_right = cur->_left;

				delete cur;
				return true;
			}
			// 2个孩子
			else
			{	// 找右边最小的rightmin
				Node* rightminP = cur;
				Node* rightmin = cur->_right;

				while (rightmin->_left)
				{
					rightminP = rightmin;
					rightmin = rightmin->_left;
				}

				cur->_kv.first = rightmin->_kv.first;
				if (rightminP->_left == rightmin)
				{
					rightminP->_left = rightmin->_right;
					
				}
				else
				{	
					rightminP->_right = rightmin->_right;
					
				}
				
				delete rightmin;
				return true;
			}
		}
	}
	return false;
}

对于删除具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

🍁AVL树的性能

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

🍁AVL树实现的总代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

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

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

	AVLT() = default;

	/*AVLT(const AVLT<K, V> t)
	{
		_root = Copy(t._root);
	}*/

	AVLT& operator=(AVLT<K, V> t)
	{
		swap(_root, t._root);
		return *this;
	}

	~AVLT()
	{
		Destroy(_root);
		_root = nullptr;
	}

	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 (parentParent == nullptr)// 更改根节点
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}

			subR->_parent = parentParent;
		}

		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* parentParent = parent->_parent;

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

		if (parentParent == nullptr)// 更改根节点
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

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


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

	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 = 0;
			subLR->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == -1)
		{
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	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;

		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)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(0);
			}
		}

		return true;
	}

	Node* Find(const pair<K,V> kv)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				cur = cur->_left;
			}
			else
				return cur;
		}
		return nullptr;
	}

	bool Erase(const pair<K, V>kv)
	{
		Node* cur = _root;
		Node* parent = nullptr;

		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
			{	// 1、0个孩子
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
						return true;
					}

					if (parent->_left == cur)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;

					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
						return true;
					}

					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;

					delete cur;
					return true;
				}
				// 2个孩子
				else
				{	// 找右边最小的rightmin
					Node* rightminP = cur;
					Node* rightmin = cur->_right;

					while (rightmin->_left)
					{
						rightminP = rightmin;
						rightmin = rightmin->_left;
					}

					cur->_kv.first = rightmin->_kv.first;
					if (rightminP->_left == rightmin)
						rightminP->_left = rightmin->_right;
					else
						rightminP->_right = rightmin->_right;

					delete rightmin;
					return true;
				}
			}
		}
		return false;
	}

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* newRoot = new Node(root->_key, root->_value);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);

		return newRoot;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int _Height(Node* pRoot)
	{
		if (pRoot == nullptr)
		{
			return 0;
		}

		return _Height(pRoot->_left) > _Height(pRoot->_right) ? _Height(pRoot->_left) + 1 : _Height(pRoot->_right) + 1;
	}

	bool _IsBalanceTree(Node* pRoot)
	{
		// 空树也是AVL树
		if (nullptr == pRoot) return true;

		// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = _Height(pRoot->_left);
		int rightHeight = _Height(pRoot->_right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		// pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (diff != pRoot->_bf || (diff > 1 || diff < -1))
			return false;
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(pRoot->_left) && _IsBalanceTree(pRoot ->_right);
	}
	void test()
	{
		cout<< _IsBalanceTree(_root) << endl;
	}
private:

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

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

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

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}

	Node* _root = nullptr;
};

你学会了吗?
好啦,本章对于《C++:平衡搜索二叉树(AVL)》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!

如你喜欢,点点赞就是对我的支持,感谢感谢!!!

请添加图片描述

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

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

相关文章

Redis 7.x 系列【27】集群原理之通信机制

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2 节点和节点2.1 集群拓扑2.2 集群总线协议2.3 流言协议2.4 心跳机制2.5 节点握…

matlab仿真 数字信号载波传输(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第七 章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all M8; msg[1 4 3 0 7 5 2 6]; ts0.01; T1; %t0:ts:T; t0:ts:T-ts; %x0:ts:length(msg); x0:ts:length(msg)-ts; f…

爬虫 APP 逆向 ---> 粉笔考研

环境&#xff1a; 粉笔考研 v6.3.15&#xff1a;https://www.wandoujia.com/apps/1220941/history_v6031500雷电9 模拟器&#xff1a;https://www.ldmnq.com/安装 magisk&#xff1a;https://blog.csdn.net/Ruaki/article/details/135580772安装 Dia 插件 (作用&#xff1a;禁…

前端开发知识-vue

大括号里边放键值对&#xff0c;即是一个对象。 一、vue可以简化前端javascript的操作。 主要特点是可以实现视图、数据的双向绑定。 使用vue主要分为三个步骤&#xff1a; 1.javascript中引入vue.js 可以src中可以是vue的网址&#xff0c;也可以是本地下载。 2.在javasc…

地形材质制作(能使地面湿润)

如图&#xff0c;创建一个材质并写以下逻辑 Landscape Layer Blend节点能使在地形模式绘制中有三个选择&#xff0c;根据以上逻辑&#xff0c;Red是原材质,Green是绿色材质也就是草&#xff0c;Blue为水&#xff08;这个我认为比较重要&#xff09; Blue的颜色最好为这个 这个节…

董宇辉离职,我一点都不意外!只不过感觉来的太快

下面这张图&#xff0c;是我在半年多前写的一段随笔&#xff0c;没想到来的这么快&#xff01; 碰巧的是今天中午&#xff0c;在开发者群里有两位老铁自曝&#xff0c;本以为能公司干到老&#xff0c;但公司却不给机会&#xff0c;已经不在是公司员工了。 最近&#xff0c;晓衡…

一些关于颜色的网站

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 1、中国传统色 2、网页颜色选择器 3、渐变色网站 4、多风味色卡生成 5、波浪生成 6、半透明磨砂框 色卡组合

苍穹外卖01

0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 &#xff08;nginx.exe要在非中文的目录下&#xff09; 开启服务&#xff1a; start nginx 查看任务进程是否存在&#xff1a; tasklist /fi "imagename eq nginx.exe" 关闭ngi…

SAPUI5基础知识20 - 对话框和碎片(Dialogs and Fragments)

1. 背景 在 SAPUI5 中&#xff0c;Fragments 是一种轻量级的 UI 组件&#xff0c;类似于视图&#xff08;Views&#xff09;&#xff0c;但它们没有自己的控制器&#xff08;Controller&#xff09;。Fragments 通常用于定义可以在多个视图中重用的 UI 片段&#xff0c;从而提…

【数据结构--排序】

目录 一、排序概述1.1、排序的相关定义1.2、排序用到的结构与函数 二、常见排序算法2.1、冒泡算法&#xff08;交换顺序&#xff09;&#xff08;1&#xff09;算法&#xff08;2&#xff09;性能分析 2.2、简单选择排序&#xff08;1&#xff09;算法&#xff08;2&#xff09…

express连接mysql

一、 安装express npm install express --save二、express配置 //引入 const express require("express"); //创建实例 const app express(); //启动服务 app.listen(8081, () > {console.log("http://localhost:8081"); });三、安装mysql npm i m…

《昇思25天学习打卡营第6天|ResNet50图像分类》

写在前面 从本次开始&#xff0c;接触一些上层应用。 本次通过经典的模型&#xff0c;开始本次任务。这里开始学习resnet50网络模型&#xff0c;应该也会有resnet18&#xff0c;估计18的模型速度会更快一些。 resnet 通过对论文的结论进行展示&#xff0c;说明了模型的功能&…

第2章 编译SDK

安装编译依赖 sudo apt-get update sudo apt-get install clang-format astyle libncurses5-dev build-essential python-configparser sconssudo apt-get install repo git ssh make gcc libssl-dev liblz4-tool \ expect g patchelf chrpath gawk texinfo chrpath diffstat …

springboot促进高等教育可持续发展管理平台-计算机毕业设计源码36141

摘 要 随着全球对可持续发展的日益关注&#xff0c;高等教育作为培养未来领导者和创新者的摇篮&#xff0c;其在推动可持续发展中的角色日益凸显。然而&#xff0c;传统的高等教育管理模式在应对复杂多变的可持续发展挑战时&#xff0c;显得力不从心。因此&#xff0c;构建一个…

stm32入门-----USART串口实现数据包的接收和发送

目录 前言 数据包 1.HEX数据包 2.文本数据包 C编程实现stm32收发数据包 1.HEX数据包的收发 2.文本数据包的收发 前言 前面几期讲解了USART串口发送数据和接收数据的原理&#xff0c;那本期在前面的基础上学习stm32 USART串口发送和接收数据包。本期包括两个项目&a…

数据库作业四

1. 修改 student 表中年龄&#xff08; sage &#xff09;字段属性&#xff0c;数据类型由 int 改变为 smallint &#xff1a; ALTER TABLE student MODIFY Sage SMALLINT; 2. 为 Course 表中 Cno 课程号字段设置索引&#xff0c;并查看索引&#xff1a; ALTER TABLE…

Linux系统下非root用户自行安装的命令切换为root权限时无法使用,提示comman not found解决办法

今天在开发的时候遇上了一个问题就是要去我们数据平台中进行数据的提取&#xff0c;数据存储用的是minio&#xff0c;一个MinIO部署由一组存储和计算资源组成&#xff0c;运行一个或多个 minio server 节点&#xff0c;共同作为单个对象存储库。独立的MinIO实例由具有单个 mini…

多区域DNS以及主从DNS的搭建

搭建多域dns服务器&#xff1a; 搭建DNS多区域功能&#xff08;Multi-Zone DNS&#xff09;主要是为了满足复杂网络环境下的多样化需求&#xff0c;提高DNS服务的灵活性、可扩展性和可靠性。 适应不同网络环境&#xff1a; 在大型组织、跨国公司或跨地域服务中&#xff0c;网…

微服务安全——SpringSecurity6详解

文章目录 说明SpringSecurity认证快速开始设置用户名密码基于application.yml方式基于Java Bean配置方式 设置加密方式自定义用户加载方式自定义登录页面前后端分离认证认证流程 SpringSecurity授权web授权:基于url的访问控制自定义授权失败异常处理方法授权:基于注解的访问控制…

2024上半年热门网络安全产品和工具TOP10_wiz安全产品

今年上半年&#xff0c;利用生成式人工智能&#xff08;GenAI&#xff09;的网络安全工具继续激增。许多供应商正在利用GenAI的功能来自动化安全运营中心&#xff08;SOC&#xff09;的工作&#xff0c;特别是在自动化日常活动方面&#xff0c;如收集威胁信息和自动创建查询。 …