【C++】模拟实现红黑树(插入)

目录

红黑树的概念

红黑树的性质

红黑树的调整情况

红黑树的模拟实现

枚举类型的定义

红黑树节点的定义

插入函数的实现

旋转函数的实现

左旋

右旋

自检函数的实现

红黑树类


红黑树的概念

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

红黑树的性质

  1. 每个结点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

红黑树的调整情况

  • 新节点插入为红色:

新节点默认着色为红色。

  • 父节点为黑色:

如果新插入节点的父节点是黑色,那么通常只需要进行简单的颜色调整即可,因为黑色节点不会破坏红黑树的性质。

  • 父节点为红色:

这是需要重点关注的情况,因为红色父节点可能违反红黑树的性质。

  • 叔节点也为红色: 如果叔节点(父节点的兄弟节点)也是红色,则将父节点和叔节点都变为黑色,并将祖父节点(父节点的父节点)变为红色。接着,将祖父节点作为新的当前节点,继续向上检查是否破坏了性质。 -叔节点为黑色或不存在: 根据新节点是父节点的左子节点还是右子节点,以及父节点是祖父节点的左子节点还是右子节点,需要进行不同的调整。

  • 父节点是左子节点:

新节点是父节点的右子节点:将父节点进行左旋操作,然后重新从父节点开始着色。 新节点是父节点的左子节点:将父节点变为黑色,将祖父节点变为红色,然后对祖父节点进行右旋操作。

  • 父节点是右子节点:

新节点是父节点的左子节点:将父节点进行右旋操作,然后重新从父节点开始着色。

红黑树的模拟实现

枚举类型的定义

enum Color
 {
  RED,
  BLACK
 };

红黑树节点的定义

  • 成员有:左孩子指针、有孩子指针、父节点指针、一个键值对、颜色(枚举类型)

  • 构造函数:将三个指针置空,颜色默认为红色,由外面传递的键值对作为值

struct RBTreeNode
 {
  RBTreeNode<K, V>* _left;
  RBTreeNode<K, V>* _right;
  RBTreeNode<K, V>* _parent;
  pair<K, V> _kv;
  Colour _color;

  RBTreeNode(const pair<K, V>& kv)
   :_left(nullptr)
   , _right(nullptr)
   , _parent(nullptr)
   , _kv(kv)
   , _color(RED)
  {}
 };

插入函数的实现

  • 函数参数:

const pair<K, V>& kv:要插入到红黑树中的键值对。

  • 检查根节点是否为空:

if (_root == nullptr):如果红黑树为空(即根节点为空),则创建一个新的节点作为根节点,并设置其颜色为黑色。然后返回true表示插入成功。

  • 查找插入位置:

使用parent和cur指针遍历树,查找插入位置。parent用于记录当前节点的父节点,cur用于遍历树。根据键值对的大小关系,将cur指针移动到正确的子树中。如果在遍历过程中发现键值对已经存在于树中(即cur->_kv.first == kv.first),则直接返回false表示插入失败。

  • 创建新节点并插入:

创建一个新的节点cur,并设置其值为kv,颜色为红色。根据之前找到的parent节点的键值对大小关系,将新节点插入到父节点的左子树或右子树中,并更新节点的父节点指针。

  • 调整红黑树:

由于新插入的节点颜色是红色,可能会破坏红黑树的性质。因此,需要进行一系列调整操作来恢复红黑树的性质。while (parent && parent->_color == RED)循环用于检查父节点的颜色,如果父节点是红色的,则需要进行调整。 根据父节点是祖父节点的左子节点还是右子节点,以及新节点是父节点的左子节点还是右子节点,选择不同的调整策略。如果父节点和叔叔节点(祖父节点的另一个子节点)都是红色的,则将父节点和叔叔节点的颜色改为黑色,并将祖父节点的颜色改为红色。然后更新cur和parent指针,继续检查新的父节点。 如果叔叔节点是黑色的,则根据新节点是父节点的左子节点还是右子节点,选择进行左旋或右旋操作,以及可能的颜色调整。在循环结束后,确保根节点的颜色是黑色,这是红黑树的一个基本性质。

  • 返回插入结果:

由于插入操作已经成功完成,无论是否进行了调整操作,都返回true表示插入成功。

