红黑树:c++实现

news2024/10/24 5:20:05

1. 红⿊树的概念

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

1.1 红⿊树的规则:

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

2. 根结点是⿊⾊的

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

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

 

1.2 思考⼀下,红⿊树如何确保最⻓路径不超过最短路径的2倍的?

• 由规则4可知,从根到NULL结点的每条路径都有相同数量的⿊⾊结点,所以极端场景下,最短路径 就就是全是⿊⾊结点的路径,假设最短路径⻓度为bh(blackheight)

• 由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀ ⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh。

• 综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都 存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh

1.3 红⿊树的效率:

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么由此推出  ,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径 ,那么时间复杂度还是 :

2^ h − 1< N < 2^ 2∗h − 1

h ≈ logN 2 ∗ logN O(logN)

红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜 ⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红 ⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格。

 

2. 红⿊树的实现

2.1 红⿊树的结构

//单个节点,枚举表示颜色
enum Color
{
	Balck
	, Red
};
//其余的参考没有平衡因子的avl树的结构
template<class K,class V>
//把这个设计成struct,全部都是public
struct RBTreeNode
{
	//参数
	pair<K, V> _kv;
	RBTreeNode<K,V>* _left;
	RBTreeNode<K,V>* _right;
	RBTreeNode<K,V>* _parent;
	Color _color;
	//初始化
	RBTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};
template<class K,class V>
class RBTree
{
	
public:
	using Node = RBTreeNode<K,V>;
private:
	Node* _root = nullptr;
};

2.2 红⿊树的插⼊

2.2.1 红⿊树树插⼊⼀个值的⼤概过程

1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。

2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树 插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。

3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束

4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是 红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种 情况分别处理。

2.2.2 情况1:变⾊(c为当前节点,p为父节点,u为叔叔节点,g为祖父节点)

“叔叔存在且叔叔为红”

c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。

 分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊结点,g再变红,相 当于保持g所在⼦树的⿊⾊结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新 是因为,g是红⾊,如果g的⽗亲还是红⾊,那么就还需要继续处理;如果g的⽗亲是⿊⾊,则处理结束 了;如果g就是整棵树的根,再把g变回⿊⾊。情况1只变⾊,不旋转。所以⽆论c是p的左还是右,p是g的左还是右,都是上⾯的变⾊处理⽅式。

 

if (uncle && uncle->_color == Red)
{
				parent->_color = Balck;
				uncle->_color = Balck;
				grandparnet->_color = Red;

                //更新当前节点和父节点,继续向上检查
				cur = grandparnet;
				parent = cur->_parent;
}

 总结:这里就是指,当叔叔节点存在,且为红的时候,把父节点和叔叔节点变成黑,再把祖父节点变成红,更新当前节点和父节点,继续向上检查

2.2.3 情况2:单旋+变⾊

“叔叔不存在或为黑”的第一种情况

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则 c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上 来的。 分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解 决问题,需要旋转+变⾊。

比如说:

当parent是g的左子树的时候,需要对g节点进行右单旋,同时把p变成黑色,g变成红色

if (cur == parent->_left)
{
				//					g
				//				p       u
				//			c	
				//对g进行右单旋	,然后把p变成黑,g变成红	
				RotateR(grandparnet);
				parent->_color = Balck;
				grandparnet->_color = Red;

}

当parent是g的右子树的时候,同理,对g进行左单旋,同时把p变成黑色,g变成红色

			else
			{
/*			g
		u      p
					c
					直接对g进行左单旋,然后把p变成黑,把g变成红*/
				RotateL(grandparnet);
				grandparnet->_color = Red;
				parent->_color = Balck;
			}

2.2.4 情况2:双旋+变⾊ 

“叔叔不存在或为黑”的第二种情况

比如:

当p为g的左子树,c为p的右子树时, 先把p进行左单旋变成图3,在对g进行右单旋,把c变黑,把g变红即可

else if (cur == parent->_right)
{
				//					g
				//				p      u
				//					c
				//先对p进行左单旋,再对g进行右单旋,把c变成黑,g变成红
				RotateL(parent);
				RotateR(grandparnet);
				cur->_color = Balck;
				grandparnet->_color = Red;
}

同理,当p为g的右子树,c为p的左子树时, 先把p进行右单旋变成图3,在对g进行左单旋,把c变黑,把g变红即可

					if (cur == parent->_left)
					{
				/*		g
					 u     p
						c
先对p进行右单旋,在对g进行左单旋,然后把c变成黑,把g变成红*/
						RotateR(parent);
						RotateL(grandparnet);
						cur->_color = Balck;
						grandparnet->_color = Red;
					}

