C++数据结构——红黑树

news2024/11/17 1:34:31

一,关于红黑树

红黑树也是一种平衡二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,颜色右两种,红与黑,因此也称为红黑树。

通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树可以确保没有一条路径会比其他路径长出两倍,因此是“近似平衡”。

下面就是一棵红黑树:

结合上面的图,我们可以了解下红黑树成立的各种条件:

①每个节点不是红色就是黑色

②根节点是黑色的

③如果一个节点是红色的,那么这个红色节点的两个孩子节点都是黑色的

④对于每个节点,从该节点到其所有后代叶结点的路径上,黑色节点的数量相同

⑤每个叶子节点都是黑色的(这里所说的叶子节点是上图中的空节点)

二,红黑树节点和结构定义

2.1 节点

//用枚举来标识颜色
enum Colour
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

2.2 结构

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:

    //此处实现各种成员函数和接口

private:
    Node* _root = nullptr;
}

三,红黑树插入*

3.1 基本插入

基本插入也和之前的差不多,直接上代码:

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 (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;
	//插入完成,开始改变颜色和调整旋转
	cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同
	//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环

	while (parent && parent->_col == RED)//父节点存在且父节点为红色
	{
        //这里的循环控制平衡,具体看下面的各种情况
        
    }
}

按搜索树的性质插入之后就是要判断红黑树的性质是否遭到破坏。

新插入节点默认为红色,因此:如果双亲节点颜色是黑色,那么没有违反红黑树的任何性质,不需要调整,就是上面代码最后的循环直接跳过;但是如果新插入的节点的双亲为红色,就违反了上面的规则“红节点的孩子为黑”,也就是出现了连续的红节点,需要调整,就是进入上面的循环部分

此时就要分下面的几条情况来讨论,维持平衡的方法的关键就是看叔叔也就是父亲的兄弟节点的状态

3.2 情况一:uncle节点存在且为红

3.2.1 cur是新增节点

如下图(假设cur是新增):

uncle存在且为红时,我们直接将父节点和叔叔节点都变成黑,再将爷爷节点变为红,但是只这样做肯定不行,比如下图:

一次调整过后就会出现上面的情况,所以需要不断往上调整

3.2.2 cur不是新增节点

如下图:

如上图,cur本身是黑色,是树中原来的节点,因为子树有新增变成了红,所以对于cur节点有两种情况符合情况一:

①本身是新增,默认新增节点为红

②子树有新增,通过情况一的向上调整变成了红 

3.3 情况二+情况三:uncle不存在或者存在为黑

从情况一我们可以看出,cur可能是新节点也可能不是新节点,但最终结果都是变红。

如果uncle节点不存在,那么cur一定是新增,如果cur不是新增,最开始循环的条件为父节点存在且为红,又因为红节点的孩子为黑这条性质,cur必定为黑,但是由于uncle不存在,导致以爷爷节点为根的子树左右子树黑节点数量不一样。

所以如果uncle不存在,cur必定为新增,如下图:

而对于uncle节点存在且为黑时, 那么cur原来肯定是黑色的因为父节点是红的,右因为左右两边黑色节点数量要一致,cur的孩子也为红,这时候在cur孩子后面新增节点时,就变成了情况一,会不断向上调整颜色,也会把cur变成红色,如下图:

所以,情况二和情况三的最终情况都是cur为红,所以可以放在一起讨论

