【C++篇】红黑树的实现

news2025/1/17 16:44:53

目录

前言:

一,红黑树的概念

1.1,红黑树的规则

1.2,红黑树的最长路径 

1.3,红黑树的效率分析

 二,红黑树的实现

2.1,红黑树的结构

2.2,红黑树的插入

2.2.1,大致过程 

2.2.2,情况1:变色处理 

 2.2.3,情况2:单旋+变色

2.2.4,情况3:双旋+变色

2.3,红黑树插入代码的实现

2.4,红黑树的验证

三,整体代码

四,测试代码


前言:

本篇会用到上篇【AVL树的实现】中的旋转知识。

一,红黑树的概念

        红黑树是一颗二叉搜索树,它的每一个节点增加一个存储为来表示节点的颜色。可以是红色或者黑色。它通过对从根开始到叶子节点的每条路径上各个节点颜色的约束,确保最长路径不会超过最短路径的2倍,从而实现平衡的。

1.1,红黑树的规则

1,每个节点不是红色就是黑色。

2,根节点是黑色的。

3,红色节点的两个孩子只能是 黑色节点或者是空节点。也就是说不能出现连续的红色节点

4,对于任意一个节点,从该节点开始,到叶子节点的所有路径上,均包含相同数量的黑色节点。

以上 都是红黑树,满足红黑树的规则。

1.2,红黑树的最长路径 

1,由第四条规则可知,从根节点开始的每条路径上,黑色节点的数量相同。所以在极端场景下,一颗红黑树,它的最短路径就是节点全为黑色的路径。假设红黑树的每条路径黑色节点数量都为b,那么最短路径的节点数量为b.

2,由 第三条规则可知,一条路径上不能由连续的红色节点,最长路径是由一黑一红间隔组成的,所以最长路径为2*b。

3,而对于一颗红黑树,最长和最短路径不一定存在。我们可以得出对于任意一颗红黑树,它的任意 一条路径长度x都是,b<=x<=2*b.

1.3,红黑树的效率分析

假设N是红黑树节点的数量,h是最短路径的长度,最长路径不超过2*h

可以得到2^h-1<= N <= 2^(2*h)-1,推出h大致为logN,也就意味着红黑树的增删查该最坏走2*logN,时间复杂度O(logN).

 二,红黑树的实现

2.1,红黑树的结构

enum color
{
    Red,
    Black
};

template<class k,class v>
struct RBTreeNode
{
    RBTreeNode(const pair<k,v>& kv)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_kv(kv)
    {}
    RBTreeNode<k, v>* _left;
    RBTreeNode<k, v>* _right;
    RBTreeNode<k, v>* _parent;
    pair<k, v> _kv;
    color _col;
};

template<class k,class v>
class RBTree
{
    typedef RBTreeNode<k, v> Node;
public:

   //...

private:

    Node* _root=nullptr;
};
 

2.2,红黑树的插入

2.2.1,大致过程 

1,插入一个值需要按照搜索树的规则进行插入,再判断插入后是否满足红黑树的规则。

2,如果是空树插入,新增节点就是黑色节点。如果是非空树插入,新增节点就必须是红色节点,因为如果插入黑色节点,就一定会破坏规则4,而插入红色节点是有可能会破坏规则3,而且对于规则3来说,解决方法比规则4的解决方法容易。

3,非空树插入后,如果父节点是黑色节点,则没有违反任何规则,插入结束。

4,非空树插入后,如果父节点是红色节点,则违反规则3,进一步分析。

2.2.2,情况1:变色处理 

由上图可知,c为红,p为红,g为黑,u存在且为红。在这种情况下,我们需要将p和u变黑,g变红,g成为新的c,继续向上更新。

分析:

        因为p和u都是红色的,g是黑色的。把p和u变黑,左边子树路径各增加一个黑色节点,g再变红,相当于g所在路径的黑色节点数量不变,同时解决了c和p连续红节点的问题。

        需要继续往上跟新是因为g是红色,如果g的父亲还是红色,就需要继续处理;如果g的父亲是黑色,则处理结束;如果g是整棵树的根,再将g变成黑色即可。 

 2.2.3,情况2:单旋+变色

c为红,p为红,u不存在或者u存在且u为黑色。在这种情况下,就需要进行单旋+变色处理

分析:

u不存在时,c一定是新增节点。

u存在且为黑色时,c一定不是新增节点,是在c的子树中插入,符号情况1,经过情况1后,c被调整为红色的。

 上图展示的是右单旋的场景,下面是根据右单旋,画出的左单旋大致图,与右单旋过程相似:

 总结:经过单旋+变色后,我们可以看到p做了子树新的根,且p是黑色的,所以不管p的父亲是黑色的还是红色的,都满足红黑树的规则,所以此时,就不需要往上更新了。

