红黑树的 概念性质 和 详解实现(插入旋转等)

news2024/11/15 19:54:26

文章目录

  • 概念
  • 满足的条件性质
  • 实现
    • 红黑树的定义
    • 红黑树节点
    • 插入操作
      • 情况一
      • 情况二
      • 情况三
      • Insert()总代码
    • 其余操作
      • 左右单旋
      • RotateL 左单旋
      • RotateR 右单旋
      • prevCheck 红黑树性质检测
      • isBalance 红黑树平衡判断
      • InOrder 中序遍历
  • 完整代码

概念

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

满足的条件性质

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

这些性质保证了红黑树的高度不会超过 2log(n+1) ,其中 n 是树中节点的个数。因此,红黑树可以在O(log n)时间内完成插入、删除、查找等操作,是一种非常高效的数据结构。

这里提一下 NIL节点


nil 节点就是空节点,在红黑树的实现中,nil 节点代替二叉树中的 NULL:叶子节点的左节点和右节点指针都指向 nil 节点;只一个子树的节点,其另外一个子节点指针也指向 nil 节点;根节点的父节点也指向 nil 节点。


当我们从红黑树中删除一个节点时,需要考虑其子树的平衡问题,否则可能导致树不平衡,进而破坏红黑树性质。如果该节点只有一个子节点,我们需要将该节点的子节点替代该节点的位置。如果该节点没有子节点,则在其位置插入一个NIL节点,起到保持红黑树平衡的作用。

实现

红黑树的定义

// 枚举类型 标明颜色
// 节点颜色
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)
	{}
};

// 红黑树定义
template <class K, class V>
struct RBTree
{
	//typedef节点
	typedef RBTreeNode<K, V> Node;
public:
	// 关键:插入操作
	bool Insert(const pair<K, V> kv)
	{}
	
	// 中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	
	// 判断平衡
	bool IsBalance()
	{}

private:
	// 中序遍历
	void _InOrder(Node* root)
	{}

	// 检查红黑树
	// 会先判断当前节点是否为nullptr
	// 如果是,则说明检查到叶节点,记录当前节点中黑色节点的数量,并且与之前的比较
	// 如果不相等,则输出错误信息并返回false。如果相等,则返回true。
	bool prevCheck(Node* root, int blackNum, int& benchmark)
	{}

	// 左单旋
	void RotateL(Node* parent)
	{}

	// 右单旋
	void RotateR(Node* parent)
	{}

private:
	Node* _root = nullptr;
};

红黑树节点

  • _left:表示当前节点的左子节点指针;
  • _right:表示当前节点的右子节点指针;
  • _parent:表示当前节点的父节点指针;
  • _kv:表示当前节点存储的键值对;
  • _col:表示当前节点的颜色,用于约束红黑树的性质。
  • 构造函数:用于初始化成员变量;
// 枚举类型 标明颜色
// 节点颜色
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)
	{}
};

插入操作

我们将插入操作分为三个部分进行:

1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true

// 第一部分
// 1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true。
if (_root == nullptr)
{
	_root = new Node(kv);
	_root->_col = BLACK; //根节点为黑色
	return true;
}

2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。

  • 由于红黑树本身是一种平衡二叉树,在插入操作中先进行平衡二叉树一样的操作,第二部分就是进行平衡二叉树的节点插入操作。
// 第二部分
// 2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。


Node* parent = nullptr;
Node* cur = _root;
// 如果cur->_kv.first小于kv.first,则将cur指向右子节点,并且parent指向cur;否则将cur指向左子节点,并且parent指向cur。
// 如果插入的键值已存在,则直接返回false
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;
	}
}

// 判断kv应该插入parent的左边or右边
cur = new Node(kv);
if (kv.first > parent->_kv.first) {
	parent->_right = cur;
}
else {
	parent->_left = cur;
}

cur->_parent = parent;

3. 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的左子节点,则需要进行右旋,然后进行变色操作,使之平衡。

对于红黑树的平衡我们分为三种情况:

