C++从入门到起飞之——红黑树 全方位剖析!

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1. 红⿊树的概念

2. 红⿊树的实现

2.1 构建整体框架

 2.2 红黑树的插入

 2.3 红黑树的验证

 2.4 红黑树的其他简单接口

3、红黑树完整源码

4、完结散花


1. 红⿊树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路 径⻓出2倍,因⽽是接近平衡的。

>红⿊树的规则:

1. 每个结点不是红⾊就是⿊⾊

2. 根结点是⿊⾊的

3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的 红⾊结点。

4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

 说明:《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦结点 不是传统的意义上的叶⼦结点,⽽是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了 ⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道 ⼀下这个概念即可。

2. 红⿊树的实现

2.1 构建整体框架

枚举类型定义红黑色:

//枚举类型定义颜色
enum Colour
{
	RED,
	BLACK
};

红黑树每个节点的结构: 

//节点结构(默认存储pair类型的key/val结构)
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		,_col(RED)
	{}
	pair<K, V> _kv;
	RBTreeNode* _parent;
	RBTreeNode* _left;
	RBTreeNode* _right;
	Colour _col;//初始化为红色
};

红黑树整体结构

//红黑树
template<class K,class V>
class RBTree
{
	typedef RBTreeNode Node;
public:
       //各种接口的实现

private:
	Node* _root=nullptr;
};

 2.2 红黑树的插入

1、红黑树是特殊的二叉搜索数,所以我们进行插入时,还是先按照二叉搜索数的规则进行插入!

//如果树为空,在根插入并且颜色为黑色
if (_root == nullptr)
{
	_root = new Node(kv);
	_root->_col = BLACK;
	return true;
}
//树不为空按搜索树规则先进行插入
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
	if (kv.first < cur->_kv.first)//小往左走
	{
		parent = cur;
		cur = parent->_left;
	}
	else if (kv.first > cur->_kv.first)//大往右走
	{
		parent = cur;
		cur = parent->_right;
	}
	else
	{
		return false;//不支持相同元素的插入
	}
}
cur = new Node(kv);
cur->_col = RED;
if (kv.first < parent->_kv.first)//K小插入在左边
	parent->_left = cur;
else//K大插入在右边
	parent->_right = cur;
cur->_parent = parent;

2、插入成功后,我们就要维护红黑树的规则了。 

>如果插入节点的父亲是黑色,那插入后依然符合红黑树的规则,我们不用处理。

>如果插入节点的父亲是红色,那么此时就有连续的红色节点,我们就要进行处理。

        a、插入节点的叔叔存在且为红,我们只需要进行变色。

变色原理:因为parent的颜色为红,所以g的颜色一定为黑。插入前从g开始的路径的黑色节点的数量相同,要解决连续红色节点的问题,我们必然要把parent变黑色。但从g到cur路径的黑色节点多了一个,所以我们把g变红,在把u变黑就完美的解决了问题!

不过,g变红后,又可能会引起祖先节点的连续红色节点问题,所以我们还要继续向上维护红黑树的规则!

变色代码实现