bool Insert(const pair<K, V>& kv)
  {
   if (_root == nullptr)
   {
    _root = new Node(kv);
    _root->_color = BLACK;
    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);
   cur->_color = RED;
   if (parent->_kv.first < kv.first)
   {
    parent->_right = cur;
    cur->_parent = parent;
   }
   else
   {
    parent->_left = cur;
    cur->_parent = parent;
   }

   while (parent && parent->_color == RED)
   {
    Node* grandfather = parent->_parent;
    if (parent == grandfather->_left)
    {

     Node* uncle = grandfather->_right;
     if (uncle && uncle->_color == RED)
     {

      parent->_color = uncle->_color = BLACK;
      grandfather->_color = RED;


      cur = grandfather;
      parent = cur->_parent;
     }
     else
     {
      if (cur == parent->_left)
      {

       RotateR(grandfather);
       parent->_color = BLACK;
       grandfather->_color = RED;
      }
      else
      {

       RotateL(parent);
       RotateL(grandfather);
       cur->_color = BLACK;
       grandfather->_color = RED;
      }

      break;
     }
    }
    else
    {

     Node* uncle = grandfather->_left;
     if (uncle && uncle->_color == RED)
     {

      parent->_color = uncle->_color = BLACK;
      grandfather->_color = RED;


      cur = grandfather;
     }
     else
     {
      if (cur == parent->_right)
      {
       RotateL(grandfather);
       parent->_color = BLACK;
       grandfather->_color = RED;
      }
      else
      {

       RotateR(parent);
       RotateL(grandfather);
       cur->_color = BLACK;
       grandfather->_color = RED;
      }

      break;
     }
    }
   }

   _root->_color = BLACK;

   return true;
  }

旋转函数的实现

  • 这两个函数RotateL和RotateR分别用于执行红黑树的左旋和右旋操作。在红黑树的插入和删除操作中,当破坏了红黑树的性质时,通常需要通过节点的旋转和颜色调整来恢复树的平衡。

左旋

  • 左旋操作通常用于当某个节点的右子节点是红色时。左旋操作将右子节点提升为父节点,并将原父节点降为其左子节点。同时,原父节点的左子节点则成为新父节点的右子节点。

void RotateL(Node* parent)  
{  
    Node* subR = parent->_right; // 右子节点  
    Node* subRL = subR->_left; // 右子节点的左子节点  
  
    parent->_right = subRL; // 原父节点的右子节点指向右子节点的左子节点  
    if (subRL)  
        subRL->_parent = parent; // 更新subRL的父节点指针  
  
    subR->_left = parent; // 右子节点的左子节点指向原父节点  
    parent->_parent = subR; // 更新原父节点的父节点指针  
  
    Node* parentParent = parent->_parent; // 原父节点的父节点  
  
    if (_root == parent) // 如果原父节点是根节点  
    {  
        _root = subR; // 更新根节点为右子节点  
        subR->_parent = nullptr; // 根节点的父节点指针置为null  
    }  
    else  
    {  
        if (parentParent->_left == parent) // 如果原父节点是其父节点的左子节点  
        {  
            parentParent->_left = subR; // 更新父节点的左子节点为右子节点  
        }  
        else  
        {  
            parentParent->_right = subR; // 如果原父节点是其父节点的右子节点,则更新父节点的右子节点为右子节点  
        }  
        subR->_parent = parentParent; // 更新右子节点的父节点指针  
    }  
}

右旋

  • 右旋操作与左旋操作相反,通常用于当某个节点的左子节点是红色时。右旋操作将左子节点提升为父节点,并将原父节点降为其右子节点。同时,原父节点的右子节点则成为新父节点的左子节点。

  
  void RotateR(Node* parent)  
{  
    Node* subL = parent->_left; // 左子节点  
    Node* subLR = subL->_right; // 左子节点的右子节点  
  
    parent->_left = subLR; // 原父节点的左子节点指向左子节点的右子节点  
    if (subLR)  
        subLR->_parent = parent; // 更新subLR的父节点指针  
  
    subL->_right = parent; // 左子节点的右子节点指向原父节点  
    parent->_parent = subL; // 更新原父节点的父节点指针  
  
    Node* parentParent = parent->_parent; // 原父节点的父节点  
  
    if (_root == parent) // 如果原父节点是根节点  
    {  
        _root = subL; // 更新根节点为左子节点  
        subL->_parent = nullptr; // 根节点的父节点指针置为null  
    }  
    else  
    {  
        if (parentParent->_left == parent) // 如果原父节点是其父节点的左子节点  
        {  
            parentParent->_left = subL; // 更新父节点的左子节点为左子节点  
        }  
        else  
        {  
            parentParent->_right = subL; // 如果原父节点是其父节点的右子节点,则更新父节点的右子节点为左子节点  
        }  
        subL->_parent = parentParent; // 更新左子节点的父节点指针  
    }  
}

自检函数的实现

  • Check函数

这个函数递归地检查红黑树的每个节点,确保满足红黑树的性质。