3.4 插入代码

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 (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;
	//插入完成,开始改变颜色和调整旋转
	cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同
	//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环

	while (parent && parent->_col == RED)//父节点存在且父节点为红色
	{
		//找祖父
		Node* grandfather = parent->_parent;
		assert(grandfather);
		assert(grandfather->_col == BLACK);
		//红黑树的关键看叔叔,也就是父亲的兄弟
		if (parent == grandfather->_left)//父亲在爷爷的左边,叔叔分为几种情况分开讨论
		{
			Node* uncle = grandfather->_right;//父亲在左边,那么叔叔就在右边
			//情况一:uncle存在且为红
			if (uncle && uncle->_col == RED)
			{
				//父亲和叔叔变黑是为了替代祖父的黑,祖父变红是为了保持黑色节点数量不变
				parent->_col = uncle->_col = BLACK;//把父亲和叔叔变黑
				grandfather->_col = RED;//把祖父变红
				//继续往上处理 -- 将祖父当成新增节点,循环往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+三,uncle不存在 + 存在且为黑
			else
			{
				//新增在左边:右单旋+变色
				//      g            p
				//   p     u  --> c     g 
				//c                         u
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				//新增在右边:左单旋 + 右双旋+变色
				//     g
				//  p     u   -->   
				//    c
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else//父亲在右边,叔叔可能在左边  (parent == grandfather->_right)
		{
			Node* uncle = grandfather->_left;
			//情况一
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else //情况二+三,uncle不存在 + 存在且为黑
			{
				//新增在右边:左单旋+变色
				//      g
				//   u     p
				//            c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				//新增在左边:左右双旋+变色
				//     g
				//  u     p
				//       c
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}

		}
	}
	//循环结束
	_root->_col = BLACK;
	return true;
}

四,其他接口实现

4.1 左旋转函数

//左旋转函数
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	//subRL可能是空
	if (subRL)
	{
		subRL->_parent = parent;
	}
	//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整
	Node* ppNode = parent->_parent;

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

	//parent是整棵树的根
	if (_root == parent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else//parent是子树的根
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;
	}
}

4.2 右旋转函数

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

	parent->_left = subLR;
	//subLR可能是空
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整
	Node* ppNode = parent->_parent;

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

	//parent是整颗树的根
	if (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;
	}
}

4.3 检查函数

public:
	bool IsBalance() //根据规则来检查
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		//定义基准值,用来判断每条路径的黑色节点数量是否相同
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}

		//检查连续红色节点
		return _Check(_root,0,benchmark);

	}
private:
	bool _Check(Node* root,int blackNum,int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点数量不相等";
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blackNum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红节点" << endl;
			return false;
		}
		return _Check(root->_left,blackNum,benchmark) && _Check(root->_right,blackNum,benchmark);
	}

 4.4 打印

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
private:
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

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

4.5 查找

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

4.6 析构

~RBTree()
{
	_Destroy(_root);
	_root == nullptr;
}
private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
		root == nullptr;
	}

五,红黑树源代码和测试代码

源代码(RBTree.h)

#pragma once
//最长路径不超过最短路径的两倍

//红黑树和AVL树相比来说,红黑树的优点是旋转的次数更少
//如果两个树都插入1万个树,查找时,AVL树最多要查找30次,红黑树最多查找60次
// 但是这种差别对于CPU来说可以忽略不计,所以论综合性能,还是红黑树更胜一筹
#include<iostream>
#include<assert.h>
using namespace std;

//用枚举来标识颜色
enum Colour
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	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;
			}
		}
	}
	~RBTree()
	{
		_Destroy(_root);
		_root == 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 (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;
		//插入完成,开始改变颜色和调整旋转
		cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同
		//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环

		while (parent && parent->_col == RED)//父节点存在且父节点为红色
		{
			//找祖父
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);
			//红黑树的关键看叔叔,也就是父亲的兄弟
			if (parent == grandfather->_left)//父亲在爷爷的左边,叔叔分为几种情况分开讨论
			{
				Node* uncle = grandfather->_right;//父亲在左边,那么叔叔就在右边
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					//父亲和叔叔变黑是为了替代祖父的黑,祖父变红是为了保持黑色节点数量不变
					parent->_col = uncle->_col = BLACK;//把父亲和叔叔变黑
					grandfather->_col = RED;//把祖父变红
					//继续往上处理 -- 将祖父当成新增节点,循环往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三,uncle不存在 + 存在且为黑
				else
				{
					//新增在左边:右单旋+变色
					//      g            p
					//   p     u  --> c     g 
					//c                         u
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//新增在右边:左单旋 + 右双旋+变色
					//     g
					//  p     u   -->   
					//    c
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//父亲在右边,叔叔可能在左边  (parent == grandfather->_right)
			{
				Node* uncle = grandfather->_left;
				//情况一
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况二+三,uncle不存在 + 存在且为黑
				{
					//新增在右边:左单旋+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//新增在左边:左右双旋+变色
					//     g
					//  u     p
					//       c
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
		}
		//循环结束
		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance() //根据规则来检查
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		//定义基准值,用来判断每条路径的黑色节点数量是否相同
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}

		//检查连续红色节点
		return _Check(_root,0,benchmark);

	}
private:
	bool _Check(Node* root,int blackNum,int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点数量不相等";
				return false;
			}
			return true;
		}
		if (root->_col == BLACK)
		{
			++blackNum;
		}
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续红节点" << endl;
			return false;
		}
		return _Check(root->_left,blackNum,benchmark) && _Check(root->_right,blackNum,benchmark);
	}
	//左旋转函数
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		//subRL可能是空
		if (subRL)
		{
			subRL->_parent = parent;
		}
		//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整
		Node* ppNode = parent->_parent;

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

		//parent是整棵树的根
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else//parent是子树的根
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}
	//右旋转函数
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		//subLR可能是空
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整
		Node* ppNode = parent->_parent;

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

		//parent是整颗树的根
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
	}
	//打印子函数
	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;
		root == nullptr;
	}
