C++:红黑树

红黑树的概念

红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出两倍,因而是接近平衡的。

如下所示就是一颗红黑树:

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(不存在连续的红色节点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径都包含相同数量的黑色节点)
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)NIL节点

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

根据红黑树的性质,当一颗子树中的节点全部为黑色节点时,路径最短;在另一颗包含红色节点的子树中,由于节点为一黑一红间隔,节点数量最多是全黑子树的两倍,路径最长。

红黑树节点的定义

//颜色
enum color
{
	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;
	color _col;
 
	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
};

在节点的定义中,为什么要将节点的默认颜色给成红色的?

新插入节点的颜色只会影响性质3或者性质4(新插入的节点不是根节点的时候),

如果新插入的节点是黑色的节点,那么一定会破坏性质4,破坏性质4要想让红黑树平衡最坏的情况需要将整颗树的节点都动一遍,很难维护。

如果新插入的节点是红色的节点,可能会破坏性质3(红色节点的孩子一定是黑色,黑色节点的孩子不一定是红色),即使破坏了性质3,最坏的情况也就不过只要更改三个节点的颜色。

红黑树的插入

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏

(注意:由于红黑树的插入也会涉及旋转等问题,所以随着高度的增加红黑树树子树的情况就会变得非常复杂,所以采用和AVL树一样的处理方法,使用具象图来表示)

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

(cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)

情况一:cur为红,p为红,g为黑,u存在且为红

cur和p均为红,违反了性质三,此处能否将p直接改为黑?

切记不可,否则就会违反性质4,处理起来会很棘手。

 处理方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

如果p是红色,由于新插入的节点默认是红色的,就会出现连续的红色节点,违反性质三,处理完后保证了性质四不会变。

如果g是根节点,调整完成后,需要将g改为黑色(根节点一定为黑色)

如果g是子树,一定有双亲,且g的双亲如果是红色,需要继续向上调整,如下图:

经调整后是可能会出现上图情况,即本来g的p是红色的,所以需要继续往上调整,以上是当cur是新插入节点的情况,同理,我们调整的这部分也可能是下面调整上来导致,即a,b,c,d,e都不为0的情况,当在a或b新插入节点导致的连续红色节点,一路向上调整这,需要纵观全局,图上可能是一棵树的全部,或者是一棵树的顶部,也可能是一颗树的中部,甚至是一颗树的底部

无论是哪一部分,无论是在哪颗子树,只要按处理方式处理,往上更新即可。

情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑

u的情况有两种:

  1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4
  2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色

处理方式:

  • p为g的左孩子,cur为p的左孩子,则进行右单旋转;
  • p为g的右孩子,cur为p的右孩子,则进行左单旋转;
  • p、g变色—>p变黑,g变红

情况1——u不存在

当u不存在&&p是g的左&&cur是p的左,abcde子树都不应该存在(de肯定不存在,c若存在那么cur插入前就应该旋转过来了,cur不可能插在p的这个位置,cur若不是新插入节点p应该是黑色,那么违反红黑树性质4。),如下图:

单纯的左子树的左高,以g为旋转点进行右单旋,旋转完成后,p变黑色g变红色:

即平衡,就不用再做处理,不再往上更新

当u不存在&&p是g的右&&cur是p的右,与上面同理,如下图:

单纯的右子树的右高,以g为旋转点进行左单旋,旋转完成后,p变黑色g变红色:

即平衡,就不用再做处理,不再往上更新

情况2——u存在且为黑

为什么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色?

分析如下:

cur为红色有两种情况:

一种情况是cur本来是红色,即cur为新插入的红色节点,但是新插入红色cur节点的情况不可能存在,因为如果是新插入红色cur节点无论如何都会违反红黑树的性质。

一种情况由子树往上更新来导致cur变成红色,即由情况一向上更新演变到cur为红,u为黑

由此分析发现只能是第二种情况。

以下更新情况都是由情况一向上更新演变到cur为红u为黑高度最少的情况即相差一层高度:

