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

news2024/10/18 17:42:13

🌈个人主页:秋风起,再归来~
🔥系列专栏: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、完结散花

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

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

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

我们下期不见不散~~

​​​​

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

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

相关文章

C#学习笔记(三)

C#学习笔记&#xff08;三&#xff09; 第 二 章 命名空间和类、数据类型、变量和代码规范二、类的组成和使用分析1. 基本概念2. 类的内容组成3. 方法的初步理解 第 二 章 命名空间和类、数据类型、变量和代码规范 二、类的组成和使用分析 1. 基本概念 类是程序的基本单元&a…

能源设施安全智能守护:AI监控卫士在油气与电力领域的应用

能源行业的安全与稳定运行对于社会的可持续发展至关重要&#xff0c;无论是石油、天然气还是电力设施&#xff0c;都面临着复杂的监测需求。思通数科推出的AI视频监控卫士&#xff0c;通过应用先进的人工智能技术&#xff0c;为能源行业的安全监测提供了高效、智能的解决方案。…

Web前端高级工程师培训:使用 Node.js 构建一个 Web 服务端程序(1)

1-使用 Node.js 构建一个 Web 服务端程序 文章目录 1-使用 Node.js 构建一个 Web 服务端程序1、Node.js的安装与基础语法2、Node.js 中的 JavaScript 与 浏览器中的 JavaScript2-1、Node.js 中的 JavaScript2-2、浏览器 中的 JavaScript 3、什么是 WebServer(APP)&#xff1f;4…

USB UVC开启 PU功能研究

文章目录 前言一、UVC拓展结构二、修改是否开启亮度等功能 在处理单元1. 处理单元理解2.实际代码部分2.修改主要对控制段进行修改 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 项目需要&#xff1a; 在哪里学习UVC 肯定是USB中文网 网址 https:…

React父子组件,父组件状态更新,子组件的渲染状况

目录 React.memo不包裹 React.memo包裹 传递一个简单数据类型 传递一个复杂数据类型 传递一个函数 React.memo不包裹 如果子组件没有使用React.memo包裹&#xff0c;则父组件中数据更新时&#xff0c;子组件会重新进行渲染 父组件&#xff1a; import { useState } fro…

MongoDB 8.0已全面可用

全球广受欢迎的文档型数据库MongoDB目前最新最强的版本&#xff0c;在易用性、企业级安全性、 弹性、可用性等方面均有大幅提升&#xff0c;适用于各种应用程序。 MongoDB 8.0的优化使整体吞吐量提高了32%&#xff0c;时间序列数据聚合的处理速度提高了200%以上。MongoDB 8.0的…

SegFormer: 一个基于Transformer的高效视觉图像分割算法

今天我分享一篇关于 Transformer 架构在图像视觉分割中的应用的文章&#xff0c;主题是 SegFormer。SegFormer 是一种新颖的语义分割方法&#xff0c;它结合了无位置编码的层次化 Transformer 编码器和轻量级 All-MLP 解码器&#xff0c;避免了传统方法中的复杂设计&#xff0c…

Flink Kubernetes Operator

Flink Kubernetes Operator是一个用于在Kubernetes集群上管理Apache Flink应用的工具。 一、基本概念 Flink Kubernetes Operator允许用户通过Kubernetes的原生工具&#xff08;如kubectl&#xff09;来管理Flink应用程序及其生命周期。它简化了Flink应用在Kubernetes集群上的…

字节 HLLM 论文阅读

github连接&#xff1a;https://github.com/bytedance/HLLM 探讨问题&#xff1a; 推荐LLM的三个关键问题&#xff1a; LLM预训练权重通常被认为是对世界知识的概括&#xff0c;其对于推荐系统的价值&#xff1f;对推荐任务进行微调的必要性&#xff1f;LLM是否可以在推荐系统…

SL3037B降压恒压芯片DC24伏输入5伏输出带单片机,电流100mA