首先,它检查传入的节点是否为空(即已经到达了叶子节点)。如果是,它会比较从根节点到该叶子节点的黑色节点数量是否等于refVal(预期值)。如果不等,则打印错误信息并返回false。接着,它检查当前节点是否与其父节点连续为红色,如果是,则打印错误信息并返回false。如果当前节点是黑色,则增加blacknum的计数。最后,它递归地检查左子树和右子树。

  • IsBalance 函数

这个函数用于检查整棵红黑树是否平衡。

首先,它检查根节点是否为空或红色。如果根节点为空,则树是平衡的;如果根节点为红色,则树不平衡。接下来,它计算从根节点到最左侧叶子节点路径上的黑色节点数量,并将此值作为refVal(预期值)。然后,它调用Check函数,从根节点开始,检查整棵树是否满足红黑树的性质,特别是黑色节点数量的要求。

bool Check(Node* root, int blacknum, const int refVal)
  {
   if (root == nullptr)
   {
    //cout << balcknum << endl;
    if (blacknum != refVal)
    {
     cout << "存在黑色节点数量不相等的路径" << endl;
     return false;
    }

    return true;
   }

   if (root->_color == RED && root->_parent->_color == RED)
   {
    cout << "有连续的红色节点" << endl;

    return false;
   }

   if (root->_color == BLACK)
   {
    ++blacknum;
   }

   return Check(root->_left, blacknum, refVal)
    && Check(root->_right, blacknum, refVal);
  }
 public:
  bool IsBalance()
  {
   if (_root == nullptr)
    return true;

   if (_root->_color == RED)
    return false;

   //参考值
   int refVal = 0;
   Node* cur = _root;
   while (cur)
   {
    if (cur->_color == BLACK)
    {
     ++refVal;
    }

    cur = cur->_left;
   }

   int blacknum = 0;
   return Check(_root, blacknum, refVal);
  }

红黑树类

  • 使用KV模型

  • 私有成员为红黑树根节点指针

  • 包含插入函数、旋转函数、中序遍历、判断是否是红黑树等

template<class K, class V>
 class RBTree
 {
  typedef RBTreeNode<K, V> Node;
 public:
  bool Insert(const pair<K, V>& kv)
  {
   if (_root == nullptr)
   {
    _root = new Node(kv);
    _root->_color = BLACK;
    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);
   cur->_color = RED;
   if (parent->_kv.first < kv.first)
   {
    parent->_right = cur;
    cur->_parent = parent;
   }
   else
   {
    parent->_left = cur;
    cur->_parent = parent;
   }

   while (parent && parent->_color == RED)
   {
    Node* grandfather = parent->_parent;
    if (parent == grandfather->_left)
    {

     Node* uncle = grandfather->_right;
     if (uncle && uncle->_color == RED)
     {

      parent->_color = uncle->_color = BLACK;
      grandfather->_color = RED;


      cur = grandfather;
      parent = cur->_parent;
     }
     else
     {
      if (cur == parent->_left)
      {

       RotateR(grandfather);
       parent->_color = BLACK;
       grandfather->_color = RED;
      }
      else
      {

       RotateL(parent);
       RotateL(grandfather);
       cur->_color = BLACK;
       grandfather->_color = RED;
      }

      break;
     }
    }
    else
    {

     Node* uncle = grandfather->_left;
     if (uncle && uncle->_color == RED)
     {

      parent->_color = uncle->_color = BLACK;
      grandfather->_color = RED;


      cur = grandfather;
     }
     else
     {
      if (cur == parent->_right)
      {
       RotateL(grandfather);
       parent->_color = BLACK;
       grandfather->_color = RED;
      }
      else
      {

       RotateR(parent);
       RotateL(grandfather);
       cur->_color = BLACK;
       grandfather->_color = RED;
      }

      break;
     }
    }
   }

   _root->_color = BLACK;

   return true;
  }

  void RotateL(Node* parent)
  {
   Node* subR = parent->_right;
   Node* subRL = subR->_left;

   parent->_right = subRL;
   subR->_left = parent;

   Node* parentParent = parent->_parent;

   parent->_parent = subR;
   if (subRL)
    subRL->_parent = parent;

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

    subR->_parent = parentParent;
   }
  }

  void RotateR(Node* parent)
  {
   Node* subL = parent->_left;
   Node* subLR = subL->_right;

   parent->_left = subLR;
   if (subLR)
    subLR->_parent = parent;

   Node* parentParent = parent->_parent;

   subL->_right = parent;
   parent->_parent = subL;

   if (_root == parent)
   {
    _root = subL;
    subL->_parent = nullptr;
   }
   else
   {
    if (parentParent->_left == parent)
    {
     parentParent->_left = subL;
    }
    else
    {
     parentParent->_right = subL;
    }

    subL->_parent = parentParent;
   }
  }

  void InOrder()
  {
   _InOrder(_root);
   cout << endl;
  }

  void _InOrder(Node* root)
  {
   if (root == nullptr)
    return;

   _InOrder(root->_left);
   cout << root->_kv.first << " ";
   _InOrder(root->_right);
  }

 private:
  // 根节点->当前节点这条路径的黑色节点的数量
  bool Check(Node* root, int blacknum, const int refVal)
  {
   if (root == nullptr)
   {
    //cout << balcknum << endl;
    if (blacknum != refVal)
    {
     cout << "存在黑色节点数量不相等的路径" << endl;
     return false;
    }

    return true;
   }

   if (root->_color == RED && root->_parent->_color == RED)
   {
    cout << "有连续的红色节点" << endl;

    return false;
   }

   if (root->_color == BLACK)
   {
    ++blacknum;
   }

   return Check(root->_left, blacknum, refVal)
    && Check(root->_right, blacknum, refVal);
  }
 public:
  bool IsBalance()
  {
   if (_root == nullptr)
    return true;

   if (_root->_color == RED)
    return false;

   //参考值
   int refVal = 0;
   Node* cur = _root;
   while (cur)
   {
    if (cur->_color == BLACK)
    {
     ++refVal;
    }

    cur = cur->_left;
   }

   int blacknum = 0;
   return Check(_root, blacknum, refVal);
  }

 private:
  Node* _root = nullptr;
 };
}

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

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

