Java数据结构(七)——优先级队列与PriorityQueue

news2024/12/23 20:18:13

文章目录

  • 优先级队列与PriorityQueue
      • 基本概念和性质
      • 建堆
      • 堆的插入
      • 堆的删除
      • 堆的应用
    • PriorityQueue
      • PriorityQueue的构造方法
      • PriorityQueue的常用方法
      • PriorityQueue的模拟实现
    • 经典TopK问题

优先级队列与PriorityQueue

优先级队列是一种特殊类型的队列,其中元素按照优先级进行排序,最高优先级的元素最先被取出。其概念源于对元素进行优先级排序的需求。与普通队列先进先出(FIFO)的原则不同,优先级队列中的元素根据其优先级值进行排列和取出。在许多场景下,如任务调度、交通信号管理等,优先级队列都发挥着重要作用。

Java中的优先级队列(PriorityQueue)是一种利用 数据结构实现的、按照元素优先级排序的队列,它允许元素以任意顺序插入,但取出时会按照优先级高低进行排序。

基本概念和性质

是一种特殊的完全二叉树,其中每个节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。

准确来说,堆在逻辑上是一棵完全二叉树,物理结构上常用数组表示,存储时按照层序遍历的次序存入数组。
所以,我们先要掌握堆的相关知识。

堆的两个特性:

  1. 结构性: 用数组表示一棵完全二叉树
  2. 有序性: 任一结点的关键字是其子树所有节点的最大值(或最小值)。
    “最大堆”,也称“大顶堆”(或“大根堆”),简称大堆。特点是:父亲大于等于孩子
    “最小堆”,也称“小顶堆”(或“小根堆”),简称小堆。特点是:父亲小于等于孩子

在这里插入图片描述

观察图片,最大堆中第二层的7小于第三层的9和11,因为堆只要求父结点大于(或小于)自己的子结点,而不要求大于(或小于)其同层结点的子结点。不要认为第k层的结点都大于(小于)第k+1层!


由于堆的存储方式是数组以及堆性质的特殊性,有以下公式(parent等均表示数组下标,以第一个公式为例,其含义:父结点的下标 = (任一孩子下标 - 1) / 2):

  • parent = (child - 1) / 2
  • leftchild = parent * 2 + 1
  • rightchild = parent * 2 + 2

第一个公式结果小于0时,代表没有父结点;当第二、三个公式的结果大于数组最后一个元素的下标时,代表没有孩子结点。

练习:

下列关键字序列为堆的是()

A. 60,70,65,50,32,100 B. 100,60,70,50,32,65 C. 32,50,100,70,65,60 D. 70,65,100,32,50,60

解: 由于堆逻辑上是一棵完全二叉树,存储时按照层序遍历顺序,所以直接画图就可以解决问题。不想画图,也可以较容易地观察:A中60>70,所以要建立小根堆,第二层70和65均大于60,但第三层的50显然小于70,不满足堆,排除;B中100>60,所以要建立大根堆,第二层60和70均小于100,第三层的50和32小于其父亲60,65小于其父亲70,满足大根堆,故选择B;C、D同理,可分析得不满足堆得性质。


建堆

给我们一个数组,怎样将其建为堆呢?向下调整算法 是最常用的方法,其更高效、更稳定。

向下调整算法的基本思想是 从根节点开始,逐步将父节点与子节点进行比较和必要的交换,直到整个堆满足堆的性质。

向下调整算法的前提是 左右子树均为堆。

向下调整算法的结果是根结点最终到达能使整棵树满足堆性质的位置

向下调整算法的基本思想中涉及父结点与子结点的交换,其规则为:

  • 如果要建立小根堆,则将其与左右孩子中较小的一个作比较,如果比 较小孩子 大(证明较小孩子是父、左右孩子三个结点中最小的),将根结点与较小孩子交换,到达新位置,继续与新的左右孩子的较小值比较,如果比 较小孩子 大,交换,以此类推,直到某次比 较小孩子 小或不存在孩子,结束。
  • 如果要建立大根堆,则将其与左右孩子中较大的一个作比较,如果比 较大孩子 小(证明较大孩子是父、左右孩子三个结点中最大的),将根结点与较大孩子交换,到达新位置,继续与新的左右孩子的较大值比较,如果比 较大孩子 小,交换,以此类推,直到某次比 较大孩子 大或不存在孩子,结束。

