数据结构--AVL树(平衡二叉树)

  ✅博客主页:爆打维c-CSDN博客​​​​​​ 🐾

🔹分享c、c++知识及代码 🐾

🔹Gitee代码仓库 五彩斑斓黑1 (colorful-black-1) - Gitee.com

一、AVL树是什么?(含义、性质)

1.AVL树的概念

        AVL树是最先发明的自平衡二叉树,AVL树是⼀颗高度平衡二叉搜索树,通过控制高度差去控制平衡。为什么叫它AVL树?AVL 是大学教授 G.M. Adelson-Velsky 和 E.M. Landis 名称的缩写,他们提出了平衡二叉树的概念,为了纪念他们,将 平衡二叉树 称为 AVL树。在介绍AVL树之前,我们先介绍一下二叉搜索树的性质。

二叉搜索树或者是⼀棵空树,或者是具有以下性质的⼆叉树:
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
• 它的左右⼦树也分别为⼆叉搜索树

        如图所示,这是一颗二叉搜索树,特点就是左子树上所有结点均小于等于根节点的值,而右子树上所有结点均大于等于根节点的值。

2.AVL树的性质

AVL是⼀颗空树或具备下列性质的⼆叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树的高度差的绝对值不超过1
  • 树高为O(log2N)
2.1 平衡因子(Balance Factor,简写为bf)

平衡因子(bf):结点的右子树的深度减去左子树的深度。

即: 结点的平衡因子 = 右子树的高度 - 左子树的高度 。

在 AVL树中,所有节点的平衡因子都必须满足: -1<=bf<=1;

如图所示,以结点10和根节点8为例,10:bf=1-0=1,8:bf=2-3=-1

请记住:我们通过平衡因子来控制树的平衡!

2.2 区分是否是AVL树

下面是AVL树和非AVL树对比的例图:右边的树虽然是一个二叉搜索树 但很明显不符合AVL树的性质2 即左右子树的高度差的绝对值不超过1,所以右边的树并不是AVL树,而左边符合AVL树的性质。


二、AVL的使用场景以及查找效率

1.AVL树的作用

        我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。

例如:我们按顺序将一组数据 1,2,3,4,5,6 分别插入到一颗空二叉查找树和AVL树中,插入的结果如下图:

由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(log2N)。高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因。

2.与二分查找的区别

        当我们在内存中存储和搜索数据,用AVL树就能完美满足我们的需求,它搜索速度很快,效率也很高,因为它是一个平衡二叉搜索树,高度为O(log2N) ,查找效率也为O(log2N) ,AVL树增删查改时间复杂度为:O(N)

        虽然⼆分查找也可以实现 O(log2N) 级别的查找效率,但是二分查找有两大缺陷:1. 需要存储在支持下标随机访问的结构中,并且数据要有序。2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据。

这里也就体现出了平衡⼆叉搜索树的价值。


三、如何实现一个AVL树(底层代码逻辑)

1.AVL树的结构

我们首先定义一个AVL结点的结构体和AVL树的类,代码如下

template<class K,class V>
struct AVLTreeNode{pair<K, V> _kv;AVLTreeNode<K, V>* _parent;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;int _bf; //平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_bf(0){}
};template<class K,class V>
class  AVLTree {typedef AVLTreeNode<K, V> Node;
public://.......
private:Node* _root = nullptr;
};

2. AVL树的插入

2.1插入的过程
  1. 根据值的大小(二叉搜索树的规则) 寻找空结点的位置 找到位置后开辟新空间 
  2. 新增结点以后,只会影响祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可以停⽌了,具体情况我们下⾯再详细分析。
  3. 更新平衡因⼦过程中没有出现问题,则插⼊结束
  4. 更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后本质调平衡的同时,本质降低了⼦树的⾼度,不会再影响上⼀层,所以插⼊结束。
2.2.平衡因子更新

更新原则:
• 平衡因⼦ = 右子树高度-左子树高度
• 只有⼦树⾼度变化才会影响当前结点平衡因⼦。
• 插⼊结点,会增加⾼度,所以新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在parent的左⼦树,parent平衡因⼦--
• parent所在⼦树的⾼度是否变化决定了是否会继续往上更新


更新停止条件:(三种情况)
更新后parent的平衡因子等于0,更新中parent的平衡因⼦变化为-1->0 或者 1->0,说明更新前parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会影响parent的父亲结点的平衡因子,更新结束。
更新后parent的平衡因子等于1 或 -1更新前更新中parent的平衡因⼦变化为0->1 或者 0->-1,说明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新。
更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因⼦变化为1->2 或者 -1->-2,说明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插⼊结束。

