【C++進階】第8課—紅黑樹封裝map和set

文章目錄

  • 1. map和set的源碼及框架分析
  • 2. 模擬實現map和set
    • 2.1 實現可以復用紅黑樹的框架,支持insert操作
    • 2.2 實現迭代器iterator
      • 2.2.1 實現迭代器++
      • 2.2.2 實現迭代器 - -
      • 2.2.3 解決key不能修改的問題
      • 2.2.4 重載operator[ ]
  • 3. 完整代碼
    • 3.1 ==紅黑樹頭文件RBTree.h==
    • 3.2 ==mymap頭文件==
    • 3.3 ==myset頭文件==
    • 3.4 ==測試文件test.cpp==

在這里插入圖片描述
??學過紅黑樹后,現在我們來學習如何使用紅黑樹來封裝mymap和myset

1. map和set的源碼及框架分析

  • SGI-STL30版本源代碼,map和set的源代碼在map/set/stl_map.h/stl_set.h/stl_tree.h等幾個頭文件中

在這里插入圖片描述


在這里插入圖片描述


2. 模擬實現map和set

2.1 實現可以復用紅黑樹的框架,支持insert操作

  • 當然,之前我們實現的紅黑樹是以map的key/value實現的,要實現泛型,就需要改變一些參數,例如節點數據應該給模版參數T,插入數據時應該是T& data
  • 對比源碼調整一下,key參數就用K,value參數就用V,紅黑樹中的數據類型,我們使用T
  • 其次因為RBTree實現了泛型不知道T參數到底是K,還是pair<K, V>,那么insert內部進行插入邏輯比較時,就沒辦法進行比較,因為pair的默認支持的是key和value一起參與比較,我們需要的是任何時候只比較key,所以我們在map和set層分別實現一個MapKeyOfTSetKeyOfT的仿函數傳給RBTree的KeyOfT,然后RBTree中通過KeyOfT仿函數取出T類型對象中的key,再進行比較,具體細節參考如下代碼實現

在這里插入圖片描述


2.2 實現迭代器iterator

  • 迭代器實現時,其核心邏輯就是++/- -時該如何實現
  • 迭代器++的核心邏輯就是不看全局,只看局部,只考慮當前中序局部要訪問的下一個結點
  • 實現迭代器的思路
    1. 實現紅黑樹,封裝迭代器,使用紅黑樹封裝map和set
    2. 實現迭代器++/- -
    3. 搭建iterator和const_iterator框架
    4. 解決key不支持修改的問題
    5. 重載方括號operator[ ]

2.2.1 實現迭代器++

  • 第一種情況:it所指向的節點,它的右子樹不為空,++it步驟

在這里插入圖片描述


  • 第2種情況: it所指向的節點,它的右子樹為空時,++it步驟

在這里插入圖片描述


  • 如果it節點是它父節點的左孩子節點

在這里插入圖片描述


  • 代碼實現邏輯

在這里插入圖片描述


2.2.2 實現迭代器 - -

  • 迭代器- -時思路和++時大同小異,這里直接上代碼講解

在這里插入圖片描述


  • 搭建iterator和const_iterator框架

在這里插入圖片描述


2.2.3 解決key不能修改的問題

在這里插入圖片描述


2.2.4 重載operator[ ]

在這里插入圖片描述


3. 完整代碼

3.1 紅黑樹頭文件RBTree.h