//插入后进行维护红黑树规则的逻辑
//parent存在且为红
while (parent&& parent->_col = RED)
{
	Node* grandfather = parent->_parent;
	//p在g的右边
	if (parent == grandfather->_right)
	{
		//g
	//u		p
		Node* uncle = grandfather->_left;
		if (uncle&& uncle->_col = RED)//uncle存在且为红
		{
			//变色处理
			uncle->_col = parent->_col = BLACK;
			grandfather->_col = RED;
			//更新cur继续向上处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else//uncle不存在或者存在且为黑
		{

		}
	}
	else//p在g的左边
	{
		//g
	//p		u
		Node* uncle = grandfather->_left;
		if (uncle&& uncle->_col = RED)//uncle存在且为红
		{
			//变色处理
			uncle->_col = parent->_col = BLACK;
			grandfather->_col = RED;
			//更新cur继续向上处理
			cur = grandfather;
			parent = cur->_parent;
		}
		else//uncle不存在或者存在且为黑
		{

		}
	}
}

        b、如果uncle不存在或者uncle存在且为黑,这时我们就要进行旋转加变色处理。

单旋+变色:

>如果uncle不存在,说明c一定是新增节点,如果c是变色后的节点,那么它在变色前一定是黑色,而从g开始的路径到c就多一个黑色节点!

 >如果uncle存在且为黑,说明c一定是变色后的节点,如果c是新增的节点,那么从g开始的路径到u就多一个黑色节点!

 变色原理:我们根据c、p、g的位置来选择合理的旋转逻辑,然后再把p变黑,g变红即可解决问题!

双旋+变色:

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则 c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上 来的。

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解 决问题,需要旋转+变⾊。

如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且 不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且 不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

 插入完整代码:

bool insert(const pair<K, V>& kv)
{
	//如果树为空,在根插入并且颜色为黑色
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}
	//树不为空按搜索树规则先进行插入
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first < cur->_kv.first)//小往左走
		{
			parent = cur;
			cur = parent->_left;
		}
		else if (kv.first > cur->_kv.first)//大往右走
		{
			parent = cur;
			cur = parent->_right;
		}
		else
		{
			return false;//不支持相同元素的插入
		}
	}
	cur = new Node(kv);
	cur->_col = RED;
	if (kv.first < parent->_kv.first)//K小插入在左边
		parent->_left = cur;
	else//K大插入在右边
		parent->_right = cur;
	cur->_parent = parent;

	//插入后进行维护红黑树规则的逻辑
	//parent存在且为红
	while (parent&& parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		//p在g的右边
		if (parent == grandfather->_right)
		{
			//g
		//u		p
			Node* uncle = grandfather->_left;
			if (uncle&& uncle->_col == RED)//uncle存在且为红
			{
				//变色处理
				uncle->_col = parent->_col = BLACK;
				grandfather->_col = RED;
				//更新cur继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle不存在或者存在且为黑
			{
				if (cur == parent->_right)
				{
					//g
				//u		p
				//		   c
				//以g为旋转点进行左单旋
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//g
				//u		p
				//	  c
				//进行右左双旋
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
				break;
			}
		}
		else//p在g的左边
		{
			//g
		//p		u
			Node* uncle = grandfather->_left;
			if (uncle&& uncle->_col == RED)//uncle存在且为红
			{
				//变色处理
				uncle->_col = parent->_col = BLACK;
				grandfather->_col = RED;
				//更新cur继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle不存在或者存在且为黑
			{
				if (cur == parent->_left)
				{
					//g
				//p		u
			//c
				//以g为旋转点进行右单旋
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//g
				//p		u
			//		c
				//进行左右双旋
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
				break;
			}
		}
	}
	//如果持续更新变色到根
	_root->_col = BLACK;
	return true;
}

 2.3 红黑树的验证

>检查每条路径的黑色节点是否相等,是否有连续的红色节点

//检查每条路径的黑色节点是否相等,是否有连续的红色节点
bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历⾛到空时,意味着⼀条路径⾛完了 
		//cout << blackNum << endl;
		if (refNum != blackNum)
		{
			cout << "存在黑色结点的数量不相等的路径" << endl;
			return false;
		}
		return true;
	}

	// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了 
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色节点" << endl;
		return false;
	}
	if (root->_col == BLACK)
	{
		blackNum++;
	}
	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}

>检查平衡 

//检查平衡
bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == RED)
		return false;

	// 参考值 
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refNum;
		}
		cur = cur->_left;
	}
	return Check(_root, 0, refNum);
}

 >插入一万个随机数看看是否平衡

void testInert()
{
	const int N = 10000;
	RBTree<int, int> t;
	vector<int> v;
	srand((unsigned int)time(nullptr));
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}
	for (auto e : v)
	{
		t.insert({ e,e });
	}
	cout << t.IsBalance() << endl;
}

 2.4 红黑树的其他简单接口