下面给出插入过程的部分代码,旋转部分在第三小点着重讲解

bool Insert(const pair<K, V>& kv) {//树为空 直接插入if (!_root) {_root = new Node(kv); //生成新节点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;//控制平衡while (parent) {if (parent->_left == cur) {parent->_bf--;}else if (parent->_right == cur) {parent->_bf++;}else assert("balance false");if (parent->_bf == 0) {break; //此时已经达到平衡 不影响上面父节点}else if (parent->_bf == 1 || parent->_bf == -1) {//0-> 1 |-1 需要继续向上cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2) {//此时已经不符合平衡二叉搜索树的条件 需要旋转//....(旋转逻辑代码)break;}else {assert("balance false!");}}return true;
}

3.AVL树的旋转

一边子树的高度过高会导致二叉搜索树的不平衡,所以我们需要旋转来降低树高度

旋转的原则
1. 保持搜索树的规则
2. 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度
旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

3.1右单旋

当前结点cur(5)为左子树高平衡因子为-1且parent(10)的平衡因子为-2时,需要右单旋来达到平衡.

以parent为旋转点 将sublr变为parent(10)的左子树 parent(10)变为cur(5)的右子树 此时平衡

3.2左单旋

当前结点cur(15)右子树高平衡因子为1且parent(10)的平衡因子为2时,需要左单旋来达到平衡.

以parent为旋转点 将subrl变为parent(10)的右子树 parent(10)变为cur(15)的左子树 此时平衡

3.3左右双旋

当为以下情况 结点cur右子树高平衡因子为1且parent的平衡因子为-2时,需要左右双旋

先以subl为旋转点左单旋 再以parent为旋转点右单旋

3.4右左双旋

当为以下情况 结点cur左子树高平衡因子为-1且parent的平衡因子为2时,需要右左双旋

先以subr为旋转点右单旋 再以parent为旋转点左单旋

上述过程最好在草稿纸上推演一边,一般情况很多,这里只给出抽象情况,感兴趣的可以自己分析

总结

在插入的过程中,会出现一下四种情况破坏AVL树的特性,我们可以采取如下相应的旋转。

插入位置状态操作
在结点T的左结点(L)的 左子树(L) 上做了插入元素左左型右旋
在结点T的左结点(L)的 右子树(R) 上做了插入元素左右型左右旋
在结点T的右结点(R)的 右子树(R) 上做了插入元素右右型左旋
在结点T的右结点(R)的 左子树(L) 上做了插入元素右左型右左旋

注意:
T 表示 平衡因子(bf)绝对值大于1的节点。

不知道大家发现规律没,这个规则还是挺好记,下面来个图示:

插入代码最终完整版

bool Insert(const pair<K, V>& kv) {//树为空 直接插入if (!_root) {_root = new Node(kv); //生成新节点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;//控制平衡while (parent) {if (parent->_left == cur) {parent->_bf--;}else if (parent->_right == cur) {parent->_bf++;}else assert("balance false");if (parent->_bf == 0) {break; //此时已经达到平衡 不影响上面父节点}else if (parent->_bf == 1 || parent->_bf == -1) {//0-> 1 |-1 需要继续向上cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2) {//此时已经不符合平衡二叉搜索树的条件 需要旋转if (parent->_bf == -2&&cur->_bf== -1) { //最左侧树不平衡 需要右旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1) { //最右侧树不平衡 需要左旋RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1) { //需要左右旋RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1) { //需要右左旋RotateRL(parent);}else {assert("false");}break;}else {assert("balance false!");}}return true;
}void RotateR(Node* parent) {Node* pp = parent->_parent;Node* subl = parent->_left;Node* sublr = subl->_right;//链接过程 发生变化的可能有6个点parent->_left = sublr;parent->_parent = subl;subl->_right = parent;if (sublr) { //如果sublr存在 那么父指向psublr->_parent = parent;}//改变pp以及subl的父指向if (pp) {if (pp->_left == parent) {pp->_left = subl;}else {pp->_right = subl;}subl->_parent = pp;}else { //pp为空 parent为根结点 此时subl为新根节点_root = subl;subl->_parent = nullptr;}parent->_bf = subl->_bf = 0; //旋转后 已经平衡 改变平衡因子
}
void RotateL(Node* parent) {Node* pp = parent->_parent;Node* subr = parent->_right;Node* subrl = subr->_left;parent->_right = subrl;parent->_parent = subr;subr->_left = parent;if (subrl) { //如果subrl存在 那么父指向psubrl->_parent = parent;}//改变pp以及subr的父指向if (pp) {if (pp->_left == parent) {pp->_left = subr;}else pp->_right = subr;subr->_parent = pp;}else { //pp为空 parent为根结点 此时subr为新根节点_root = subr;subr->_parent = nullptr;}parent->_bf = subr->_bf = 0; //旋转后 已经平衡 改变平衡因子
}
void RotateLR(Node* parent) {Node* subl = parent->_left;Node* sublr = subl->_right;int bf = sublr->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0) {subl->_bf = 0;parent->_bf = 0;sublr->_bf = 0;}else if (bf == -1) {subl->_bf = 0;parent->_bf = 1;sublr->_bf = 0;}else if (bf == 1) {subl->_bf = -1;parent->_bf = 0;sublr->_bf = 0;}else {assert("RotateLR false");}
}
void RotateRL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert("RotateRL false");}
}