#pragma once
#include <iostream>
using namespace std;//枚舉常量
enum Colour
{RED,BLACK
};//紅黑樹節點
template <class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};//迭代器
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;Node* _root;//默認構造RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}//重載運算符++Self& operator++(){//如果it節點的右子樹存在if (_node->_right){//++it ---> 找右子樹的最左節點_node = _node->_right;while (_node->_left){_node = _node->_left;}}//如果it節點的右子樹不存在else{//向上更新Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}//重載運算符--    右-根-左Self& operator--(){//在 _node == end() 處--if (_node == nullptr){//找根節點右子樹最右節點Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}//如果不是在end()--else if (_node->_left){// 左?樹不為空,中序左?樹最后?個Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{// 孩?是?親右的那個祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}//重載解引用操作符*Ref operator*(){return _node->_data;}//重載解引用操作符->Ptr operator->(){return &_node->_data;}//重載操作符!=bool operator!=(const Self& s) const{return _node != s._node;}//重載操作符==bool operator==(const Self& s) const{return _node == s._node;}
};//紅黑樹
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&, const T*> Const_Iterator;RBTree() = default;//迭代器使用Iterator begin(){Node* MinLeft = _root;while (MinLeft && MinLeft->_left){MinLeft = MinLeft->_left;}return Iterator(MinLeft, _root);}Iterator end(){return Iterator(nullptr, _root);}Const_Iterator begin() const{Node* MinLeft = _root;while (MinLeft && MinLeft->_left){MinLeft = MinLeft->_left;}return Const_Iterator(MinLeft, _root);}Const_Iterator end() const{return Const_Iterator(nullptr, _root);}//插入節點pair<Iterator,bool> insert(const T& data){//如果是空樹if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return { Iterator{_root,_root},true };}//如果是非空樹 KeyOfT kot;Node* parent = nullptr;Node* cur = _root;//尋找插入位置while (cur){//插入值比cur節點值大if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}//插入值比cur節點值小else if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}elsereturn { Iterator(cur,_root),false };}//插入節點cur = new Node(data);cur->_col = RED;Node* newnode = cur;//如果插入值大于parent值if (kot(data) > kot(parent->_data))parent->_right = cur;//如果插入值小于parent值else if (kot(data) < kot(parent->_data))parent->_left = cur;//存儲cur節點的父節點cur->_parent = parent;//定義cur節點的p、u、g節點 ---> 更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//如果parent為grandfather的左孩子if (parent == grandfather->_left){Node* uncle = grandfather->_right;//第一種情況:父親、叔叔都為紅,爺爺為黑if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//繼續向上處理cur = grandfather;parent = cur->_parent;}//第2、3種情況else{//第2種情況 ---> uncle節點不存在或者為黑色//      g//   p     u//c 右單旋if (cur == parent->_left){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//第3種情況 ---> uncle節點不存在或者為黑色//      g//   p     u//      c  左單旋 + 右單旋else{RotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}//如果parent為grandfather的右孩子else{Node* uncle = grandfather->_left;//第一種情況:父親、叔叔都為紅,爺爺為黑if (uncle && uncle->_col == RED){uncle->_col = parent->_col = BLACK;grandfather->_col = RED;//繼續向上處理cur = grandfather;parent = cur->_parent;}//第2、3種情況else{//第2種情況 ---> uncle節點不存在或者為黑色//      g//   u     p//左單旋       cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//第3種情況 ---> uncle節點不存在或者為黑色//      g//   u     p//      c  右單旋 + 左單旋else{RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}//直接賦予根節點黑色_root->_col = BLACK;return { Iterator{newnode,_root},true };}//左單旋void RotateL(Node* parent){//定義兩個節點指針Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL)subRL->_parent = parent;Node* parent_Parent = parent->_parent;parent->_parent = subR;//如果parent為根節點if (parent_Parent == nullptr){_root = subR;subR->_parent = nullptr;}//如果parent不是根節點else{//如果旋轉之前parent所在的整個子樹為根節點的左子樹if (parent_Parent->_left == parent)parent_Parent->_left = subR;elseparent_Parent->_right = subR;subR->_parent = parent_Parent;}}//右單旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* parent_Parent = parent->_parent;parent->_parent = subL;//如果parent為根節點if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果parent不是根節點else{if (parent_Parent->_left == parent)parent_Parent->_left = subL;elseparent_Parent->_right = subL;subL->_parent = parent_Parent;}}//中序遍歷輸出數據void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr)return;KeyOfT kot;_Inorder(root->_left);cout << kot(root->_data) << " ";_Inorder(root->_right);}//紅黑樹節點個數size_t Size(){return _Size(_root);}size_t _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}//查找Iterator Find(const K& key){Node* cur = _root;while (cur){if (key > cur->_kv.first)cur = cur->_right;else if (key < cur->_kv.first)cur = cur->_left;elsereturn Iterator(cur, _root);}return end();}//銷毀void Destroy(Node* _root){if (_root == nullptr)return;Destroy(_root->_left);Destroy(_root->_right);delete _root;}//析構~RBTree(){Destroy(_root);_root = nullptr;}//前序遍歷查找每條路徑黑色節點個數bool Check(Node* root, int Count_BlackNode, int retNum){if (root == nullptr){if (Count_BlackNode != retNum){cout << "存在黑色節點數量不等的路徑" << endl;return false;}return true;}KeyOfT kot;//root節點非空 ---> 檢查兩個孩子節點不方便,可能還有孩子不存在,直接檢查其父節點if (root->_col == RED && root->_parent->_col == RED){cout << kot(root->_data) << "存在連續的紅色節點" << endl;return false;}//如果節點為黑色if (root->_col == BLACK){++Count_BlackNode;}return Check(root->_left, Count_BlackNode, retNum) && Check(root->_right, Count_BlackNode, retNum);}//檢查是否滿足平衡bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED){cout << "根節點為紅色!" << endl;return false;}//記錄一條路徑上黑色節點數量int retNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++retNum;}cur = cur->_left;}return Check(_root, 0, retNum);}private:Node* _root = nullptr;
};