相关文章

数据结构进阶篇 之 【并归排序】(递归与非递归实现)详细讲解

都说贪小便宜吃大亏&#xff0c;但吃亏是福&#xff0c;那不就是贪小便宜吃大福了吗 一、并归排序 MergeSort 1.基本思想 2.实现原理 3.代码实现 4.归并排序的特性总结 二、非递归并归排序实现 三、完结撒❀ –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀…

Leetcode链表刷题总结(Java版)

链表 1、移除链表元素&#xff08;考虑全情况&#xff09; 问题需求&#xff1a;根据给定的val值&#xff0c;移除链表中值是这个val的节点 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 这里有一个问题就是&#xff0c;如果需要被移除的节点不是中间的某个节点…

【蓝桥杯嵌入式】六、真题演练(二)-1研究篇:第 13 届真题-第二部分

温馨提示&#xff1a; 真题演练分为模拟篇和研究篇。本专栏的主要作用是记录我的备赛过程&#xff0c;我打算先自己做一遍&#xff0c;把遇到的问题和不同之处记录到演练篇&#xff0c;然后再返回来仔细研究一下&#xff0c;找到最佳的解题方法记录到研究篇。题目在&#xff1a…

【CSS】浮动笔记及案例

CSS浮动 1. 认识浮动 float属性可以指定一个元素沿着左侧或者是右侧放置&#xff0c;允许文本和内联元素环绕它 float属性最初只使用文字环绕图片但却是早起CSS最好用的左右布局方案 绝对定位、浮动都会让元素脱标&#xff0c;以达到灵活布局的目的可以通过float属性让元素脱…

SSM实战项目——哈哈音乐(二)后台模块开发

1、项目准备 ① 引入后台模块&#xff08;hami-console&#xff09;需要的依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0…

【Qt】:常用控件(五:显示类控件)

常用控件 一.ProgressBar二. Calendar Widget 一.ProgressBar 使⽤ QProgressBar 表⽰⼀个进度条 代码⽰例:设置进度条按时间增⻓ 设置定时器&#xff0c;每个0.1秒&#xff0c;让进度条1 在实际开发中&#xff0c;进度条的取值&#xff0c;往往是根据当前任务的实际进度来进行…

创意绘图画画小程序:融合白板黑板功能,开启绘画新纪元

创意绘图画画小程序&#xff1a;融合白板黑板功能&#xff0c;开启绘画新纪元 在数字化时代的浪潮下&#xff0c;艺术创作正逐渐摆脱传统形式的束缚&#xff0c;以更加多元、便捷的方式走进人们的生活。其中&#xff0c;创意绘图画画小程序以其独特的白板画、黑板画功能&#…

蓝桥杯真题:k倍区间

import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int k sc.nextInt();int[] a new int[n];long[] v new long[200000];long…

【注册中心】ZooKeeper