首先我们令 插入的节点为 cur父节点为 parentparent的父节点为grandparent父节点同一层的另一个节点grandparent的另一个子节点)为uncle


情况一

情况一:

  • 当新节点的父节点和叔节点都是红色时,需要将父节点和叔节点变为黑色,将祖父节点变为红色,并将新节点指向祖父节点,然后继续向上处理。

cur为红色,parent为红色,grandparent为黑色,uncle存在且为红色

在这里插入图片描述

在这里插入图片描述

if (uncle && uncle->_col == RED)
{
	// 变色
	parent->_col = uncle->_col = BLACK;
	grandfather->_col = RED;
	
	// 继续向上处理
	cur = grandfather;
	parent = cur->_parent;
}
// 根节点变为黑色
_root->_col = BLACK;

情况二

情况二:

  • 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的右子节点,则需要进行左旋,变成第三种情况。

cur为红色,parent为红色,grandparent为黑色,uncle不存在 / 存在且为黑色,且cur为parent的右子节点

u不存在

在这里插入图片描述
u存在且为黑色

在这里插入图片描述
在这里插入图片描述

// 情况二 : cur为红,p为红,g为黑,u不存在 / u为黑
// -> 右单旋+变色 -> 右单旋+p变黑,g变红
if (cur == parent->_left)
{
	// 右单旋 祖父节点
	RotateR(grandfather);
	parent->_col = BLACK;
	grandfather->_col = RED;
}

情况三

情况三:

  • 当新节点的父节点是红色,而叔节点是黑色(或者不存在),并且新节点是其父节点的左子节点,则需要进行右旋,然后进行变色操作,使之平衡。

cur为红色,parent为红色,grandparent为黑色,uncle不存在 / 存在且为黑色,且cur为parent的左子节点

u不存在

在这里插入图片描述
u存在且为黑

在这里插入图片描述
在这里插入图片描述

// 情况三 : cur为红,p为红,g为黑,u不存在 / u存在且为黑
// 双旋 + 变色
if (cur == parent->_right)
{
	RotateL(parent);
	RotateR(grandfather);
	cur->_col = BLACK;
	grandfather->_col = RED;
}

Insert()总代码

  1. 根据上文分析的三种情况总结并补充其余细节写出插入操作的总实现
bool Insert(const pair<K, V> kv)
{
	// 二叉平衡树的插入操作

	// 1. 判断根节点是否为空,如果为空,则直接将新节点作为根节点创建并返回true。
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK; //根节点为黑色
		return true;
	}


	// 2. 在树中寻找待插入节点要插入的位置,当遍历到叶子节点时,将新节点加入到该叶子节点的左边或右边,成为该叶子节点的子节点。

	Node* parent = nullptr;
	Node* cur = _root;
	// 根据 kv.first 的大小逐步向左或右寻找合适的插入位置:
	// 如果cur->_kv.first小于kv.first,则将cur指向右子节点,并且parent指向cur;否则将cur指向左子节点,并且parent指向cur。
	// 如果插入的键值已存在,则直接返回false
	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应该插入parent的左边or右边
	cur = new Node(kv);
	if (kv.first > parent->_kv.first) {
		parent->_right = cur;
	}
	else {
		parent->_left = cur;
	}

	cur->_parent = parent;


	// 3. 插入新节点后,需要通过旋转和变色实现红黑树的自平衡。
	// 如果新节点的父节点是黑色的,则直接插入不需要调整。如果父节点是红色的,则需要进行适当的旋转和变色使之平衡。
	while (parent && parent->_col == RED) 
	{
		// 定义祖父节点
		Node* grandfather = parent->_parent;
		assert(grandfather);
		assert(grandfather->_col == BLACK); //根据红黑树的性质,此时grandparent节点应该是黑色的

		// 平衡主要看 uncle节点
		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;
			}
			else
			{
				// 情况二 : cur为红,p为红,g为黑,u不存在 / u为黑
				// -> 右单旋+变色 -> 右单旋+p变黑,g变红
				if (cur == parent->_left)
				{
					// 右单旋 祖父节点
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}

				// 情况三 : cur为红,p为红,g为黑,u不存在 / u存在且为黑
				// 双旋 + 变色
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}

		// (parent == grandfather->_right)
		else 
		{
			Node* uncle = grandfather->_left;
			// 情况一
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				// 继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else 
			{
				// 情况二
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	// 根节点为黑色
	// 返回true;
	_root->_col = BLACK;
	return true;
}

其余操作

左右单旋

这里不再进行旋转的详细解释,具体在下面的文章中:

AVLTree 的插入旋转操作

RotateL 左单旋

void RotateL(Node* parent)
{
	// 保存父节点右子树的左子树 subRL
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	// 将 parent 的右子树指向 subRL
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	// 调整 subR 和 parent 的关系
	subR->_left = parent;
	parent->_parent = subR;

	// 如果此时 parent 是根节点,修改根节点为 subR
	if (_root == parent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		// 将 parent 的父节点 ppNode 指向 subR
		Node* ppNode = parent->_parent;
		if (ppNode->_left == parent)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}

		subR->_parent = ppNode;
	}
}