2.2.4,情况3:双旋+变色

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c一定是新增结点,u存在且为黑,则 c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。

 上图展示的是左右双旋+变色,同样右左双旋类似:

同样经过双旋+变色后,c成为新的根,且c为黑色,所以也不需要继续向上更新了。

2.3,红黑树插入代码的实现

bool Insert(const pair<k, v>& kv)
{
	//插入根节点,color->Black
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = Black;
		return true;
	}
	//根据搜索树的规则查找插入位置
	Node* cur = _root;
	Node* parent = nullptr;
	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);
	cur->_col = Red;
	//与父亲节点连接好
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	//颜色处理+旋转
	while (parent&& parent->_col == Red)
	{
		//g
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			//p为g的左边
			//    g
			//  p   u
			Node* uncle = grandfather->_right;
			//叔叔存在且为红,情况1
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = Black;
				uncle->_col = Black;
				grandfather->_col = Red;
				//继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况2,3
				//叔叔不存在或者叔叔为黑
				//u为黑,则c之前是黑的
				//u不存在,则c是新插入的
				if (cur == parent->_left)
				{
					//右单旋场景
					 //   g
			        //  p   u
		           // c
					RotateR(grandfather);
					//变色
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else
				{
					//左右双旋场景
					//    g
					//  p   u
				   //     c
					RotateL(parent);
					RotateR(grandfather);
					//变色
					cur->_col = Black;
					grandfather->_col = Red;
				}
				//不需要进行向上更新
				break;
			}
		}
		else
		{
			//p为g的右边
			//   g
			// u   p
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == Red)
			{
				//情况1
				//变色
				parent->_col = Black;
				uncle->_col = Black;
				grandfather->_col = Red;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况2,3
				if (cur == parent->_right)
				{
					//左单旋场景
					   //  g
			          // u   p
				     //       c
					RotateL(grandfather);
					//变色
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else
				{
					//右左双旋场景
					//   g
				    // u   p
				    //   c
					RotateR(parent);
					RotateL(grandfather);
					//变色
					cur->_col = Black;
					grandfather->_col = Red;
				}
				//不需要继续向上更新
				break;
			}
		}
	}
	//跟的颜色可能被调整为红色,最后一步改为黑色即可
	_root->_col = Black;
	return true;
}
//右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* pparent = parent->_parent;

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

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;
	parent->_parent = subR;
	subR->_left = parent;
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
}

2.4,红黑树的验证

我们只需验证形成的树是否满足红黑树的四条规则即可。

其中,规则1和规则 2可以直接检查。

对于规则3,我们可以进行前序遍历,遍历到一个节点,判断该节点的颜色,再判断它的两个孩子的颜色,这样做太麻烦了。我们可以反过来,遍历到一个节点,如果他是红色的,判断它的父亲节点是否为黑色。

对于规则4,我们可以先从根开始找到一条路径上黑色节点的个数refNum,再对整棵树进行前序遍历,用变量 blackNum记录黑色节点的个数,当遍历到空的时候,与refNum比较即可。

bool Check(Node* root, int BlackNum, int num)
{
	if (root == nullptr)
	{
		if (BlackNum != num)
			return false;
		return true;
	}

	if (root->_col == Red && root->_parent->_col == Red)
		return false;
	if (root->_col == Black)
		BlackNum++;
	return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
}
//验证
bool isbalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == Red)
		return false;
	int num = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == Black)
			num++;
		cur = cur->_left;
	}
	return Check(_root, 0, num);
}

三,整体代码

enum color
{
	Red,
	Black
};