(如果再少一层,即少cur下面一层(cur是新插入的红色节点),则该树一定违反红黑树性质)

u存在&&u为黑&&p是g的左&&cur是p的左:

上图经过情况一演变到下图:

单纯的左子树的左高,以g为旋转点进行右单旋,旋转完成后,p变成黑色,g变成红色,如下图:

经过旋转+变色以后该红黑树就平衡了,不用往上更新

u存在&&u为黑&&p是g的右&&cur是p的右:

上图经过情况一演变到下图:

单纯的右子树的右高,以g为旋转点进行左单旋,旋转完成后,p变成黑色,g变成红色,如下图:

 

经过旋转+变色以后该红黑树就平衡了,不用往上更新

u存在&&u为黑&&p是g的左&&cur是p的左的模板:

abdemnxy等子树是包含相同数量黑色节点的红黑子树,从xymn几个位置中某个位置子树插入红色节点,引发cur从黑变成红,进而引发左子树左高导致的右单旋

u存在&&u为黑&&p是g的右&&cur是p的右的模板:

abdemnxy等子树是包含相同数量黑色节点的红黑子树,从xymn几个位置中某个位置子树插入红色节点,引发cur从黑变成红,进而引发右子树右高导致的左单旋

旋转+变色以后,这颗子树不违反红黑树规则,和插入前比较,黑色节点数量不变,不会影响上层,处理结束。

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

情况三和情况二其实是一样的,只不过引发的双旋,除了处理旋转,其他都按情况二方式处理

处理方式:

  • p为g的左孩子,cur为p的右孩子,针对p做左单旋转,再针对g做右单旋;
  • p为g的右孩子,cur为p的左孩子,针对p做右单旋转,再针对g做左单旋;
  • cur、g变色—>cur变黑,g变红

以下直接给出模板:

u存在&&u为黑&&p是g的左&&cur是p的右的模板:

abdemnxy等子树是包含相同数量黑色节点的红黑子树,从ab两个位置中某个位置子树插入红色节点,引发cur从黑变成红,进而引发左子树右高导致的左右单双旋

u存在&&u为黑&&p是g的右&&cur是p的左的模板:

abdemnxy等子树是包含相同数量黑色节点的红黑子树,从ab两个位置中某个位置子树插入红色节点,引发cur从黑变成红,进而引发右子树左高导致的右左单双旋

和情况二同理,旋转+变色以后,这颗子树不违反红黑树规则,和插入前比较,黑色节点数量不变,不会影响上层,处理结束

插入代码实现如下:

public:
    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)
		{
			parent = cur;
			if (cur->_kv.first < kv.first)
				cur = cur->_right;
			else if (cur->_kv.first > kv.first)
				cur = cur->_left;
			else
				return false;
		}
 
		//将节点插入
		cur = new Node(kv);
		cur->_col = RED;
		if (parent->_kv.first < cur->_kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
 
		cur->_parent = parent;
 
		//连续的红色
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;
			assert(grandparent);
 
			//parent是grandparent的左
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
 
				//unlce存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新parent和uncle的颜色为黑色,grandparent为红色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
					//向上更新
					cur = grandparent;
					parent = cur->_parent;
				}
				//uncle不存在或者为黑
				else
				{
					//cur是parent的左,左高,右单旋,以grandparent为旋转点
					if (cur == parent->_left)
					{
						RotateR(grandparent);
						grandparent->_col = RED;
						parent->_col = BLACK;
					}
					//cur是parent的右,左右双旋的情况
					else
					{
						RotateL(parent);
						RotateR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
 
					break;//旋转调整颜色后一定平衡,只是更改颜色未旋转不一定平衡
				}
			}
			//parent是grandparent的右
			else
			{
				Node* uncle = grandparent->_left;
 
				//uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新parent和uncle的颜色为黑色,grandparent为红色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
					//向上更新
					cur = grandparent;
					parent = cur->_parent;
				}
				//uncle不存在或者为黑
				else
				{
					//cur是parent的右,右高,左单旋
					if (cur == parent->_right)
					{
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					//cur是parent的左,右左双旋
					else
					{
						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
 
					break;//旋转调整颜色后一定平衡,只是更改颜色未旋转不一定平衡
				}
			}
		}
 
		_root->_col = BLACK;//根节点必须为黑色
 
		//插入成功
		return true;
	}