private:
	Node* _root = nullptr;
};

测试

#include"RBTree.h"

void TestRBTree1()
{
	int a[] = { 4,2,6,1,3,5,15,7,16,14 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}
	cout << "IsBalance:" << t1.IsBalance() << endl;
}

void TestRBTree2()
{
	size_t N = 10000;
	srand(time(0));
	RBTree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		cout << "Insert:" << x << ":" << i << endl;
		t1.Insert(make_pair(x, i));
	}
	cout << "IsBalance:" << t1.IsBalance() << endl;
}


int main()
{
	TestRBTree2();
	cout << endl;
	TestRBTree1();
	return 0;
}

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

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

相关文章

01 质数筛

一、根据概念进行枚举 1、判断质数的枚举算法 根据概念:除了1和它本身以外没有其他约数的数为质数 //输入一个数n&#xff0c;判断n是不是质数 #include<bits/stdc.h> using namespace std;int main(){int n;cin>>n;//根据概念:除了1和它本身以外没有其他约数的…

进程通信与socket编程实践之猜数字小游戏

socket是实现进程通信的一种重要方式&#xff0c;本文将通过socket编程实现服务器进程与客户端进程之间的通信&#xff0c;并在通信之外实现猜数字的小游戏。 1. 设计思路 本文设计的C/S结构的猜数字游戏功能如下&#xff1a;服务器端自动生成一个1-100之间的随机数字&#x…

linux基础学习(7):find命令

1.按照文件名搜索 find 搜索路径 选项 文件名 选项&#xff1a; -name&#xff1a;按文件名搜索 -ineme&#xff1a;不区分文件名大小写搜索 -inum&#xff1a;按inode号搜索 按文件名搜索跟按关键词搜索不一样&#xff0c;只能搜到文件名完整对应的文件 *根据文件名…

加速应用开发:低代码云SaaS和源码交付模式如何选

随着数字化转型的加速&#xff0c;企业对于快速开发和交付高质量应用的需求也越来越迫切。为了满足这一需求&#xff0c;开发者们开始探索采用低代码平台进行软件开发工作&#xff0c;以加速应用开发过程。 目前&#xff0c;市场上的低代码产品众多&#xff0c;但基本可分为简单…

特征工程之特征降维

为什么要进行特征降维&#xff1f; 特征对训练模型是非常重要的,用于训练的数据集包含一些不重要的特征,可能导致模型泛化性能 不佳 某些特征的取值较为接近&#xff0c;其包含的信息较少 希望特征独立存在对预测产生影响&#xff0c;两个特征同…

Goldsky - 使用ClickHouse和Redpanda的黄金标准架构

本文字数&#xff1a;6240&#xff1b;估计阅读时间&#xff1a;16 分钟 作者&#xff1a;ClickHouse Team 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 介绍 作为一家以开源为根基的公司&#xff0c;我们发现用户通常是第一个识别…

JWT登录

JWT JSON Web Token&#xff08;JSON Web令牌&#xff09; 是一个开放标准(rfc7519)&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任&#xff0c;因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法…

3.确认弹窗(ConfirmPopup)

愿你出走半生,归来仍是少年&#xff01; 环境&#xff1a;.NET 7 在开发中&#xff0c;最常用的弹窗之一表示确认弹窗&#xff0c;为了减少重复的开发工作&#xff0c;所以需要基于Popup进行封装。 1.布局 分为标题、确认内容、按钮三个区域&#xff0c;都是可供调整的。 &l…

二叉树堆的应用实例分析:堆排序 | TOP-K问题

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 前言一、堆排序1.1 排序思想1.2 堆排序过程&#xff08;图解&#xff09;1.3 堆排序代…

查看代码是否是在GPU上跑的