下图演示了对根结点27进行向下调整算法建小根堆的过程:

在这里插入图片描述


但是,我们不能保证所给数组天生满足根结点的左右子树均为堆,即不能从整棵的根结点开始执行向下调整算法。

既然不能从根结点开始向下调整,我们考虑从叶子结点开始执行向下调整,但叶子结点不存在左右孩子,即叶子结点作为根结点的树本身就满足堆,所以我们要从倒数第一个非叶子结点开始

堆存储在数组里,我们怎么确定倒数第一个非叶子结点的下标呢?

倒数第一个非叶子结点就是倒数第一个结点(即数组最后一个元素)的父结点,根据公式parent = (child - 1) / 2child传入数组最后一个元素的下标,就能得到向下调整算法开始执行的结点。

对倒数第一个非叶子结点执行向下调整算法后,继续对倒数第二个结点执行,以此类推,直到整棵树的根结点执行完向下调整算法,即可建堆成功!


代码实现:(以建小堆为例)

    public void createHeap(int[] array) {
        //从倒数第一个非叶子结点开始向下调整
        for(int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            //向下调整代码:
            int parent = i;
            int child = parent * 2 + 1;
            while(child < array.length) {
                //寻找较小结点
                if (child + 1 < array.length && array[child + 1] < array[child]) {
                    child += 1;
                }
                //无需再判断child + 1 < array.length
                if (array[parent] > array[child]) {
                    int tmp = array[parent];
                    array[parent] = array[child];
                    array[child] = tmp;
                    parent = child;
                    child = parent * 2 + 1;
                }else {
                    break;
                }
            }
        }
    }
  • 每一次向下调整: 进循环前先保存父结点下标,再计算出左孩子的下标(假设左孩子是较小孩子);判断child下标是否存在,尝试进入循环;进入循环后,寻找较小孩子,(注意判断条件必须包含child + 1 < array.length且必须是第一个条件)如果之前认为左孩子是较小孩子的假设不成立,那么就更新child的值;判断父结点和较小孩子结点的大小,如果父结点小于较小结点,证明父结点是三个结点中最小的,直接跳出循环,否则,交换较小孩子和父结点,更新parent,接着计算新的父结点的左孩子下标并更新。继续尝试进入循环…

  • 特别注意当找到较小结点后,判断父结点和较小结点大小时,不需要先判断child + 1 < array.length,因为

    参与调整的结点一定存在孩子,否则不会进入while循环,一定存在则child的值是有效值。可能存在调整结点只有左孩子没有右孩子的情况,这时在寻找较小结点时,child的值不会变化,表示存在的左孩子的下标,调整的结点只要有孩子,就必须与孩子进行比较来维持堆的结构,如果此时判断父结点和较小结点大小时,加上child + 1 < array.length条件,就会因为调整结点没有右孩子而导致左孩子没有与父结点比较,这导致建堆可能失败。例如非堆序列 [32,50,100,70,65,60] ,如果犯了上述错误,则建堆结果与原序列一致,建堆失败!


堆的插入

当向堆中插入新元素时,先将其存储在数组已有元素后一个位置,然后 向上调整 到合适的位置以保证堆的结构。

下图为向一个大根堆中插入新元素11的向上调整的过程,整个过程要不断与父亲结点比较并不断上调,直到满足堆的性质。