template<class k,class v>
struct RBTreeNode
{
	RBTreeNode(const pair<k,v>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
	{}
	RBTreeNode<k, v>* _left;
	RBTreeNode<k, v>* _right;
	RBTreeNode<k, v>* _parent;
	pair<k, v> _kv;
	color _col;
};

template<class k,class v>
class RBTree
{
	typedef RBTreeNode<k, v> Node;
public:
	bool Insert(const pair<k, v>& kv)
	{
		//插入根节点,color->Black
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = Black;
			return true;
		}
		//根据搜索树的规则查找插入位置
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		cur->_col = Red;
		//与父亲节点连接好
		if (parent->_kv.first < kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		//颜色处理+旋转
		while (parent&& parent->_col == Red)
		{
			//g
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				//p为g的左边
				//    g
				//  p   u
				Node* uncle = grandfather->_right;
				//叔叔存在且为红,情况1
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = Black;
					uncle->_col = Black;
					grandfather->_col = Red;
					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//情况2,3
					//叔叔不存在或者叔叔为黑
					//u为黑,则c之前是黑的
					//u不存在,则c是新插入的
					if (cur == parent->_left)
					{
						//右单旋场景
						 //   g
				        //  p   u
			           // c
						RotateR(grandfather);
						//变色
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						//左右双旋场景
						//    g
						//  p   u
					   //     c
						RotateL(parent);
						RotateR(grandfather);
						//变色
						cur->_col = Black;
						grandfather->_col = Red;
					}
					//不需要进行向上更新
					break;
				}
			}
			else
			{
				//p为g的右边
				//   g
				// u   p
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == Red)
				{
					//情况1
					//变色
					parent->_col = Black;
					uncle->_col = Black;
					grandfather->_col = Red;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//情况2,3
					if (cur == parent->_right)
					{
						//左单旋场景
						   //  g
				          // u   p
					     //       c
						RotateL(grandfather);
						//变色
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						//右左双旋场景
						//   g
					    // u   p
					    //   c
						RotateR(parent);
						RotateL(grandfather);
						//变色
						cur->_col = Black;
						grandfather->_col = Red;
					}
					//不需要继续向上更新
					break;
				}
			}
		}
		//跟的颜色可能被调整为红色,最后一步改为黑色即可
		_root->_col = Black;
		return true;
	}
	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		parent->_parent = subR;
		subR->_left = parent;
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	int Height()
	{
		return _Height(_root);
	}
	int size()
	{
		return _size(_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 isbalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col == Red)
			return false;
		int num = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == Black)
				num++;
			cur = cur->_left;
		}
		return Check(_root, 0, num);
	}
private:
	bool Check(Node* root, int BlackNum, int num)
	{
		if (root == nullptr)
		{
			if (BlackNum != num)
				return false;
			return true;
		}

		if (root->_col == Red && root->_parent->_col == Red)
			return false;
		if (root->_col == Black)
			BlackNum++;
		return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
	}
	int _size(Node* root)
	{
		if (root == nullptr)
			return 0;
		return _size(root->_left) + _size(root->_right) + 1;
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
	Node* _root=nullptr;
};

四,测试代码

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 << t.isbalance() << endl;
}
int main()
{
	TestRBTree1();
	return 0;
}

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

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

相关文章

UDP报文格式

UDP是传输层的一个重要协议&#xff0c;他的特性有面向数据报、无连接、不可靠传输、全双工。 下面是UDP报文格式&#xff1a; 1&#xff0c;报头 UDP的报头长度位8个字节&#xff0c;包含源端口、目的端口、长度和校验和&#xff0c;其中每个属性均为两个字节。报头格式为二…

解锁转型密码:不同方向的技能与素质修炼手册

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 解锁…

HTML中最基本的东西

本文内容的标签&#xff0c;将是看懂HTML的最基本之基本 &#xff0c;是跟您在写文章时候一样内容。一般想掌握极其容易&#xff0c;但是也要懂得如何使用&#xff0c;过目不忘&#xff0c;为手熟尔。才是我们学习的最终目的。其实边看边敲都行&#xff0c;或者是边看边复制粘贴…

NodeJS | 搭建本地/公网服务器 live-server 的使用与安装

目录 介绍 安装 live-server 安装方法 安装后的验证 环境变量问题 Node.js 环境变量未配置正确 全局安装的 live-server 路径未添加到环境变量 运行测试 默认访问主界面 访问文件 报错信息与解决 问题一&#xff1a;未知命令 问题二&#xff1a;拒绝脚本 公网配置…

【excel】VBA股票数据获取(搜狐股票)

文章目录 一、序二、excel 自动刷新股票数据三、付费获取 一、序 我其实不会 excel 的函数和 visual basic。因为都可以用matlab和python完成。 今天用了下VBA&#xff0c;还挺不错的。分享下。 上传写了个matlab获取股票数据的&#xff0c;是雅虎财经的。这次是搜狐股票的数…

Redis的过期策略、内存淘汰机制

Redis只能存5G数据&#xff0c;可是你写了10G&#xff0c;那会删5G的数据。怎么删的&#xff1f;还有&#xff0c;你的数据已经设置了过期时间&#xff0c;但是时间到了&#xff0c;为什么内存占用率还是比较高? 一、Redis的过期策略 Redis采用的是定期删除惰性删除策略。 1…

C语言结构体漫谈:从平凡中见不平凡

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文《1》 结构体的两种声明一、结构…