RotateR 右单旋

// 右单旋:以 parent 为轴心,将其左子树 subL 向右旋转(右子树不变)
void RotateR(Node* parent)
{
	// 保存父节点左子树的右子树 subLR
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	
	// 将 parent 的左子树指向 subLR
	parent->_left = subLR;
	if (subLR)
	{
	    subLR->_parent = parent;
	}
	
	// 调整 subL 和 parent 的关系
	subL->_right = parent;
	parent->_parent = subL;
	
	// 如果此时 parent 是根节点,修改根节点为 subL
	if (_root == parent)
	{
	    _root = subL;
	    subL->_parent = nullptr;
	}
	else
	{
	    // 将 parent 的父节点 ppNode 指向 subL
	    Node* ppNode = parent->_parent;
	    if (ppNode->_left == parent)
	    {
	        ppNode->_left = subL;
	    }
	    else
	    {
	        ppNode->_right = subL;
	    }
	
	    subL->_parent = ppNode;
	}
}

prevCheck 红黑树性质检测

  • 红黑树的性质检验函数 prevCheck,其功能是检查红黑树的各个节点是否符合红黑树的特征。
// 检查红黑树
// 会先判断当前节点是否为nullptr
// 如果是,则说明检查到叶节点,记录当前节点中黑色节点的数量,并且与之前的比较
// 如果不相等,则输出错误信息并返回false。如果相等,则返回true。
bool prevCheck(Node* root, int blackNum, int& benchmark)
{
	if (root == nullptr)
	{
		// 如果benchmark的值为0,说明之前没有记录过当前红黑树路径中的黑色节点数量
		// 这时将benchmark的值设置为当前黑色节点的数量。
		if (benchmark == 0) {
			benchmark = blackNum;
			return true;
		}

		// 如果当前路径上的黑色节点数量与之前记录的benchmark不相等,输出错误信息并返回false
		if (blackNum != benchmark) {
			cout << "某条路径的黑色节点数量不相等" << endl;
			return false;
		}else { // 当前路径上的黑色节点数量与之前记录的benchmark相等,返回true
			return true;
		}
	}

	// 遇到黑色节点,++blackNum
	if(root->_col==BLACK)
	{
		++blackNum;
	}

	// 遇到连续的红节点,则证明有错
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "error: 存在连续的红色节点" << endl;
		return false;
	}

	return prevCheck(root->_left, blackNum, benchmark)
		&& prevCheck(root->_right, blackNum, benchmark);
}

isBalance 红黑树平衡判断

  • 红黑树的平衡性检验函数 IsBalance。
bool IsBalance()
{
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
	{
		cout << "false: 根节点为红色" << endl;
		return false;
	}

	// 黑色节点数量 基准值
	int benchmark = 0;
	// 也可行 ↓
	/*Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			++benchmark;

		cur = cur->_left;
	}*/

	return prevCheck(_root, 0, benchmark);
}

InOrder 中序遍历