3.2 mymap頭文件

#pragma once#include "RBTree_副本.h"namespace ZY
{//用于比較的仿函數template<class K,class V>class mymap{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Const_Iterator const_iterator;//插入pair<iterator, bool> insert(const pair<K, V>& kv){return _mymap.insert(kv);}//operator[]V& operator[](const K& key){pair<iterator, bool> ret = _mymap.insert({ key,V() });return ret.first->second;}//判斷樹平衡bool IsBalance(){return _mymap.IsBalance();}//返回節點個數size_t size(){return _mymap.Size();}//中序遍歷void Inoreder(){_mymap.Inorder();}iterator begin(){return _mymap.begin();}iterator end(){return _mymap.end();}const_iterator begin() const{return _mymap.begin();}const_iterator end() const{return _mymap.end();}private:RBTree<K, pair<const K, V>, MapKeyOfT> _mymap;};
}

3.3 myset頭文件

#pragma once#include "RBTree_副本.h"namespace ZY
{//用于比較的仿函數template<class K>class myset{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::Const_Iterator const_iterator;//插入pair<iterator, bool> insert(const K& key){return _myset.insert(key);}//判斷樹平衡bool IsBalance(){return _myset.IsBalance();}//返回節點個數size_t size(){return _myset.Size();}//中序遍歷void Inoreder(){_myset.Inorder();}iterator begin(){return _myset.begin();}iterator end(){return _myset.end();}const_iterator begin() const{return _myset.begin();}const_iterator end() const{return _myset.end();}private:RBTree<K, const K, SetKeyOfT> _myset;};
}

3.4 測試文件test.cpp