private:
    //右边高左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
 
		//将根的右子树的左子树赋值给根的右子树
		parent->_right = subRL;
		//subRL为空不能访问
		if (subRL)
			subRL->_parent = parent;
 
		//将根节点变成根的右子树的左子树
		subR->_left = parent;
 
		//更新根的右子树的父节点
		subR->_parent = parent->_parent;
		//如果parent是根节点需要更新根节点
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		//如果parent上面还有祖先节点,需要更新祖先节点的左节点/右节点
		else
		{
			if (parent->_parent->_left == parent)
				parent->_parent->_left = subR;
			else
				parent->_parent->_right = subR;
		}
 
		//更新根的父节点
		parent->_parent = subR;
	}
 
	//左边高右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
 
		//根节点的左指针指向左子树的右子树
		parent->_left = subLR;
		//如果根节点的左子树的右子树不为空则更新_parent
		if (subLR)
			subLR->_parent = parent;
 
		//根节点的左子树的右指针指向parent
		subL->_right = parent;
		//更新subL的_parent
		subL->_parent = parent->_parent;
 
		//处理parent是根节点和不是根节点的情况
		//如果parent是根节点,则赋值给_root
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		//否则链接上祖先节点
		else
		{
			//确定是祖先节点的左还是右,并链接上
			if (parent->_parent->_left == parent)
				parent->_parent->_left = subL;
			else
				parent->_parent->_right = subL;
		}
 
		//更新parent的_parent
		parent->_parent = subL;
	}

红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

分别从层序遍历(查看每层节点),中序遍历(二叉搜索树是有序的),树的最低高度和最高高度(红黑树的性质),是否平衡(红黑树的性质)四个角度判断,都满足即是红黑树。

public:
    //层序遍历
	vector<vector<int>> levelOrder() {
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
 
		queue<Node*> q;
		int levelSize = 1;
		q.push(_root);
 
		while (!q.empty())
		{
			// levelSize控制一层一层出
			vector<int> levelV;
			while (levelSize--)
			{
				Node* front = q.front();
				q.pop();
				levelV.push_back(front->_kv.first);
				if (front->_left)
					q.push(front->_left);
 
				if (front->_right)
					q.push(front->_right);
			}
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
 
			// 上一层出完,下一层就都进队列
			levelSize = q.size();
		}
 
		return vv;
	}
 
    //中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
 
    //求高度差
	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}
 
 
    //判断是否平衡
	bool IsBalanceTree()
	{
		// 检查红黑树几条规则
 
		Node* pRoot = _root;
		// 空树也是红黑树
		if (nullptr == pRoot)
			return true;
 
		// 检测根节点是否满足情况
		if (BLACK != pRoot->_col)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
 
		// 获取任意一条路径中黑色节点的个数 -- 比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;
 
			pCur = pCur->_left;
		}
 
		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}
private:
    //求最大高度
    int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
 
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);
 
		return lh > rh ? lh + 1 : rh + 1;
	}
 
    //求最小高度
	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
 
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);
 
		return lh < rh ? lh + 1 : rh + 1;
	}
 
 
    //中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
 
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
 
    //是否是合法的红黑树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
 
		// 统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;
 
		// 检测当前节点与其双亲是否都为红色
		if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
		{
			cout << "违反性质三:存在连在一起的红色节点" << endl;
			return false;
		}
 
		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}

根据红黑树的性质验证是否为红黑树:

性质2:根节点可以直接进行判断

性质3:遇到红色节点就去检查父节点的颜色

性质4:先统计一条路径上黑色节点数量,再进行前序遍历,和每条路径上的黑色节点数量进行比较判断。

红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

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

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

相关文章

ArrayList——简单洗牌算法