4.AVL树的查找

以⼆叉搜索树逻辑实现即可,搜索效率为 O(log2N)

Node* Find(const K& key) {/*if (!_root) {return false;}*/Node* cur = _root; //树为空不进入循环直接返回Falsewhile (cur) {if (cur->_kv.first < key) {//此时值小 往大的右走cur = cur->_right;}else if (cur->_kv.first > key) {//此时值大 往左cur = cur->_left;}else {return cur;}}return nullptr;
}

5.AVL树平衡检测

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;
}
bool _IsBalanceTree(Node* root)
{// 空树也是AVL树if (nullptr == root)return true;// 计算pRoot结点的平衡因⼦:即pRoot左右⼦树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因⼦与pRoot的平衡因子不相等,或者// pRoot平衡因⼦的绝对值超过1,则⼀定不是AVL树if (abs(diff) >= 2){cout << root->_kv.first << "高度差异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}// pRoot的左和右如果都是AVL树,则该树⼀定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

6.参考文章

https://blog.csdn.net/xiaojin21cen/article/details/97602146


如果这篇文章对你有帮助的话,请给博主一个免费的赞鼓励一下吧~ 💓

本文仅简单介绍了有关AVL树的一些基本概念和相关代码实现,以上个人拙见,若有错误之处,希望各位能提出宝贵的建议和更正,感谢您的观看

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法】连通块问题(C/C++)

目录 连通块问题 解决思路 步骤&#xff1a; 初始化&#xff1a; DFS函数&#xff1a; 复杂度分析 代码实现&#xff08;C&#xff09; 题目链接&#xff1a;2060. 奶牛选美 - AcWing题库 解题思路&#xff1a; AC代码&#xff1a; 题目链接&#xff1a;687. 扫雷 -…

24.11.26 Mybatis2

resultMap 中的标签和属性 如果是主键列 一般用id标签对应 propertyjava对象的属性 column 数据库中的列( javaType实体类数据类型 jdbcType数据库列的数据类型 ) 不需要配置 <id property"empno" column"empno" />如果是普通列 一般用result对…

Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)

14.1 命令请求的执行过程 一个命令请求从发送到获得回复的过程中&#xff0c;客户端和服务器都需要完成一系列操作。 14.1.1 发送命令请求 当用户在客户端中输入一个命令请求的时候&#xff0c;客户端会把这个命令请求转换为协议格式&#xff0c;然后通过连接到服务器的套接字…

ArcGIS pro中的回归分析浅析(加更)关于广义线性回归工具的补充内容

在回归分析浅析中篇的文章中&#xff0c; 有人问了一个问题&#xff1a; 案例里的calls数据貌似离散&#xff0c;更符合泊松模型&#xff0c;为啥不采用泊松而采用高斯呢&#xff1f; 确实&#xff0c;在中篇中写道&#xff1a; 在这个例子中我们为了更好地解释变量&#x…

【面试题】2025年百度校招Java后端面试题

文章目录 前言一、网络IO1、服务器处理并发请求有哪几种方式&#xff1f;2、说一下select&#xff0c;poll&#xff0c;epoll的区别&#xff1f;3、Java 有一种现代的处理方式&#xff0c;属于异步I/O&#xff0c;是什么&#xff1f;redis&#xff0c;nginx&#xff0c;netty 是…

【Zookeeper 和 Kafka】为什么 Zookeeper 不用域名?

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

RiceChem——用于评估大语言模型在教育领域自动长答卷评分 (ALAG) 的数据集

摘要 论文地址:https://arxiv.org/abs/2404.14316 源码地址&#xff1a;https://github.com/luffycodes/automated-long-answer-grading 迄今为止&#xff0c;教育领域的自然语言处理&#xff08;NLP&#xff09;主要集中在简答题评分和自由文本作文评分方面。然而&#xff0c…