2.3 红⿊树的插⼊代码实现

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			Node* root = new Node(kv);
			_root = root;
			_root->_color = Balck;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		
		cur = new Node(kv);
		cur->_color = Red;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		
		
		while (parent && parent->_color == Red)
		{
			Node* grandparnet = parent->_parent;
			if (parent == grandparnet->_left)
			{
				Node* uncle = grandparnet->_right;
				if (uncle && uncle->_color == Red)
				{
					parent->_color = Balck;
					uncle->_color = Balck;
					grandparnet->_color = Red;
					cur = grandparnet;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						//					g
						//				p       u
						//			c	
						//对g进行右单旋	,然后把p变成黑,g变成红	
						RotateR(grandparnet);
						parent->_color = Balck;
						grandparnet->_color = Red;

					}
					else if (cur == parent->_right)
					{
						//					g
						//				p      u
						//					c
						//先对p进行左单旋,再对g进行右单旋,把c变成黑,g变成红
						RotateL(parent);
						RotateR(grandparnet);
						cur->_color = Balck;
						grandparnet->_color = Red;
					}
					break;
				}
			}
			else if (parent == grandparnet->_right)
			{
				Node* uncle = grandparnet->_left;
				if (uncle && uncle->_color == Red)
				{
					uncle->_color = Balck;
					parent->_color = Balck;
					grandparnet->_color = Red;
					cur = grandparnet;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
				/*		g
					 u     p
						c
先对p进行右单旋,在对g进行左单旋,然后把c变成黑,把g变成红*/
						RotateR(parent);
						RotateL(grandparnet);
						cur->_color = Balck;
						grandparnet->_color = Red;
					}
					else
					{
		/*			g
				u      p
							c
							直接对g进行左单旋,然后把p变成黑,把g变成红*/
						RotateL(grandparnet);
						grandparnet->_color = Red;
						parent->_color = Balck;
					}
					break;
				}
			}
		}
		_root->_color = Balck;
		return true;
	}

2.4 红⿊树的查找

//查找节点
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_kv.first)
		{
			cur = cur->_right;
		}
		else if (key < cur->_kv.first)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

2.5 红⿊树的验证

这⾥获取最⻓路径和最短路径,检查最

⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条 件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还 是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。

1. 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。

2. 规则2直接检查根即可

3. 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检 查⽗亲的颜⾊就⽅便多了。

4. 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到 ⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点 数量作为参考值,依次⽐较即可。

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

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


bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_color == Red)
		return false;

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

2.6红黑树完整代码

RBTree.h

#include<iostream>
using namespace std;

//单个节点
enum Color
{
	Balck
	, Red
};
template<class K,class V>
//把这个设计成struct,全部都是public
struct RBTreeNode
{
	//参数
	pair<K, V> _kv;
	RBTreeNode<K,V>* _left;
	RBTreeNode<K,V>* _right;
	RBTreeNode<K,V>* _parent;
	Color _color;
	//初始化
	RBTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};

template<class K,class V>
class RBTree
{
	
public:
	using Node = RBTreeNode<K,V>;
	//红黑树的插入
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			Node* root = new Node(kv);
			_root = root;
			_root->_color = Balck;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		
		cur = new Node(kv);
		cur->_color = Red;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		
		
		while (parent && parent->_color == Red)
		{
			Node* grandparnet = parent->_parent;
			if (parent == grandparnet->_left)
			{
				Node* uncle = grandparnet->_right;
				if (uncle && uncle->_color == Red)
				{
					parent->_color = Balck;
					uncle->_color = Balck;
					grandparnet->_color = Red;
					cur = grandparnet;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						//					g
						//				p       u
						//			c	
						//对g进行右单旋	,然后把p变成黑,g变成红	
						RotateR(grandparnet);
						parent->_color = Balck;
						grandparnet->_color = Red;

					}
					else if (cur == parent->_right)
					{
						//					g
						//				p      u
						//					c
						//先对p进行左单旋,再对g进行右单旋,把c变成黑,g变成红
						RotateL(parent);
						RotateR(grandparnet);
						cur->_color = Balck;
						grandparnet->_color = Red;
					}
					break;
				}
			}
			else if (parent == grandparnet->_right)
			{
				Node* uncle = grandparnet->_left;
				if (uncle && uncle->_color == Red)
				{
					uncle->_color = Balck;
					parent->_color = Balck;
					grandparnet->_color = Red;
					cur = grandparnet;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
				/*		g
					 u     p
						c
先对p进行右单旋,在对g进行左单旋,然后把c变成黑,把g变成红*/
						RotateR(parent);
						RotateL(grandparnet);
						cur->_color = Balck;
						grandparnet->_color = Red;
					}
					else
					{
		/*			g
				u      p
							c
							直接对g进行左单旋,然后把p变成黑,把g变成红*/
						RotateL(grandparnet);
						grandparnet->_color = Red;
						parent->_color = Balck;
					}
					break;
				}
			}
		}
		_root->_color = Balck;
		return true;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	//查找节点
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_kv.first)
			{
				cur = cur->_right;
			}
			else if (key < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

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

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


	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_color == Red)
			return false;

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

private:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_kv.first<<" ";
		_Inorder(root->_right);
	}
	void RotateR(Node* parent)
	{
		Node* child_left = parent->_left;
		Node* child_left_right = child_left->_right;
		if (child_left_right != nullptr)
		{
			child_left_right->_parent = parent;
		}
		parent->_left = child_left_right;
		Node* parent_parent = parent->_parent;
		parent->_parent = child_left;
		child_left->_right = parent;
		
		if (parent_parent == nullptr)
		{
			_root = child_left;
			child_left->_parent = nullptr;
		}
		else
		{
			if (parent == parent_parent->_left)
			{
				parent_parent->_left = child_left;
				child_left->_parent = parent_parent;
			}
			else
			{
				parent_parent->_right = child_left;
				child_left->_parent = parent_parent;
			}
		}
	}
	void RotateL(Node* parent)
	{
		Node* pR = parent->_right;
		Node* pRL = pR->_left;
		parent->_right = pRL;
		if (pRL)
		{
			pRL->_parent = parent;
		}
		pR->_left = parent;
		Node* parentparent = parent->_parent;
		parent->_parent = pR;
		if (parentparent == nullptr)
		{
			_root = pR;
			pR->_parent = nullptr;
		}
		else
		{
			if (parentparent->_left == parent)
			{
				parentparent->_left = pR;
				pR->_parent = parentparent;
			}
			else
			{
				parentparent->_right = pR;
				pR->_parent = parentparent;

			}
		}
	}
private:
	Node* _root = nullptr;
};

Test.cpp 

#include"RBTree.h"

void TestRBTree1()
{
	RBTree<int, int> t;
	// 常规的测试用例
	//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({ e, e });
	}
	
	t.Inorder();
	cout << endl;
	cout << t.IsBalance() << endl;
}

int main()
{
	TestRBTree1();

	return 0;
}

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

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

相关文章

用LaTeX写一篇帅帅的算法学习题解

前言 先来看看用 LaTeX \text{LaTeX} LaTeX 写出来的题解是啥样&#xff0c;内容是瞎写的。 前提知识与环境 默认已经掌握了 LaTeX \text{LaTeX} LaTeX 的一些用法&#xff0c;特别是公式如何写&#xff0c;如果你对这块还是很了解&#xff0c;可以先学着使用 Typora \…

DEEP TEMPORAL GRAPH CLUSTERING.md

ICLR23 推荐指数&#xff1a; #paper/⭐ 原因的话&#xff0c;可以找找前人的一篇文章(可以看&#xff0c;但是当你阅读前人文章会发现。) 动机/优点 很明确&#xff0c;时序图只需要考虑时间相近的点&#xff0c;因此开销特别小。但是邻接矩阵&#xff0c;就要考虑所有点的关…

Dockerfile最佳实践:如何创建高效的容器

在微服务和云计算时代&#xff0c;Docker就已经成为应用开发和部署不可或缺的工具。如今虽处大模型时代&#xff0c;但这些基础技术仍然是我们需要掌握的。 容器化允许开发者将应用程序及其依赖打包到一个单一的、可移植的单元中&#xff0c;确保了可预测性、可扩展性和快速部…

反射机制(Reflection)

1. 反射 Java的反射机制(reflection)是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff1b;这种动态获取信息以及动态调用对象方法的功能称为java语言的反…

创建一个c#程序,实现字符串类型转整数类型

首先&#xff0c;创建一个c#程序 在代码编辑器中编写代码&#xff0c;点击Run按钮或者按下F5键来运行程序。 下面&#xff0c;编写将字符串类型转换为整数类型的代码。 sing System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Task…

安装samples/1_Utilities/deviceQuery等文件

本文章摘抄来自https://zhuanlan.zhihu.com/p/666647168 从cuda11.6开始cuda toolkit就不自带cuda-samples了&#xff0c;而deviceQuery又是cuda-sample的一个子库&#xff0c;所以需要自己手动装一下。 我的系统是ubuntu20.04&#xff0c;已经安装了CUDA Toolkit 12.2。 第…