一、SL3037B芯片概述 SL3037B是一款内置功率MOSFET的单片降压型开关模式转换器&#xff0c;具有高效、稳定、外围元器件少等特点。它能够在宽输入电源范围&#xff08;5.5~60V&#xff09;内实现0.6A的峰值输出电流&#xff0c;并具有出色的线电压和负载调整率。此外&#xff…

uniapp小程序自定义聚合点

注&#xff1a; 1.默认的聚合点可以点击自动展示子级点位&#xff0c;但是自定义的聚合点在ios上无法触发markerClusterClick的监听&#xff0c;至今未解决&#xff0c;不知啥原因 2.ios和安卓展示的点位样式还有有差别 源码附上 <template><view class"marke…

SpringMVC之 文件上传和下载

1. 文件上传 1.1 前端注意事项 文件上传操作&#xff0c;前端的表单项需要如下三项设置&#xff1a; &#xff08;1&#xff09;input标签的type属性应设置为file&#xff0c;并且注意不要在input标签中设置value属性&#xff0c;因为这可能导致文件上传不成功&#xff1b; …

轻触节点,链表里的悄然邂逅

公主请阅 1. 移除链表元素1. 题目说明示例 1示例 2示例 3 1.2 题目分析1.3 代码部分1.4 代码解析 2. 反转链表2. 1题目说明示例 1示例 2示例 3 2.2 题目分析2.3 代码部分2.4 代码分析 1. 移除链表元素 题目传送门 1. 题目说明 给你一个链表的头节点 head 和一个整数 val &…

无头浏览器测试:如何使用 Puppeteer 和 Browserless?

什么是无头浏览器测试&#xff1f; 无头浏览器测试通常指没有头的物体或东西&#xff0c;在浏览器的语境中&#xff0c;它指的是没有 UI 的浏览器模拟。无头浏览器自动化使用 Web 浏览器进行端到端测试&#xff0c;而无需加载浏览器的 UI。 无头模式是一个功能&#xff0c;它…

SpringBoot1~~~

目录 快速入门 依赖管理和自动配置 修改自动仲裁/默认版本号 starter场景启动器 自动配置 修改默认扫描包结构 修改默认配置 读取application.properties文件 按需加载原则 容器功能 Configuration Import ​编辑 Conditional ImportResource 配置绑定Configur…

一款AutoXJS现代化美观的日志模块AxpLogger

简介 Axp Logger是一款基于autox.js的现代化日志模块&#xff0c;具备窗口事件穿透、拖拽和缩放功能。 Axp Logger文档 特性现代化的UI设计支持点击穿透模式&#xff08;不影响脚本运行&#xff09;监听音量-键切换模式支持窗口操作模式窗口拖拽移动窗口自由缩放清空日志关闭日…

高精-阶乘和-保姆级教程

提供50的阶乘30414093201713378043612608166064768844377641568960512000000000000&#xff1b; 显然要做这题&#xff0c;int 或者 long long 类型的整型放不下这么长的数据所以我们要使用数组用高精度实现&#xff1b; 然后容易想到这题 肯定要用到高精度乘法和高精度加法&…

外包干了3周,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;大专生&#xff0c;21年通过校招进入武汉某软件公司&#xff0c;干了差不多3个星期的功能测试&#xff0c;那年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我才在一个外包企业干了3周的功…

Qt(10.11)

数据表 源代码&#xff1a; #include "widget.h" #include "ui_widget.h" #include<QMessageBox>//消息对话框 #include<QDebug> #include<QSqlRecord> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui-&g…

十、pico+Unity交互开发教程——射线抓取与更多交互功能

一、回顾与引入 回顾上一篇直接抓取的教程&#xff0c;VR交互一般需要可交互的对象&#xff08;Interactable&#xff09;和发起交互的对象&#xff08;Interactor&#xff09;。直接抓取和射线抓取的可交互对象无区别&#xff0c;可参考上一篇教程设置组件。两者区别在于发起…