import torchif torch.cuda.is_available():print("运行在GPU上") else:print("运行在CPU上")进入容器后 如果进入容器后&#xff0c;是没法通过nvidia-smi命令查看显卡型号的&#xff0c;但是环境仍然是GPU在运行。 nvidia-smi 没进入的时候

【笔记】杨继深老师电磁兼容(EMC)课程1-3笔记

视频链接 【杨继深老师电磁兼容&#xff08;EMC&#xff09;课程最新版——精品课_哔哩哔哩_bilibili 1、什么是电磁骚扰&#xff08;EMI&#xff09;&#xff1f; &#xfeff; 第1讲 什么是辐射发射 P1 - 01:39. &#xfeff; 骚扰&#xff1a;主动性的对其他设备造成影响…

从多巴胺到老钱风,品牌如何做好人设营销

在今年开年&#xff0c;又一大旅游城市爆火&#xff0c;被网友称为“讨好型市格”的哈尔滨&#xff0c;第一次有了清晰的人设&#xff0c;哈尔滨也迎来无数游客。其实品牌玩人设营销不止今年&#xff0c;在去年就已经有趋势&#xff0c;比如i人e人营销、“多巴胺穿搭”&#xf…

激光雷达标定入门(7)海康摄像头驱动

如果你在使用海康威视摄像头时遇到了编译报错的问题&#xff0c;可能是链接库的路径配置不正确。下面是解决这个问题的步骤和原理&#xff1a; 1. 克隆海康摄像头驱动代码 首先&#xff0c;你需要将海康摄像头的驱动代码克隆到你的工作空间中。使用以下命令&#xff1a; git…

【docker】解决docker overlay2目录占用大量磁盘空间,导致验证码出不来,报错Can‘t create output stream!

问题&#xff1a; 验证码出现Cant create output stream!报错信息 排查&#xff1a; 所在服务器磁盘使用率已经到达100%&#xff0c;经排查&#xff0c;服务器目录/var/lib/docker/overlay2占用大量磁盘空间&#xff0c; 解决&#xff1a; 使用【docker system prune】命令删…

哪吒监控面板对VPS统一管理

VPS安装Nginx Proxy Manager 可视化面板 - 非必须 Nginx作用是做一个代理&#xff0c;不用代理直接安装哪吒面板也是可以的&#xff0c;但是必须要有一个域名和github账号。 1、更新下VPS系统环境&#xff1a; apt update -y && apt install -y curl socat wget sudo…

安装宝塔面板后k8s所在节点pod无法正常工作解决方法,kubernetes k8s 与宝塔面板冲突解决方法

在实际项目过程中我们使用了k8s 在生产环境中运行管理服务。 但是对服务器的状态管理我们使用了宝塔面板进行 K8s 版本1.2.8 宝塔面板 版本 8.05 操作步骤是这样的。 1.完成1.2.8 k8s的节点安装&#xff0c;并正常运行服务。 过程略 2.安装宝塔面板 ​ yum install -y …

ChromeDriver谷歌驱动最新版安装120/121/122

chromeDriver最新版本下载 最新驱动 https://googlechromelabs.github.io/chrome-for-testing/参考&#xff1a; https://blog.csdn.net/m0_57382185/article/details/134007615

中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要 --九五小庞

原文链接&#xff1a;中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要_滚动新闻_中国政府网 第二篇 坚持创新驱动发展 全面塑造发展新优势 坚持创新在我国现代化建设全局中的核心地位&#xff0c;把科技自立自强作为国家发展的战略支撑&#xff0c;面…

Cute Http File Server 使用文章

下载 官网&#xff1a;http://iscute.cn/chfs 蓝奏下载&#xff1a;https://wwts.lanpw.com/iKP1i1m9572h 开源&#xff1a;https://github.com/docblue/chfsgui 介绍 Cute Http File Server 是国内免费开源的局域网传输服务器软件。 可以不用借助QQ、某信软件传输文件&am…

AI绘图软件:探索未来的创意工具

AI绘图软件有很多&#xff0c;以下是一些比较知名的AI绘图软件&#xff1a; Adobe Photoshop&#xff1a;全球最流行的图像编辑软件之一&#xff0c;具备多种AI功能&#xff0c;如智能修复、智能笔刷等。Corel Painter&#xff1a;一款专业的数字艺术软件&#xff0c;有AI功能…