【深度学习】使用FasterRCNN模型训练自己的数据集(记录全流程

此处用的FasterRCNN模型使用的是B导的源码&#xff0c;读者可以去B站搜B导的视频进行了解和学习&#xff0c;视频中B导非常细心讲解了如何训练自己的数据集以及预测。 文章目录 前言一、准备数据集二、环境配置2.1 基础环境2.2 其他依赖包安装2.3 预训练权重下载 二、训练数据集…

程序员成长秘籍:是迈向管理巅峰,还是深耕技术架构?

专业在线打字练习平台-巧手打字通&#xff0c;只输出有价值的知识。 一 管理和架构 做技术的同学一般有两条职业发展路径&#xff0c;横向的管理路线和纵向的技术路线。管理路线对应的是管理岗&#xff0c;讲究的是排兵布阵&#xff0c;通过各种资源的优化配置发挥价值。技术路…

(Linux驱动学习 - 10).MISC驱动实验

一.MISC介绍 1.MISC定义 misc 的意思是混合、杂项的&#xff0c;因此 MISC 驱动也叫做杂项驱动&#xff0c;也就是当我们板子上的某 些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱 动&#xff0c;通常嵌套在 platform 总线驱动中&…

智能贴身监测,健康生活建议,圆道妙医智能手表体验

如今热衷于运动和健康生活的爱好者越来越多&#xff0c;相关的赛事等活动也是逐年增多&#xff0c;很多朋友为了能够直观的了解自己的健康状况&#xff0c;都会配备一款智能手表&#xff0c;这样戴在身上就可以随时了解自己的心率、血氧等数据。最近我尝试了一款圆道妙医推出的…

MobaXterm连接Cloudflare Tunnel内网穿透的SSH

背景 如官方文档所示&#xff0c;Cloudflare Tunnel要求我们对SSH客户端进行配置&#xff0c;使本地的cloudflared软件代理SSH才能连接。 存在问题 由于MobaXterm的Session实质为嵌入式PuTTY&#xff0c;不使用OpenSSH样式的配置文件&#xff08;即~/.ssh/config&#xff09…

SpringBoot框架下购物推荐网站的设计模式与实现

3系统分析 3.1可行性分析 通过对本东大每日推购物推荐网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本东大每日推购物推荐网站采用JAVA作为开发语言&…

对层级聚类树进行模块分割,定位基因在哪个模块中

拷贝数据到 ImageGP (http://www.ehbio.com/Cloud_Platform/front/#/analysis?pageb%27Ng%3D%3D%27)&#xff0c;并设置参数. ID untrt_N61311 untrt_N052611 untrt_N080611 untrt_N061011 trt_N61311 trt_N052611 trt_N080611 trt_N061011 ENSG000…

【ROS2实操二】服务通信

简介 服务通信也是ROS中一种极其常用的通信模式&#xff0c;服务通信是基于请求响应模式的&#xff0c;是一种应答机制。也即&#xff1a;一个节点A向另一个节点B发送请求&#xff0c;B接收处理请求并产生响应结果返回给A。比如如下场景&#xff1a;机器人巡逻过程中&#xff0…

cuda入门学习

最近接触cuda 编程&#xff0c;记录一下。 1 工作实现一个【0-100&#xff09;的加法 如果用python sum 0 for i in range(200):sumi print(sum)2 cuda 的一些简单的概念 一维情况下大概是这样的 (1个grid * 2个blocks * 4个thread) 3 代码直接上代码 我把100分为20个b…

Jenkins---01

什么是敏捷开发 敏捷开发以用户的需求进化为核心&#xff0c;采用迭代、循序渐进的方法进行软件开发。在敏捷开 发中&#xff0c;软件项目在构建初期被切分成多个子项目&#xff0c;各个子项目的成果都经过测试&#xff0c;具备可视、 可集成和可运行使用的特征。换言之&…

2024年编程资料【9月份部分】

资料列表 「CSDN会员免费电子书1000本」 https://pan.quark.cn/s/5019390a751a 【黑马程序员】年度钻石会员-人工智能AI进阶 https://pan.quark.cn/s/1d14a2a179c2 JavaScript从入门到高级教程 - 带源码课件 https://pan.quark.cn/s/c16ed07eac93 【马哥教育】云原生微服务治理…

测试常用插件: ModHeader - Modify HTTP headers插件进行IP模拟/IP欺骗

由于公司是做海外项目的&#xff0c;所以付款时有要求进行模拟不同IP登录进去时会优先显示该地区的支付方式。 1.安装插件 这里以Microsoft Edge为例&#xff0c;打开扩展 搜索&#xff1a;ModHeader - Modify HTTP headers&#xff0c;进行获取安装即可 安装完成后&#xff…

CVESearch部署、使用与原理分析

文章目录 前言1、概述2、安装与使用2.1、源码安装2.1.1、部署系统依赖组件2.1.1.1、下载安装基础组件2.1.1.2、下载安装MongoDB Community Edition 7.0 2.1.2、使用源码安装系统2.1.2.1、安装CVESearch2.1.2.2、填充MongoDB数据库2.1.2.3、填充Redis数据库 2.2、使用方法 3、测…

LeetCode | 704.二分查找

标准的二分查找&#xff0c;直接上模板&#xff01; class Solution(object):def search(self, nums, target):""":type nums: List[int]:type target: int:rtype: int"""l 0r len(nums) - 1while l < r:mid (l r 1) / 2if nums[mid] …