特殊语法介绍&#xff1a; List<List<E>> 该语法情况比较特殊&#xff0c;相当于一个“二维数组”存着一个个线性表的结构&#xff0c;如图&#xff1a; 该语法的灵活性强&#xff0c;可适用于多种类型和多种情况。接下来就使用该语法来实现一个简单的洗牌操作。…

halo进阶-主题插件使用

开始捣鼓捣鼓halo&#xff0c;换换主题&#xff0c;加个页面 可参考&#xff1a;Halo 文档 安装/更新主题 主题如同壁纸&#xff0c;萝卜青菜各有所爱&#xff0c;大家按需更换即可&#xff1b; Halo好在一键更换主题&#xff0c;炒鸡方便。 安装/更新插件 此插件还扩展了插件…

CR80通用清洁卡:证卡打印机、ATM机、POS机、读卡器等卡片设备清洁维护的好助手!

随着科技的进步&#xff0c;ATM机、POS终端、门禁系统、证卡打印机、读卡器等卡片设备在我们的日常生活中扮演着越来越重要的角色&#xff0c;些设备在长时间使用和环境因素的影响下&#xff0c;容易积聚油脂、灰尘和其他污染物&#xff0c;从而对其性能和功能产生负面影响。 深…

Xcode 打包报错Command PhaseScriptExecution failed with a nonzero exit code

解决办法: 1、在Xcode项目中 Pods -> Targets Support Files -> Pods-项目名 -> Pods-项目名-frameworks 中(大约在第44行) 加上 -f 2、CocoaPods版本太旧了,可以尝试升级CocoaPods版本 使用sudo gem update cocoapods更新cocoapods&#xff0c;问题将在1.12.1版本已…

力扣2968.执行操作使频率分数最大

力扣2968.执行操作使频率分数最大 方法一&#xff1a;滑窗 前缀和 求前缀和数组s 求一个数组补齐到中位数的差值 枚举右端点 class Solution {public:int maxFrequencyScore(vector<int>& nums, long long k) {int res0,n nums.size();sort(nums.begin(),nums…

单元测试AIR原则:提升代码质量的秘密武器

文章目录 引言一、AIR原则1. Automatic&#xff08;自动化&#xff09;2. Independent&#xff08;独立性&#xff09;3. Repeatable&#xff08;可重复性&#xff09; 二、Automatic&#xff08;自动化&#xff09;三、Independent&#xff08;独立性&#xff09;四、Repeatab…

GIF录屏工具Gif123 v3.3.0单文件

软件介绍 GIF的优势是小、轻、快&#xff0c;适合时间短、画面小、需要嵌入其他页面&#xff0c;打开就自动循环播放的动画。Gif123可录制合成鼠标轨迹,可调整鼠标指针大小,可在设置中打开鼠标指针高亮光圈功能,高亮光圈可跟随鼠标移动以指示鼠标位置。软件极其简单&#xff0…

C语言实现教学计划编制问题,Dev C++编译器下可运行(240606最新更新)

背景&#xff1a; 问题描述 大学的每个专业都要编制教学计划。假设任何专业都有固定的学习年限&#xff0c;每学年含两学期&#xff0c; 每学期的时间长度和学分上限都相等。每个专业开设的课程都是确定的&#xff0c;而且课程的开设时间的安排必须满足先修关系。每个课程的先…

树形表/树形数据接口的开发