public:
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);
}

完整代码

gitee - RBTree代码

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

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

相关文章

HNU-操作系统OS-ucoreLab系列-感悟

谨以此片篇,献给熬夜的8个晚上,以及逝去的时光。 感悟: 今天结束了所有的Lab实验(2023.6.3),感慨万千。 喜是这个实验终于结束了,悲是其实有好多地方我都没有理解。 应该指出,由于验收的助教学长学姐们的宽容,HNU实际上在验收这一块的要求还是比较低的。 但是这个…

第三章 Electron 使用Koa以及Koa-Router

一、Koa是什么 &#x1f447; &#x1f447; &#x1f447; 据网上的资料显示&#xff0c;Koa 是下一代的 Node.js 的 Web 框架。是express原班人马打造,同样用于构建服务端web application的。旨在提供一个更小型、更富有表现力、更可靠的 Web 应用和 API 的开发基础。扯这些…

在输入URL后,前端浏览器的工作流程

这是一个经久不衰的面试题&#xff0c;整理一下。 浏览器 浏览器是一个多进程的架构。 主进程&#xff1a;主要适用于界面的显示&#xff0c;用户的交互&#xff0c;子进程管理。 渲染进程&#xff1a;将HTML&#xff0c;css&#xff0c;JS转换成页面&#xff0c;V8引擎以及…

redisson 随笔 0-入门

0. 虽说时运不佳&#xff0c;仍欲提桶跑路 分布式锁的常见实现方案 常用锁的用例 runoob Lua教程 对于分布式锁的实现方案&#xff0c;本文如标题所言&#xff0c;简单梳理了redisson的实现方案 redisson 也是基于redis的多个命令组合来实现的&#xff0c;为保证执行多个命…

项目实战:基于Linux的Flappy bird游戏开发

一、项目介绍 项目总结 1.按下空格键小鸟上升&#xff0c;不按小鸟下落 2.搭建小鸟需要穿过的管道 3.管道自动左移和创建 4.小鸟撞到管道游戏结束 知识储备 1.C语言 2.数据结构-链表 3.Ncurses库 4.信号机制 二、Ncurses库介绍 Ncurses是最早的System V Release 4.0 (…

F. Editorial for Two(二分答案+反悔贪心)

F. Editorial for Two&#xff08;二分答案反悔贪心&#xff09; F. Editorial for Two 1、问题 给定一个 n n n和 k k k&#xff0c;以及一个长度为 n n n数组。现在从 n n n个数中&#xff0c;挑出 k k k个数&#xff0c;称作个子序列。然后将这个子序列分成两部分&#x…

干翻Mybatis源码系列之第八篇:Mybatis二级缓存的创建和存储

给自己的每日一句 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽…

C++之---树/数据结构

一、树 什么是树&#xff1f; 1.1 树&#xff08;Tree&#xff09;是n&#xff08;n>0&#xff09;个结点的有限集。n0时称为空树。在任意一棵非空树中&#xff1a; &#xff08;1&#xff09; 有且仅有一个特定的称为根&#xff08;Root&#xff09;的结点&#xff1b; &am…

CodeForces..最新行动.[中等].[遍历].[判断]

题目描述&#xff1a; 题目解读&#xff1a; "最近操作"字段会显示最近操作的n个文件。 最初有编号文件1&#xff0c;2&#xff0c;... n在"最近操作"字段&#xff0c;还有其他无限多个文件不在。 当某个文件pi发生操作时&#xff1a; 如果它位于“最近…

小红书账号矩阵优化软件

小红书账号矩阵优化软件 大家有关注过品牌在⼩红书上的打法有哪些吗&#xff1f; #品牌营销#小红书运营#爆文拆解#品牌投放#爆品打造 我们如果确定了我们要去做小红书&#xff0c;那我到底该怎么去做&#xff1f;现在小红书对我们目前这些品牌来说&#xff0c;你们是作为把它…

Allegro16.6详细教程(二)