#include"mymap.h"
#include"myset.h"
#include<vector>void print_set(const ZY::myset<int>& s)
{for (auto e : s){cout << e << " ";}cout << endl;
}void print_map(const ZY::mymap<string,string>& mp)
{for (auto e : mp){cout << e.first << " " << e.second << " ";}cout << endl;
}//插入測試
void myset_test1()
{ZY::myset<int> st;//常規測試//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//for (auto& e : a)//{//	st.insert(e);//}//帶有雙旋的測試用例int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto& e : arr){st.insert(e);}if (st.IsBalance())cout << "該二叉樹是紅黑樹!" << endl;elsecout << "該二叉樹不是紅黑樹!" << endl;cout << "節點個數:" << st.size() << endl;//st.Inoreder();//測試迭代器ZY::myset<int>::iterator it = st.begin();while (it != st.end()){//*it += 1;cout << *it << " ";++it;}cout << endl;print_set(st);
}void mymap_test1()
{ZY::mymap<string, string> mp;mp.insert({ "left","左邊" });mp.insert({ "right","右邊" });mp.insert({ "sort","排序" });mp.insert({ "string","字符串" });//mp.Inoreder();//mp.size();if (mp.IsBalance())cout << "該二叉樹是紅黑樹!" << endl;elsecout << "該二叉樹不是紅黑樹!" << endl;cout << "節點個數:" << mp.size() << endl;ZY::mymap<string, string>::iterator it = mp.begin();while (it != mp.end()){cout << it->first << ":" << it->second <<" ";++it;}cout << endl;//auto it1 = mp.end();//while (it1 != mp.begin())//{//	--it1;//	cout << it1->first << ":" << it1->second << " ";//}//cout << endl;mp["哈哈哈"];mp["left"] = "耶耶耶";print_map(mp);
}void test()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(i + rand());}size_t begin2 = clock();ZY::myset<int> t;for (size_t i = 0; i < v.size(); ++i){t.insert(v[i]);}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << "Size:" << t.size() << endl;if (t.IsBalance())cout << "該二叉樹是紅黑樹!" << endl;elsecout << "該二叉樹不是紅黑樹!" << endl;}int main()
{//myset_test1();mymap_test1();//test();return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91144.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91144.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91144.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【機器學習深度學習】DeepSpeed框架:高效分布式訓練的開源利器

目錄 前言 一、DeepSpeed 簡介 1.1 定位與目標 1.2 集成生態 二、核心技術解析 2.1 ZeRO&#xff08;Zero Redundancy Optimizer&#xff09; 2.2 顯存優化技術 2.3 推理優化與通信機制 三、DeepSpeed 的優勢與特性總結 四、 典型應用場景 &#x1f9e0; 大模型訓練…

從視覺到現實:掌握計算機視覺技術學習路線的十大步驟

成長路上不孤單&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;【14后&#x1f60a;///計算機愛好者&#x1f60a;///持續分享所學&#x1f60a;///如有需要歡迎收藏轉發///&#x1f60a;】今日分享關于計算機視覺技術學習路線方面的相關內容…

DeepSeek MoE 技術解析:模型架構、通信優化與負載均衡

1. MoE 簡介 MoE&#xff08;Mixed Expert Models&#xff09;&#xff0c;混合專家模型。在 Transformer 的 FFN 中&#xff0c;有一個重要的觀察是&#xff0c;其計算過程中的神經元激活是非常稀疏的&#xff0c;在一次計算中只有 90%的輸入激活不到 5%的神經元&#xff0c;…

【Linux】pthread學習筆記

1. 線程基礎(1) 線程創建與終止#include <pthread.h> // 創建線程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void*), void *arg); // 終止當前線程 void pthread_exit(void *retval); // 等待線程結束 int pthread_joi…

p5.js 從零開始創建 3D 模型,createModel入門指南

點贊 關注 收藏 學會了 如果你已經開始探索 p5.js 的 3D 世界&#xff0c;那么createModel()這個 API 絕對是你需要掌握的強大工具。它允許你創建自定義的 3D 幾何模型&#xff0c;為你的創意提供無限可能。 什么是 createModel ()&#xff1f; createModel() 用于從一個…

react 的 useTransition 、useDeferredValue

useTransition 用于 管理狀態更新的過渡&#xff08;pending&#xff09;狀態&#xff0c;避免因高優先級任務&#xff08;如用戶輸入&#xff09;被低優先級任務&#xff08;如數據獲取或復雜計算&#xff09;阻塞而導致的界面卡頓。 它特別適用于&#xff0c;需要 區分緊急更…

Unity的GameObject.Instantiate的使用

在Unity游戲引擎中&#xff0c;GameObject.Instantiate 是一個核心方法&#xff0c;用于在運行時動態創建游戲對象的副本。它常用于實例化預制體&#xff08;Prefab&#xff09;&#xff0c;例如生成敵人、子彈或場景元素。以下是其使用方法的詳細說明&#xff0c;包括語法、參…

【CSS】盒子類型

CSS盒子模型是網頁布局的核心基礎&#xff0c;每個HTML元素都被視為一個矩形盒子&#xff0c;由??內容&#xff08;Content&#xff09;、內邊距&#xff08;Padding&#xff09;、邊框&#xff08;Border&#xff09;、外邊距&#xff08;Margin&#xff09;??四部分組成。…

《嵌入式C語言筆記(十五):字符串操作與多維指針深度解析》

1.字符串與指針安全操作核心函數與陷阱函數功能安全替代功能strcpy字符串拷貝strncpy復制前n個&#xff0c;最多strlen個&#xff0c;超出有效長度&#xff0c;按原樣復制strcat字符串拼接strncatdest只連接src的前n個&#xff0c;如果n超過有效長度&#xff0c;按原樣鏈接strc…

每日學習筆記記錄(分享更新版-凌亂)

函數和變量都需要滿足&#xff1a;先聲明后使用&#xff08;重要&#xff09;在 函數的聲明中&#xff0c;形參的名字可以省略函數的定義是一種特殊的是聲明&#xff0c;比聲明更加強大&#xff1b;函數使用前必須進行聲明&#xff0c;但不必要聲明具體定義.h——函數的聲明.c—…

Windows提權(MS09-012 巴西烤肉)

演示環境&#xff1a;windows-2003前提&#xff1a;提權的前提條件是拿到服務器的webshell演示以iis的中間件解析漏洞為例&#xff08;test.asp;.jpg&#xff09; Windows提權拿到webshell之后&#xff0c;使用菜刀&#xff0c;蟻劍&#xff0c;冰蝎或者哥斯拉連接上服務器&…

常見依賴于TCP/IP的應用層協議

Protocol 協議 Acronym 縮寫 Port 端口 Description 描述 Telnet Telnet 23 Remote login service 遠程登錄服務 Secure Shell SSH 22 Secure remote login service 安全遠程登錄服務 Simple Network Management Protocol 簡單網絡管理協議 SNMP 161-162 Manage network d…

XML Schema 指示器:全面解析與深度應用

XML Schema 指示器:全面解析與深度應用 引言 XML Schema 是一種用于定義 XML 文檔結構的語言,它為 XML 文檔提供了嚴格的框架,以確保數據的準確性和一致性。在本文中,我們將深入探討 XML Schema 的基本概念、關鍵特性、指示器的作用以及其實際應用。 XML Schema 的基本概…

13、select_points_object_model_3d解析

名字 select_points_object_model_3d- 將閾值應用于 3D 對象模型的屬性。 簽名 select_points_object_model_3d( : : ObjectModel3D, Attrib,

ThinkPHP6.1+Ratchet庫 搭建websocket服務

Ratchet 是一個基于 ReactPHP 的 PHP WebSocket 庫&#xff0c;無需依賴 Swoole 擴展。以下是實現步驟&#xff1a;首先安裝 Ratchet&#xff1a;composer require cboden/ratchet創建 WebSocket 處理類&#xff1a;<?php /*** websocket處理類* DateTime 2025/7/28 10:38…

智慧工地系統:科技如何重塑建筑現場?

前幾天路過一個正在施工的樓盤&#xff0c;看到現場雖然機器轟鳴&#xff0c;但秩序井然&#xff0c;工人們佩戴著設備&#xff0c;指揮塔上閃爍著指示燈&#xff0c;和印象中那種塵土飛揚、雜亂無章的工地景象完全不同。當時就感慨&#xff0c;現在工地也“智慧”起來了。后來…

Day 25:異常處理

Day 25: Python異常處理機制 Review 上一節主要是熟悉os等python中的文件操作&#xff0c;包含&#xff1a; 基礎操作&#xff1a;目錄獲取、文件列舉、路徑拼接系統交互&#xff1a;環境變量管理、跨平臺兼容性高級功能&#xff1a;目錄樹遍歷、文件系統分析 Today 今天專…

Apache Ignite 的分布式隊列(IgniteQueue)和分布式集合(IgniteSet)的介紹

以下的內容是關于 Apache Ignite 的分布式隊列&#xff08;IgniteQueue&#xff09;和分布式集合&#xff08;IgniteSet&#xff09; 的介紹。它們是 Ignite 提供的分布式數據結構&#xff0c;讓你可以在整個集群中像使用本地 BlockingQueue 或 Set 一樣操作共享的數據。 下面我…

HTML5 `<figure>` 標簽:提升網頁語義化與可訪問性的利器

目錄什么是 <figure> 標簽&#xff1f;為什么我們要用 <figure>&#xff1f;<figure> 標簽的語法<figure> 標簽的適用場景1 圖片及其說明 (最常用)2 代碼片段及其注釋3 圖表、流程圖或數據可視化4 引用或引文 (Quote) 及其出處總結在現代網頁開發中&am…

計算機網絡五層模型

我們常說的“計算機網絡五層協議模型”&#xff0c;是一個實際應用中廣泛采用的簡化模型&#xff08;介于OSI七層&#xff08;Open System Interconnect&#xff09;與TCP/IP四層之間&#xff09;&#xff0c;用于描述網絡通信中各層的職責與作用。 文章目錄第5層&#xff1a;應…