//默认构造
RBTree() = default;
//拷贝构造
RBTree(const RBTree<K,V>& rbt)
{
	_root=_copy(rbt._root);
}
// 赋值重载
RBTree<K, V>& operator=(RBTree<K, V> tmp)
{
	std::swap(_root, tmp._root);
	return *this;
}
//中序遍历
void InOrder()
{
	_InOrder(_root);
}
//二叉树的析构
~RBTree()
{
	_Destroy(_root);
}
private:
//递归拷贝
Node* _copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* newNode = new Node(root->_kv);
	newNode->_left = _copy(root->_left);
	newNode->_right = _copy(root->_right);
	return newNode;
}
//中序遍历
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << "<" << root->_kv.first << "," << root->_kv.second << ">" << endl;
	_InOrder(root->_right);
}
//二叉树的销毁
void _Destroy(Node* root)
{
	if (root == nullptr)
		return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
}

3、红黑树完整源码

 

#pragma once
#include<iostream>
using namespace std;
//枚举类型定义颜色
enum Colour
{
	RED,
	BLACK
};
//节点结构(默认存储pair类型的key/val结构)
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		,_col(RED)
	{}
	pair<K, V> _kv;
	RBTreeNode* _parent;
	RBTreeNode* _left;
	RBTreeNode* _right;
	Colour _col;//初始化为红色
};

//红黑树
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
public:
	//默认构造
	RBTree() = default;
	//拷贝构造
	RBTree(const RBTree<K,V>& rbt)
	{
		_root=_copy(rbt._root);
	}
	// 赋值重载
	RBTree<K, V>& operator=(RBTree<K, V> tmp)
	{
		std::swap(_root, tmp._root);
		return *this;
	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
	}
	//二叉树的析构
	~RBTree()
	{
		_Destroy(_root);
	}
	
	//红黑树的查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	//红黑树的插入
	bool insert(const pair<K, V>& kv)
	{
		//如果树为空,在根插入并且颜色为黑色
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		//树不为空按搜索树规则先进行插入
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first < cur->_kv.first)//小往左走
			{
				parent = cur;
				cur = parent->_left;
			}
			else if (kv.first > cur->_kv.first)//大往右走
			{
				parent = cur;
				cur = parent->_right;
			}
			else
			{
				return false;//不支持相同元素的插入
			}
		}
		cur = new Node(kv);
		cur->_col = RED;
		if (kv.first < parent->_kv.first)//K小插入在左边
			parent->_left = cur;
		else//K大插入在右边
			parent->_right = cur;
		cur->_parent = parent;

		//插入后进行维护红黑树规则的逻辑
		//parent存在且为红
		while (parent&& parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//p在g的右边
			if (parent == grandfather->_right)
			{
				//g
			//u		p
				Node* uncle = grandfather->_left;
				if (uncle&& uncle->_col == RED)//uncle存在且为红
				{
					//变色处理
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//更新cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑
				{
					if (cur == parent->_right)
					{
						//g
					//u		p
					//		   c
					//以g为旋转点进行左单旋
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//g
					//u		p
					//	  c
					//进行右左双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
					break;
				}
			}
			else//p在g的左边
			{
				//g
			//p		u
				Node* uncle = grandfather->_right;
				if (uncle&& uncle->_col == RED)//uncle存在且为红
				{
					//变色处理
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					//更新cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑
				{
					if (cur == parent->_left)
					{
						//g
					//p		u
				//c
					//以g为旋转点进行右单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//g
					//p		u
				//		c
					//进行左右双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转+变色后链接祖先节点的节点为黑,必然不会发生连续红色节点的情况直接break;
					break;
				}
			}
		}
		//如果持续更新变色到根
		_root->_col = BLACK;
		return true;
	}
	//检查平衡
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col == RED)
			return false;

		// 参考值 
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}
			cur = cur->_left;
		}
		return Check(_root, 0, refNum);
	}

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

		parent->_left = subLR;
		if (subLR)//如果不为空
			subLR->_parent = parent;

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

		if (pParent == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
			subL->_parent = pParent;
		}
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* pParent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		if (pParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pParent->_left == parent)
			{
				pParent->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
			subR->_parent = pParent;
		}
	}
	//检查每条路径的黑色节点是否相等,是否有连续的红色节点
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			// 前序遍历⾛到空时,意味着⼀条路径⾛完了 
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色结点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}

		// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了 
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}
		if (root->_col == BLACK)
		{
			blackNum++;
		}
		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}
	//递归拷贝
	Node* _copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newNode = new Node(root->_kv);
		newNode->_left = _copy(root->_left);
		newNode->_right = _copy(root->_right);
		return newNode;
	}
	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << "<" << root->_kv.first << "," << root->_kv.second << ">" << endl;
		_InOrder(root->_right);
	}
	//二叉树的销毁
	void _Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