R.3-D Viewer 3-D Viewer,可以直接在allegro中看到board file的3-D顯示效果。3-D Viewer對於PCB Editor Products,只有環境變數中的OpenGL顯示功能開啟後才有效,而對於APD/SiP是無效的。 2.3-D viewer是在一個獨立的視窗中打開的。3-D environment環境支援多種顯示內容的過…

Spring Cloud Alibaba - Nacos源码分析(二)

目录 一、Nacos服务端服务注册 1、服务端调用接口 2、服务注册 instanceServiceV2.registerInstance EphemeralClientOperationServiceImpl.registerInstance ServiceManager clientManager Client实例AbstractClient ClientOperationEvent.ClientRegisterServiceEven…

2023《中国好声音》全国巡演Channel[V]歌手大赛广州赛区半决赛圆满举行!

2023年5月27-28日&#xff0c;由腾扬广告、Channel[V]、盛娱星汇联合主办的2023《中国好声音》全国巡演Channel[V]歌手大赛广州赛区半决赛在广州番禺天河城正式打响&#xff0c;自广州赛区赛事启动以来&#xff0c;汇集了近五千名音乐人参与其中&#xff0c;历经2个多月、超40场…

【数据库复习】第七章 数据库设计

数据库设计的过程(六个阶段) ⒈需求分析阶段 准确了解与分析用户需求&#xff08;包括数据与处理&#xff09; 最困难、最耗费时间的一步 ⒉概念结构设计阶段 整个数据库设计的关键 通过对用户需求进行综合、归纳与抽象&#xff0c;形成一个独立于具体DBMS的概念模型 ⒊…

基于微信小程序蛋糕店商城管理系统的设计与实现

1&#xff1a;后端采用技术 SpringBoot 、Mybatis、Mybatis-plus、Redis、阿里云短信息服务、Hutool 邮箱服务、WebSocket通讯服务、OSS对象存储服务、支付宝沙箱服务&#xff0c;接口简单限流、简单定时任务。。。。。。 2&#xff1a;前端采用技术 Vue2、Vue2-uploader组件、…

[图表]pyecharts模块-日历图

[图表]pyecharts模块-日历图 先来看代码&#xff1a; import random import datetimeimport pyecharts.options as opts from pyecharts.charts import Calendarbegin datetime.date(2017, 1, 1) end datetime.date(2017, 12, 31) data [[str(begin datetime.timedelta(d…

Leetcode 110-平衡二叉树

1. 递归法求解 递归三部曲&#xff1a; 确定递归函数的参数及其返回值确定终止条件确定单层递归逻辑 深度&#xff1a;从上往下 高度&#xff1a;从下往上 1.1 根据深度求解 构建求二叉树节点深度的函数&#xff08;后序遍历&#xff09;递归求该树是否是平衡二叉树&#…

国产化麒麟linux系统开发编译常见问题汇总

团队自研股票软件关注威信龚总号&#xff1a;QStockView&#xff0c;下载 1 问题处理 1.1 Unknown module in QT:QJsonDocument 缺少QJsonDocument 解决方法&#xff1a; Pro文件中加上 QTcore; 播放器库问题 1.2 代码中汉字乱码需要设置文件编码格式 原因分析&…

2023-06-03:redis中pipeline有什么好处,为什么要用 pipeline?

2023-06-03&#xff1a;redis中pipeline有什么好处&#xff0c;为什么要用 pipeline&#xff1f; 答案2023-06-03&#xff1a; Redis客户端执行一条命令通常包括以下四个阶段&#xff1a; 1.发送命令&#xff1a;客户端将要执行的命令发送到Redis服务器。 2.命令排队&#…

内网安全:Cobalt Strike 工具 渗透多层内网主机.(正向 || 反向)

内网安全&#xff1a;Cobalt Strike 工具 渗透多层内网主机. Cobalt Strike 是一款以 metasploit 为基础的 GUI 的框架式渗透工具&#xff0c;又被业界人称为 CS。拥有多种协议主机上线方式&#xff0c;集成了端口转发&#xff0c;服务扫描&#xff0c;自动化溢出&#xff0c;…