redis(2:数据结构)

1.String 2.key的层级格式 3.Hash 4.List 5.Set 6.SortedSet

OCP使用中的常见问题与解决方法

OCP的常见问题 页面卡顿&#xff1a; 遇到页面卡顿的问题时&#xff0c;首先需要区分是全局性的卡顿&#xff0c;即所有页面都出现延迟或响应缓慢&#xff0c;还是仅限于特定的监控页面。 监控数据看不到: 需要明确是全部数据都无法查看&#xff0c;还是仅限于特定集群的数…

第三十八章 Spring之假如让你来写MVC——适配器篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

【开源免费】基于SpringBoot+Vue.JS企业OA管理系统(JAVA毕业设计)

本文项目编号 T 135 &#xff0c;文末自助获取源码 \color{red}{T135&#xff0c;文末自助获取源码} T135&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

(12)springMVC文件的上传

SpringMVC文件上传 首先是快速搭建一个springMVC项目 新建项目mvn依赖导入添加webMoudle添加Tomcat运行环境.在配置tomcat时ApplicationContext置为"/"配置Artfact的lib配置WEB-INF配置文件&#xff08;记得添加乱码过滤&#xff09;配置springmvc-servlet文件&…

【华为路由/交换机的ftp文件操作】

华为路由/交换机的ftp文件操作 PC&#xff1a;10.0.1.1 R1&#xff1a;10.0.1.254 / 10.0.2.254 FTP&#xff1a;10.0.2.1 S1&#xff1a;无配置 在桌面创建FTP-Huawei文件夹&#xff0c;里面创建config/test.txt。 点击上图中的“启动”按钮。 然后ftp到server&#xff0c;…

虚拟拨号技术(GOIP|VOIP)【基于IP的语音传输转换给不法分子的境外来电披上一层外衣】: Voice over Internet Protocol

文章目录 引言I 虚拟拨号技术(GOIP|VOIP)原理特性:隐蔽性和欺骗性II “GOIP”设备原理主要功能III 基于IP的语音传输 “VOIP” (Voice over Internet Protocol)IV “断卡行动”“断卡行动”目的电信运营商为打击电诈的工作V 知识扩展虚拟号保护隐私虚拟运营商被用于拨打骚扰…

获取当前页面的url相关信息

引言&#xff1a;如何通过javascript获取当前html页面的链接信息 let currentUrl window.location.href; let protocol window.location.protocol; // 协议 let host window.location.host; // 主机名和端口 let hostname window.location.hostname; // 主机名 le…

【C++】size_t全面解析与深入拓展

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;一、什么是size_t&#xff1f;为什么需要size_t&#xff1f; &#x1f4af;二、size_t的特性与用途1. size_t是无符号类型示例&#xff1a; 2. size_t的跨平台适应性示例对…

Quinlan C4.5剪枝U(0,6)U(1,16)等置信上限如何计算?

之前看到Quinlan中关于C4.5决策树算法剪枝环节中,关于错误率e置信区间估计,为啥 当E=0时,U(0,1)=0.75,U(0,6)=0.206,U(0,9)=0.143? 而当E不为0时,比如U(1,16)=0.157,如图: 关于C4.5决策树,Quinlan写了一本书,如下: J. Ross Quinlan (Auth.) - C4.5. Programs f…

怎么进行论文选题?有没有AI工具可以帮助~

论文选题听起来简单&#xff0c;做起来难&#xff01;尤其是对于我们这群即将毕业的“学术小白”。记得那天导师布置完任务&#xff0c;我整个人就陷入了深深的沉思&#xff08;其实是发呆&#xff09;。直到室友神秘兮兮地告诉我&#xff1a;“你知道AI现在能帮人选题了吗&…

windows 极速安装 Linux (Ubuntu)-- 无需虚拟机

1. 安装 WSL 和 Ubuntu 打开命令行&#xff0c;执行 WSL --install -d ubuntu若报错&#xff0c;则先执行 WSL --update2. 重启电脑 因安装了子系统&#xff0c;需重启电脑才生效 3. 配置 Ubuntu 的账号密码 打开 Ubuntu 的命令行 按提示&#xff0c;输入账号&#xff0c;密…

TCP-IP详解卷 TCP的超时与重传

TCP-IP详解卷1-21&#xff1a;TCP的超时与重传&#xff08;Timeout and Retransmission&#xff09; 一&#xff1a;介绍 1&#xff1a; 与数据链路层的ARQ协议相类似&#xff0c;TCP使用超时重发的重传机制。 即&#xff1a;TCP每发送一个报文段&#xff0c;就对此报文段设置…