文章目录 概述Zookeeper的应用场景Zookeeper的角色Zookeeper 的数据模型zookeeper客户端常用命令Zookeeper的核心功能Zookeeper的架构与集群规则Zookeeper的工作模式Zookeeper如何实现分布式锁Zookeeper JavaAPI&#xff08;Curator&#xff09;来源 概述 Zookeeper 是一个开源…

手写防抖节流、手写深拷贝、事件总线

一、防抖 手写防抖--基本实现&#xff08;面试&#xff09; 手写防抖并且绑定this和event 添加取消功能 添加立即执行状态&#xff0c;默认不立即执行 underscore库介绍&#xff0c;lodash更轻量级 二、节流 用underscore库&#xff0c;调用throttle函数 手写基础版节流-&#…

Spring重点知识(个人整理笔记)

目录 1. 为什么要使用 spring&#xff1f; 2. 解释一下什么是 Aop&#xff1f; 3. AOP有哪些实现方式&#xff1f; 4. Spring AOP的实现原理 5. JDK动态代理和CGLIB动态代理的区别&#xff1f; 6. 解释一下什么是 ioc&#xff1f; 7. spring 有哪些主要模块&#xff1f;…

数据结构系列-队列的结构和队列的实现

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 队列 队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO&#xff0c;…

Python | Leetcode Python题解之第10题正则表达式匹配

题目&#xff1a; 题解&#xff1a; class Solution:def isMatch(self, s: str, p: str) -> bool:m, n len(s), len(p)dp [False] * (n1)# 初始化dp[0] Truefor j in range(1, n1):if p[j-1] *:dp[j] dp[j-2]# 状态更新for i in range(1, m1):dp2 [False] * (n1) …

【C++】排序算法 --快速排序与归并排序

目录 颜色分类&#xff08;数组分三块思想&#xff09;快速排序归并排序 颜色分类&#xff08;数组分三块思想&#xff09; 给定⼀个包含红⾊、⽩⾊和蓝⾊、共 n 个元素的数组 nums &#xff0c;原地对它们进⾏排序&#xff0c;使得相同颜⾊ 的元素相邻&#xff0c;并按照红⾊、…

【面试八股总结】传输控制协议TCP(一)

参考资料 &#xff1a;小林Coding、阿秀、代码随想录 一、什么是TCP协议 TCP是传输控制协议Transmission Control Protocol TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。 面向连接的&#xff1a;每条TCP连接杜只能有两个端点&#xff0c;每一条TCP连接只能是点对…

js中的事件循环

浏览器进程模型 在理解什么叫事件循环前&#xff0c;我们需要先知道浏览器的进程模型 现代浏览器的功能极度复杂&#xff0c;为了能确保各个部分独立运行互不影响&#xff0c;浏览器会在启动之时开启多个进程&#xff0c;具体而言可以分为以下三种 浏览器进程 负责浏览器的用…

Pulsar服务端处理消费者请求以及源码解析

引言 处理读写是Pulsar服务端最基本也是最重要的逻辑&#xff0c;今天就重点看看服务端是如何处理的读请求也就是消费者请求 正文 Pulsar服务端处理消费者请求的流程大致如下图所示 消费者通过TCP向服务端发起消息拉取请求Broker会根据请求中携带的ID来获取在服务端对应的…

Lua 和 Love 2d 教程 二十一点朴克牌 (上篇lua源码)

GitCode - 开发者的代码家园 Lua版完整原码 规则 庄家和玩家各发两张牌。庄家的第一张牌对玩家是隐藏的。 玩家可以拿牌&#xff08;即拿另一张牌&#xff09;或 停牌&#xff08;即停止拿牌&#xff09;。 如果玩家手牌的总价值超过 21&#xff0c;那么他们就爆掉了。 面牌…

WIFI|软体 茶凳浅谈 高通WIN QSDK - IPQ6000 与 88Q2112 的相遇

Qualcomm IPQ 系列的Ethernet IC 搭配的有 QCA8075, QCA8081 … 等等Qualcomm自家出产的芯片。QSDK中内建可以支持的3rd party芯片&#xff0c;却寥寥可数。日前&#xff0c;客户使用车载以太网 - 88Q2112 - Marvell与IPQ6000做搭配。将之记录下来&#xff0c;以供参考。 方…

传输层 --- TCP (下篇)

目录 1. 超时重传 1.1. 数据段丢包 1.2. 接收方发送的ACK丢包 1.3. 超时重传的超时时间如何设置 2. 流量控制 3. 滑动窗口 3.1. 初步理解滑动窗口 3.2. 滑动窗口的完善理解 3.3. 关于快重传的补充 3.4. 快重传和超时重传的区别 4. 拥塞控制 4.1. 拥塞控制的宏观认识…