private:
	Node* _root=nullptr;
};

4、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​

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

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

相关文章

解决JAVA使用@JsonProperty序列化出现字段重复问题(大写开头的字段重复序列化)

文章目录 引言I 解决方案方案1:使用JsonAutoDetect注解方案2:手动编写get方法,JsonProperty注解加到方法上。方案3:首字母改成小写的II 知识扩展:对象默认是怎样被序列化?引言 需求: JSON序列化时,使用@JsonProperty注解,将字段名序列化为首字母大写,兼容前端和第三方…

万字图文实战:从0到1构建 UniApp + Vue3 + TypeScript 移动端跨平台开源脚手架

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f343; vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f…

使用 NumPy 和 Matplotlib 实现交互式数据可视化

使用 NumPy 和 Matplotlib 实现交互式数据可视化 在数据分析中&#xff0c;交互式可视化可以更好地帮助我们探索和理解数据。虽然 Matplotlib 是静态绘图库&#xff0c;但结合一些技巧和 Matplotlib 的交互功能&#xff08;widgets、event handlers&#xff09;&#xff0c;我…

Git创建和拉取项目分支的应用以及Gitlab太占内存,如何配置降低gitlab内存占用进行优化

一、Git创建和拉取项目分支的应用 1. 关于git创建分支&#xff0c; git创建分支&#xff0c;可以通过git管理平台可视化操作创建&#xff0c;也可以通过git bash命令行下创建&#xff1a; A. 是通过git管理平台创建&#xff1a; 进入gitlab管理平台具体的目标项目中&#xff…

mac电脑设置chrome浏览器语言切换为日语英语等不生效问题

在chrome中设置了语言&#xff0c;并且已经置顶了&#xff0c;但是不生效&#xff0c;在windows上直接有设置当前语言为chrome显示语言&#xff0c;但是mac上没有。 解决办法 在系统里面有一个单独给chrome设置语言的&#xff1a; 单独给它设定成指定的语言&#xff0c;然后重…

Find My平板键盘|苹果Find My技术与键盘结合,智能防丢,全球定位

‌平板键盘的主要用途包括提高输入效率、支持轻量化办公、提供丰富的文本编辑功能以及快捷操作。相比于直接在屏幕上打字&#xff0c;使用键盘可以显著提升输入速度&#xff0c;减少输入错误&#xff0c;特别是对于需要大量文字输入的场景&#xff0c;如写作、记录笔记等‌。平…

如何在算家云搭建GPT-SOVITS(语音转换)

一、模型介绍 GPT-SOVITS是一款强大的小样本语音转换和文本转语音 WebUI工具。它集成了声音伴奏分离、自动训练集分割、中文ASR和文本标注等辅助工具。 具有以下特征&#xff1a; 零样本 TTS&#xff1a; 输入 5 秒的声音样本并体验即时文本到语音的转换。少量样本 TTS&…

【linux网络编程】| 网络基础 | 解析IP与Mac地址的区别

前言&#xff1a;本节内容讲解一些网络基础相关的知识点&#xff0c; 不涉及网络代码&#xff01;同样的本节内容是作为前一篇的补充知识点&#xff0c; 前一篇文章地址&#xff1a;【linux网络编程】 | 网络基础Ⅰ| 认识网络-CSDN博客&#xff0c;本篇文章内容较少&#xff0c…

Unreal Engine5安装Niagara UI Renderer插件

系列文章目录 文章目录 系列文章目录前言一、如何下载安装Niagara UI Renderer插件 前言 在2024.10.24号的今天发现unreal engine官网已经没有虚幻商城了&#xff0c;取而代之的是FAB ‌虚幻商城已经停止运营&#xff0c;Epic Games推出了新的数字资产商店FAB。‌ Epic Games…