数据表格式 需要返回的json格式 点击查看json数据 [{"childrenTreeNodes" : [{"childrenTreeNodes" : null,"id" : "1-1-1","isLeaf" : null,"isShow" : null,"label" : "HTML/CSS","na…

Spark MLlib 机器学习详解

目录 &#x1f349;引言 &#x1f349;Spark MLlib 简介 &#x1f348; 主要特点 &#x1f348;常见应用场景 &#x1f349;安装与配置 &#x1f349;数据处理与准备 &#x1f348;加载数据 &#x1f348;数据预处理 &#x1f349;分类模型 &#x1f348;逻辑回归 &a…

【成品设计】基于NB模块智能烟感系统设计

《基于NB模块智能烟感系统》 整体功能&#xff1a; 所需器件&#xff1a; 选用STM32F103为主控&#xff0c;选用DS18B20温度传感器和MQ烟雾传感器作为数据采集点&#xff0c; 采用0.96寸屏显实时显示采集到的温度、烟雾等数据&#xff0c;用蜂鸣器作为报警装置。 通过有人物联…

OpenCV的“画笔”功能

类似于画图软件的自由笔刷功能&#xff0c;当按住鼠标左键&#xff0c;在屏幕上画出连续的线条。 定义函数&#xff1a; import cv2 import numpy as np# 初始化参数 drawing False # 鼠标左键按下时为True ix, iy -1, -1 # 鼠标初始位置# 鼠标回调函数 def mouse_paint(…

初学者如何对大模型进行微调?

粗略地说&#xff0c;大模型训练有四个主要阶段&#xff1a;预训练、有监督微调、奖励建模、强化学习。 预训练消耗的时间占据了整个训练pipeline的99%&#xff0c;其他三个阶段是微调阶段&#xff0c;更多地遵循少量 GPU 和数小时或数天的路线。预训练对于算力和数据的要求非…

冯喜运:6.7今日黄金原油行情分析及独家操作策略

【黄金消息面分析】&#xff1a;周三&#xff08;6月5日&#xff09;&#xff0c;金价回升逾1.2%&#xff0c;收盘报每盎司2,355.49美元&#xff0c;全面收复前一交易日的跌幅。周三当天前公布的美国民间就业数据弱于预期&#xff0c;增强了美联储将在今年晚些时候降息的预期&a…

06- 数组的基础知识详细讲解

06- 数组的基础知识详细讲解 一、基本概念 一次性定义多个相同类型的变量&#xff0c;并且给它们分配一片连续的内存。 int arr[5];1.1 初始化 只有在定义的时候赋值&#xff0c;才可以称为初始化。数组只有在初始化的时候才可以统一赋值。 以下是一些示例规则&#xff1a; …

AI全自动批量剪辑软件,一天剪辑3000条原创视频不是梦【剪辑软件+全套教程】

创建一个AI全自动批量剪辑软件的简易程序涉及较为复杂的视频处理和机器学习技术&#xff0c;而且由于这是一个相当高级的任务&#xff0c;通常需要大量的代码以及深度学习框架支持。不过&#xff0c;我可以为您提供一个非常基础版本的程序示例&#xff0c;它会用Python的moviep…

String类知识

目录 一、String存在意义 二、字符串为何不可变 三、String类常用方法 1、字符串构造 2、String对象的比较 3、字符串查找 4、转化 &#xff08;1&#xff09;数值和字符转化 &#xff08;2&#xff09;大小写转换 &#xff08;3&#xff09;字符串转数组 &#xff08;4&…

《精通ChatGPT:从入门到大师的Prompt指南》大纲目录

第一部分&#xff1a;入门指南 第1章&#xff1a;认识ChatGPT 1.1 ChatGPT是什么 1.2 ChatGPT的应用领域 1.3 为什么需要了解Prompt 第2章&#xff1a;Prompt的基本概念 2.1 什么是Prompt 2.2 好Prompt的特征 2.3 常见的Prompt类型 第二部分&#xff1a;Prompt设计技巧 第…

【Linux取经路】守护进程

文章目录 一、前台进程和后台进程二、Linux 的进程间关系三、setsid——将当前进程设置为守护进程四、daemon——设置为守护进程五、结语 一、前台进程和后台进程 Linux 中每一次用户登录都是一个 session&#xff0c;一个 session 中只能有一个前台进程在运行&#xff0c;键盘…

【Linux】进程切换环境变量

目录 一.进程切换 1.进程特性 2.进程切换 1.进程切换的现象 2.如何实现 3.现实例子 2.环境变量 一.基本概念 二.常见环境变量 三.查询常见环境变量的方法 四.和环境变量相关的命令 五.环境变量表的组织方式 六.使用系统调用接口方式查询环境变量 1.getenv 2.反思 …