实现堆(push/pop)_堆的pop-CSDN博客

    public void offer(int val) {
        if(isFull()) {
            throw new ElemIsFullException("the elemArray is full!:已满");
        }
        elem[usedSize] = val;
        shiftUp(usedSize);
        usedSize++;
    }

    //向上调整
    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while(parent >= 0) {
            if(elem[child] < elem[parent]) {
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }
  • 此处堆插入的代码实现是模拟实现PriorityQueue代码的一部分,掌握插入的逻辑以及向上调整算法。其余的代码会在模拟实现阶段再见。

堆的删除

堆的删除一般指删除堆顶元素,具体操作是:将堆顶元素与最后一个元素交换,然后在认为最后一个元素被删除的情况下向下调整,继续维持堆的结构。

    public int poll() {
        if(isEmpty()) {
            throw new ElemIsEmptyException("elem is empty!:无元素可以删除!");
        }
        int ret = elem[usedSize-1];
        elem[usedSize-1] = elem[0];
        elem[0] = ret;
        //注意交换后认为元素已被删除,接下来要向下调整维持堆结构
        usedSize--;
        shiftDown(0, usedSize);
        return ret;
    }

    //向下调整
    private void shiftDown(int root, int len) {
        int parent = root;
        int child = parent * 2 + 1;
        while(child < len) {
            if (child + 1 < len && elem[child + 1] < elem[child]) {
                child += 1;
            }
            if (elem[parent] > elem[child]) {
                int tmp = elem[parent];
                elem[parent] = elem[child];
                elem[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }
  • 此处堆删除的代码实现是模拟实现PriorityQueue代码的一部分,掌握删除的逻辑是先交换头尾再向下调整维持堆结构。其余的代码会在模拟实现阶段再见。

堆的应用

堆最常见的应用除了实现优先级队列外,还有:

  • 堆排序
  • TopK问题
  • 频率统计和数据压缩
  • 合并有序小文件

堆排序是十分重要的一种排序方法,后续会在排序章节介绍,TopK问题是本文最后一个问题。


PriorityQueue

在Java的集合框架中,PriorityQueue 是一个基于优先级堆实现的的无界优先级队列。

其基本特性如下:

  • 不允许使用 null 元素也不允许插入不可比较的对象(即没有实现 Comparable 接口的对象,或者在创建时没有提供 Comparator 的情况下)。
  • 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  • 默认情况下为小根堆,即每次获取到的元素都是最小元素

更多具体内容如下:

PriorityQueue的构造方法

构造方法说明
PriorityQueue()创建一个空的优先级队列,默认容量为11
PriorityQueue(int initialCapacity)创建一个初始容量为initalCapacity的优先级队列,但不能指定小于1,否则会抛出异常
PriorityQueue(Collection<? extends E> c)创建一个包含指定集合元素的 PriorityQueue优先级队列
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)创建一个具有指定初始容量和指定比较器的空 PriorityQueue优先级队列
PriorityQueue(Collection<? extends E> c, Comparator<? super E> comparator)创建一个包含指定集合元素的 PriorityQueue,并根据提供的 Comparator 进行排序。

PriorityQueue默认是小顶堆,如果我们需要一个大顶堆,则需要提供一个自定义的Comparator来实现这一点,该自定义的Comparator需要反转排序规则,这样就能得到一个大顶堆。

Comparator.reverseOrder() 是 Java 中 Comparator 接口的一个静态方法,它返回了一个实现了 Comparator 接口的 Comparator 实例,这个实例可以对实现了 Comparable 接口的对象集合进行逆序排序。换句话说,它创建了一个比较器,该比较器会将较大的元素视为较低(或“更好”的)优先级,从而与默认的自然顺序(通常是升序)相反。

例如:

//默认小根堆
PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>();

//指定为大根堆
PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>(Comparator.reverseOrder());

PriorityQueue的常用方法

方法功能
boolean offer(E e)插入元素,注意插入的元素不能为空且要可比较
E peek()获取优先级最高的元素(堆顶元素),如果优先级队列为空,返回null
E poll()删除优先级最高的元素(堆顶元素),如果优先级队列为空,返回null
int size()获取有效元素个数
void clear()清空
boolean isEmpty()检测优先级队列是否为空

PriorityQueue的模拟实现

以int类型为例对关键逻辑进行了实现,同时模拟实现的没有涉及扩容,这与PriorityQueue不同,我们主要掌握建堆、堆的删除、堆的插入操作。

public class ExceedTheCapacityException extends RuntimeException {
    public ExceedTheCapacityException(String message) {
        super(message);
    }
}

public class ElemIsFullException extends RuntimeException {
    public ElemIsFullException(String message) {
        super(message);
    }
}

public class ElemIsEmptyException extends RuntimeException {
    public ElemIsEmptyException(String message) {
        super(message);
    }
}


public class MyPriorityQueue {
    
    public int[] elem;
    public int usedSize;

    //默认分配的容量
    private static final int DEFAULT_INIT_CAPACITY = 11;

    public MyPriorityQueue() {
        this(DEFAULT_INIT_CAPACITY);
    }

    public MyPriorityQueue(int capacity) {
        this.elem = new int[capacity];
    }


    private void initElem(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }

    public void createHeap(int[] array) {
        if(array.length > elem.length) {
            throw new ExceedTheCapacityException("exceed the capacity!:传入的数组超出了容量");
        }
        initElem(array);
        for(int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(i, this.usedSize);
        }
    }

    private void shiftDown(int root, int len) {
        int parent = root;
        int child = parent * 2 + 1;
        while(child < len) {
            //寻找较小结点
            if (child + 1 < len && elem[child + 1] < elem[child]) {
                child += 1;
            }
            //无需再判断child + 1 < array.length
            if (elem[parent] > elem[child]) {
                int tmp = elem[parent];
                elem[parent] = elem[child];
                elem[child] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }

    public void offer(int val) {
        if(isFull()) {
            throw new ElemIsFullException("the elemArray is full!:已满");
        }
        elem[usedSize] = val;
        shiftUp(usedSize);
        usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while(parent >= 0) {
            if(elem[child] < elem[parent]) {
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }

    public boolean isFull() {
        return elem.length == usedSize;
    }

    public int poll() {
        if(isEmpty()) {
            throw new ElemIsEmptyException("elem is empty!:无元素可以删除!");
        }
        int ret = elem[usedSize-1];
        elem[usedSize-1] = elem[0];
        elem[0] = ret;
        usedSize--;
        shiftDown(0, usedSize);
        return ret;
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }


    public int peek() {
        return elem[0];
    }
}

经典TopK问题

TopK问题,即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。比如,世界500强,游戏排行榜。

TopK问题一定可以使用排序来解决问题,但是当数据量比较庞大时,排序就很难高效解决问题了。这时,使用堆就能更好的解决问题,具体思想如下:

  1. 用数据集合中前K个元素来建堆
    • 前K个最大的元素,建小堆
    • 前K个最小的元素,建大堆
  2. 用剩余的N-K个元素依次与堆顶元素进行比较,不满足则替换堆顶元素

例如,对于500条数据,寻找前K个最大的数据元素,将集合前K个元素建立小堆,此时堆顶元素就是这K个建堆元素中最小的,也是最有可能被替换的,然后,遍历剩余的N-K个元素,分别与堆顶元素比较,如果比堆顶元素大,说明该元素可以入选当前的前10,替换堆顶元素。我们可以利用PriorityQueue很好的解决这一类问题,而不需要特意编写堆的插入和删除代码,这是我们学习集合框架的意义。

【例题】

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

class Solution {
    public int[] smallestK(int[] arr, int k) {
        //补充代码
    }
}

利用上述思路即可解决问题:

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(k == 0) {
            return ret;
        }
        //“小堆转大堆”
        PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.reverseOrder());
        //将前k个元素offer
        for(int i = 0; i < k; i++) {
            q.offer(arr[i]);
        }
        //遍历剩余元素,与堆顶元素比较,如果比堆顶元素小,则删除现堆顶,插入该元素
        for(int i = k; i < arr.length; i++) {
            if(arr[i] < q.peek()) {
                q.poll();
                q.offer(arr[i]);
            }
        }
        //将PriorityQueue中的元素都放入数组中,返回
        for(int i = 0; i < k; i++) {
            ret[i] = q.poll();
        }
        return ret;
    }
}

原题链接:面试题 17.14. 最小K个数 - 力扣(LeetCode)


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

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

相关文章

聊聊 OceanBase 内存管理

内存配置管理是数据库日常管理中非常重要的工作&#xff0c;正确合理配置数据库内存是保障系统高效运行的前提条件。 OceanBase 数据库是一个支持多租户架构的准内存级的分布式数据库&#xff0c;对大容量内存的管理和使用提出了很高的要求。实际使用上&#xff0c;OceanBase …

1.【R语言】R语言的下载和安装

R语言是一种开源编程语言&#xff0c;它提供了丰富的统计模型和图形绘制功能&#xff0c;广泛用于数据科学、统计分析、数据挖掘和机器学习。R有一个活跃的社区和大量的包&#xff0c;可以满足各种需求&#xff0c;如数据清洗、绘图和报告生成。其强大的数据处理能力和灵活的可…

【ESP-IDF FreeRTOS】队列管理

先包含下头文件。 #include "freertos/queue.h" 队列大家应该不陌生&#xff0c;就是一个先进先出的容器。用在FreeRTOS里用途就多了。 首先是可以让任务与任务之间以及中断之间通信&#xff0c;任务A把数据塞进队列再让任务B取出&#xff0c;这样就可以传递数据了…

BMC lighttpd kvm数据分析(websocket)

1.说明 lighttpd源码: https://github.com/lighttpd/lighttpd1.4.gitlighttpd wiki: https://redmine.lighttpd.net/projects/lighttpd/wiki/libfcgi: https://github.com/toshic/libfcgi/tree/master 注意: 本章的代码仓库: https://gitee.com/wit_yuan/lighttpd_kvm 2.编…

3127.构造相同颜色的正方形

1.题目描述 给你一个二维 3 x 3 的矩阵 grid &#xff0c;每个格子都是一个字符&#xff0c;要么是 B &#xff0c;要么是 W 。字符 W 表示白色&#xff0c;字符 B 表示黑色。 你的任务是改变 至多一个 格子的颜色&#xff0c;使得矩阵中存在一个 2 x 2 颜色完全相同的正方形。…

无敌美少男和无敌美少女构建企业级私有仓库(harbor)

一&#xff1a;harbor简介 Harbor 是由 vmware 公司开源的企业级 Docker Registry 项目。 它提供了以下主要功能和特点&#xff1a; 基于角色的访问控制&#xff08;RBAC&#xff09;&#xff1a;可以为不同的用户和用户组分配不同的权限&#xff0c;增强了安全性和管理的灵…

Linux下的MySQL8.0报错:[Err]1055

Linux下的MySQL8.0报错&#xff1a;[Err]1055 报错信息解决办法 报错信息 在Linux环境下的MySQL里执行SQL语句报如下错误&#xff1a;[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column information_schema.PROFIL…

maven 父子工程创建详解

maven 父子工程创建详解 一、Maven工程继承关系 继承概念 maven继承是指的Maven的项目中&#xff0c;让一个项目从另外项目中继承配置信息的机制。继承可以让我们在多个项目中共享同一个配置信息&#xff0c;简化项目的管理和维护工作 继承作用&#xff1a;在父工程中统一管理…

Datawhale AI夏令营 第五期 CV方向 Task3笔记

Task3&#xff1a;上分思路——数据集增强与模型预测 Part1&#xff1a;数据增强 数据增强是机器学习和深度学习中的一种技术&#xff0c;通过在原始数据集上应用一系列变换来人工地增加数据样本的数量和多样性&#xff0c;从而提高模型的泛化能力&#xff0c;减少过拟合&…

简单的 nginx 学习

简单的 nginx 学习 1. nginx的安装 1.1 下载安装包 去官网下载对应的nginx包&#xff0c;推荐使用稳定版本&#xff0c;上传nginx到linux系统 1.2 安装依赖环境 安装gcc环境 yum install gcc-c安装PCRE库&#xff0c;用于解析正则表达式 yum install -y pcre pcre-develzlib压…

【立体匹配】双目相机外参自标定方法介绍

双目相机外参自标定方法 原理实践 双目相机外参自标定方法是一种无需固定标定板&#xff0c;在拍摄实际场景的两张图像时&#xff0c;通过计算两幅图像之间的匹配特征点对&#xff0c;结合相机的内参矩阵&#xff0c;来实时求解两个相机之间相对位置&#xff08;即外参&#xf…

ThermoParser 介绍

ThermoParser是一个工具包&#xff0c;用于简化专业材料科学代码产生的数据分析&#xff0c;以热电学为中心&#xff0c;但也适用于任何与电子和/或声子传输有关的内容。ThermoParser是一个Python库&#xff0c;它包含数据检索、操作和绘图的函数&#xff0c;只需几行代码就可以…

HashMap 链表转红黑树的阈值为何为 8

与一个重要的统计学原理——泊松分布密切相关&#xff1a;该原理阐明了在单位时间&#xff08;或面积、体积&#xff09;内&#xff0c;随机事件的平均发生次数遵循泊松分布 为什么这因子设定为0.5呢&#xff1f; 在忽略方差的情况下&#xff0c;哈希表容量占比的期望值约为 0.…

揭秘扩散模型:DDPM的数学基础与代码实现全攻略!

(DDPM) denoising diffusion probabilistic models 理论学习 本文价值 本文是 Diffusion 这一类模型的开山之作&#xff0c;首次证明 diffusion 模型能够生成高质量的图片&#xff0c;且奠定了所有后续模型的基本原理&#xff1a;加噪 --> 去噪。DDPM 模型的效果如下&#x…

springboot+vue+mybatis计算机毕业设计飞机订票系统+PPT+论文+讲解+售后

快速发展的社会中&#xff0c;人们的生活水平都在提高&#xff0c;生活节奏也在逐渐加快。为了节省时间和提高工作效率&#xff0c;越来越多的人选择利用互联网进行线上打理各种事务&#xff0c;然后线上管理系统也就相继涌现。与此同时&#xff0c;人们开始接受方便的生活方式…

IDEA向mysql写入中文字符时出现乱码问题

可参考该博客&#xff1a;https://www.cnblogs.com/bb1008/p/7704458.html 第一步是将IDEA软件中的编码方式全部改为utf8 File -> Settings -> Editor -> File Encodings 第二步是在数据库链接中加入 ?characterEncodingUTF-8

备战2024年全国大学生数学建模竞赛:蔬菜类商品的自动定价与补货决策

目录 一、引言 二、问题分析 三、解题思路 问题1&#xff1a;销售量分布规律及相互关系 问题2&#xff1a;品类级别的补货计划与定价策略 问题3&#xff1a;单品级别的补货计划与定价策略 问题4&#xff1a;补充数据的建议与分析 四、知识点解析 五、模型建立与求解 1…

没有永远免费的加速器,但是永远有免费的加速器【20240831更新】

没有永远免费的加速器&#xff0c;但是永远有免费的加速器【每日更新】 一、迅雷加速器&#xff08;免费时长最高38天&#xff09; 可免费时长&#xff1a;8天 如果是迅雷会员&#xff0c;则免费时长为38天 官网下载链接&#xff1a;迅雷加速器—迅雷官方出品&#xff0c;为快…

关于数字存储和byte[]数组的一些心得

前言 最近做项目&#xff0c;发现一些规律&#xff0c;比如数字的存储和字符串的存储&#xff0c;先说数字吧&#xff0c;最常见的整数&#xff0c;就涉及反码和补码&#xff0c;根据这些规则&#xff0c;甚至我们自己也能造一种数据存储结构&#xff0c;比如1个字节8bit&…

bbr 和 inflight 守恒的收敛原理

先看 bbr&#xff0c;以 2 条流 bw 收敛为例&#xff0c;微分方程组如下&#xff1a; { d x d t C ⋅ g ⋅ x g ⋅ x y − x d y d t C ⋅ g ⋅ y g ⋅ y x − y \begin{cases} \dfrac{dx}{dt}C\cdot\dfrac{g\cdot x}{g\cdot xy}-x\\\ \dfrac{dy}{dt}C\cdot\dfrac{g\cdot y…