重构商业生态:DApp创新玩法与盈利模式的深度剖析

随着区块链技术的发展&#xff0c;DApp&#xff08;去中心化应用&#xff09;正在从实验走向成熟。DApp以去中心化、透明性和不可篡改性为基础&#xff0c;结合智能合约&#xff0c;逐步改变传统商业运作模式&#xff0c;创造新的市场生态。本文将从DApp的独特优势、创新玩法和…

解决Docker部署ocserv的时候,遇到客户端经常重连问题

本章教程,主要介绍在Docker部署ocserv的时候,客户端连接的时候,会出现每4分钟重连问题。 解决办法 这是ocserv的核心配置文件ocserv.conf,它通常是在/etc/ocserv/目录下,主要影响每4分钟重连的参数是auth-timeout,单位是秒,原本这个默认值是240,经过单位换算,恰巧等于…

AI赋能R-Meta分析核心技术:从热点挖掘到高级模型、助力高效科研与论文发表

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面&#xff0c;成为Science、Nature论文的…

MySQL 初阶——多版本控制 MVCC

一、版本链&#xff08;undo 日志&#xff09; a. 什么是版本链 版本链就是一条以事务为节点的单链表。其 next 指针指向前一个版本的事务。 b. 版本链的增删 当一个事务被完成时&#xff0c;这个事务就会被加入到版本链里去&#xff1b;当要回滚时&#xff0c;版本链就会删…

微服务网关Zuul

一、Zuul简介 Zuul是Netflix开源的微服务网关&#xff0c;包含对请求的路由和过滤两个主要功能。 1&#xff09;路由功能&#xff1a;负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础。 2&#xff09;过滤功能&#xff1a;负责对请求的过程…

多元线性回归【正规方程/sklearn】

多元线性回归【正规方程/sklearn】 1. 基本概念1.1 线性回归1.2 一元简单线性回归1.3 最优解1.4 多元线性回归 2. 正规方程求最优解2.1 线性回归的损失函数&#xff08;最小二乘法&#xff09;2.2 推导正规方程2.3 正规方程练习2.4 使用sklearn计算多元线性方程2.5 凸函数 3. 线…

masm 6.15下载及DOSBox自动挂载

这里写目录标题 工具参考masm下载准备自动挂载 工具 系统&#xff1a;Windows 11 应用&#xff1a;DOSBox 0.74-3 masm 6.15文件 参考 DOSBox 下载安装教程&#xff1a;本人写的《DOSBox下载安装&#xff08;Windows系统 DOSBox 0.74-3&#xff09;》 https://blog.csdn.ne…

STM32-Modbus协议(一文通)

Modbus协议原理 RT-Thread官网开源modbus RT-Thread官方提供 FreeModbus开源。 野火有移植的例程。 QT经常用 libModbus库。 Modbus是什么&#xff1f; Modbus协议&#xff0c;从字面理解它包括Mod和Bus两部分&#xff0c;首先它是一种bus&#xff0c;即总线协议&#xff0c;和…

监督学习之逻辑回归

逻辑回归&#xff08;Logistic Regression&#xff09; 逻辑回归是一种用于二分类&#xff08;binary classification&#xff09;问题的统计模型。尽管其名称中有“回归”二字&#xff0c;但逻辑回归实际上用于分类任务。它的核心思想是通过将线性回归的输出映射到一个概率值…

如何限制电脑软件的安装?

1.修改注册表&#xff08;需谨慎操作&#xff0c;建议备份注册表&#xff09;&#xff1a; 打开“运行”对话框&#xff0c;输入 regedit 打开注册表编辑器。 导航到 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer。 创建新的DWORD值&…

2024双11买什么东西比较好?双十一购物清单,双十一囤货清单排名

今年双十一好价确实多&#xff0c;一方面是年底促销&#xff0c;一方面国补也很给力&#xff0c;种草很久的产品趁着这个时间下单最好不过了&#xff0c;不知道各位有哪些心水好物&#xff0c;我今年入手了不少生活用品和数码类产品&#xff0c;下文就挑选几款我觉得特别值得入…