Java ArrayList 与顺序表:在编程海洋中把握数据结构的关键之锚

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 前言&#xff1a;在 Java编程的广袤世界里&#xff0c;数据结构犹如精巧的建筑蓝图&#xff0c;决定着程序在数据处理与存储时的效率、灵活性以…

【04】Selenium+Python 手动添加Cookie免登录(实例)

一、什么是Cookie&#xff1f; Cookie 是一种由服务器创建并保存在用户浏览器中的小型数据文件。它用于存储用户的相关信息&#xff0c;以便在后续访问同一网站时可以快速检索这些信息。Cookie 主要用于以下几个方面&#xff1a; 1.状态管理&#xff1a; Cookie 可以保存用户…

GitLab|应用部署

创建docker-compose.yaml文件 输入docker-compose配置 version: 3.8 services:gitlab:image: gitlab/gitlab-ce:15.11.2-ce.0restart: alwayscontainer_name: gitlab-ceprivileged: truehostname: 192.168.44.235environment:TZ: Asia/ShanghaiGITLAB_OMNIBUS_CONFIG: |exter…

【PX4_Autopolite飞控源码】中飞控板初始化过程中的引脚IO控制(拉低/拉高)

先选择自己飞控板支持的硬件平台 打开对应的路径我的是Desktop/px4/PX4-Autopilot/boards/zhty/nora 找到board_config.h文件&#xff0c;打开nora后再往下去找Desktop/px4/PX4-Autopilot/boards/zhty/nora/src/borad_config.h 打开后可以看到有很多GPIO引脚的定义&#xff0c…

如何使用Postman优雅地进行接口自动加密与解密

引言 在上一篇文章中&#xff0c;分享了 Requests 自动加解密的方法&#xff0c;本篇文章分享一下更加方便的调试某个服务端接口。 Postman Postman 这个工具后端小伙伴应该相当熟悉了&#xff0c;一般情况下我们会在开发和逆向过程中使用它来快速向接口发送请求&#xff0c;…

Spring Boot——统一功能处理

1. 拦截器 拦截器主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务需要执行设定好的代码&#xff0c;也就是提前定义一些逻辑&#xff0c;在用户的请求响应前后执行&#xff0c;也可以在用户请求前阻止其执行&#xff0c;例如登录操作&#xff0c;只有登…

PYTORCH基础语法知识

初识Torch PyTorch&#xff0c;简称Torch&#xff0c;主流的经典的深度学习框架&#xff0c;深度学习的框架。 简介 PyTorch是一个基于Python的深度学习框架&#xff0c;它提供了一种灵活、高效、易于学习的方式来实现深度学习模型。PyTorch最初由Facebook开发&#xff0c;被…

C嘎嘎探索篇:栈与队列的交响:C++中的结构艺术

C嘎嘎探索篇&#xff1a;栈与队列的交响&#xff1a;C中的结构艺术 前言&#xff1a; 小编在之前刚完成了C中栈和队列&#xff08;stack和queue&#xff09;的讲解&#xff0c;忘记的小伙伴可以去我上一篇文章看一眼的&#xff0c;今天小编将会带领大家吹奏栈和队列的交响&am…

刷题日常(数据流中的中位数,逆波兰表达式求值,最长连续序列,字母异位词分组)

数据流中的中位数 描述 如何得到一个数据流中的中位数&#xff1f;如果从数据流中读出奇数个数值&#xff0c;那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值&#xff0c;那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()…

SQL 复杂查询

目录 复杂查询 一、目的和要求 二、实验内容 &#xff08;1&#xff09;查询出所有水果产品的类别及详情。 查询出编号为“00000001”的消费者用户的姓名及其所下订单。&#xff08;分别采用子查询和连接方式实现&#xff09; 查询出每个订单的消费者姓名及联系方式。 在…

uniapp-vue2引用了vue-inset-loader插件编译小程序报错

报错信息 Error: Vue packages version mismatch: - vue3.2.45 (D:\qjy-myApp\admin-app\node_modules\vue\index.js) - vue-template-compiler2.7.16 (D:\qjy-myApp\admin-app\node_modules\vue-template-compiler\package.json) This may cause things to work incorrectly.…

VOLO实战:使用VOLO实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

【Linux】TCP网络编程

目录 V1_Echo_Server V2_Echo_Server多进程版本 V3_Echo_Server多线程版本 V3-1_多线程远程命令执行 V4_Echo_Server线程池版本 V1_Echo_Server TcpServer的上层调用如下&#xff0c;和UdpServer几乎一样&#xff1a; 而在InitServer中&#xff0c;大部分也和UDP那里一样&…