当前位置: 首页 > news >正文

图详解第四篇:单源最短路径--Dijkstra算法

文章目录

  • 1. 最短路径问题
  • 2. 单源最短路径--Dijkstra算法
    • 算法思想
    • 图解
    • 如何存储路径及其权值
    • 代码实现
    • 调式观察
    • 打印最短路径
    • Dijkstra算法的缺陷
  • 3. 源码

1. 最短路径问题

最短路径问题:

从带权有向图(求最短路径通常是有向图)G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

那下面我们就要来学习几个求最短路径的算法

2. 单源最短路径–Dijkstra算法

这篇文章我们先来学习第一个求单源最短路径的算法——迪杰斯特拉算法(Dijkstra),是由荷兰计算机科学家狄克斯特拉于1959年提出的,然后后面我们还会学到求多源最短路径的算法。

所以这里先给大家介绍一下什么是单源最短路径,什么是多源最短路径:

单源最短路径指的是从一个源节点出发,计算到其他所有节点的最短路径。也就是说,在单源最短路径问题中,只需要确定一个起点,然后计算该起点到图中所有其他节点的最短距离。
多源最短路径则是在图中计算任意两个节点之间的最短路径。换言之,需要求解所有可能的起点和终点之间的最短路径。

那下面我们就来学习一下第一个求单源最短路径的算法——Dijkstra算法

算法思想

首先我们可以先从概念上了解一下Dijkstra算法的思想:

单源最短路径问题:给定一个图G = ( V , E ) ,求源结点s ∈ V 到图中每个结点v ∈ V的最短路径。Dijkstra算法就适用于解决带权重的有向图上的单源最短路径问题,同时算法要求图中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。
针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合,每次从Q 中找出一个从起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v (且v不在S中)进行松弛操作。松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。
Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。

Dijkstra算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径,这个我们后面也会给大家演示。

图解

那只看上面的概念的话,大家可能还不是特别理解,那下面我们来画图带大家分析一下

首先,我们可以先来看一下算法导论上给出的图解:

在这里插入图片描述
大家可以自己先看一下

然后,我来带大家走一遍这个过程:

其实就按照上面的思想一步步走就行了。
按照上面说的,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,Q 为其余未确定最短路径的结点集合。
那起始的时候,可以认为S是空的,所有结点都在Q里面。
然后这里选择的起点是s
在这里插入图片描述
每次从Q 中找出一个从起点到该结点代价最小的结点u,那第一次这个结点u就是s,可以认为s到s的距离是0(图中每个结点里面的值就表示当前从起点到自己的最短路径,还没更新的路径用标识),那把s结点放到S集合里面,Q中删去s;
然后对s 的每一个相邻结点v 进行松弛操作(其实去更新起点到它相邻点的距离),s到它相邻的两个结点距离s-t为10,s-y为5,都比原来从起点到它们的距离小,所以更新
在这里插入图片描述
然后再从Q里面找一个到起点路径最短的点,那这次找到的是y(此时s-y为5是最小的),把y从Q中移除,放入S里面;
然后对y进行松弛操作
在这里插入图片描述
y相邻的几个顶点到y的距离+y到起点s的距离都比之前起点到它们的距离短,所以都更新
接着继续从Q中选一个到起点距离最短的是z,z从Q中移出,放入S;
接着对x进行松弛操作,更新相应的距离
在这里插入图片描述
接着继续从Q中选一个到起点距离最短的是t,t从Q中移出,放入S;
接着对t进行松弛操作,更新相应的距离
在这里插入图片描述
再接着继续从Q中选一个到起点距离最短的是x,x从Q中移出,放入S;
接着再对x进行松弛操作
在这里插入图片描述
至此,集合Q 为空(起始Q是满的,所以n个结点的话,其实就选了n次去更新),即所有节点都已经查找过一遍并确定了最短路径
算法执行结束!

如何存储路径及其权值

相信算法现在大家已经理解了,但是还有一些问题需要我们解决:

既然我们是要求最短路径,那肯定得把相关的信息存储起来啊,上面图中直接把每个顶点对应最短路径的权值直接写到了结点里面,而且每条路径是怎么走的,经过了哪些顶点,我们也很容易看出来。

可是后面我们要写代码,那在写代码的时候我们如何把这些信息也存储起来呢?

🆗,是这样处理的:
因为每个顶点不是都映射一个下标嘛,所以我们就可以搞一个数组,每个下标映射的顶点是谁,这个位置就存储起点到这个顶点的最短路径。
那最开始就是这样的:
在这里插入图片描述
然后后面我们每次更新最短路径的时候修改里面的权值就行了
那上面存的是最短路径的权值,那路径又要如何存储呢?
一条路径可能会经过多个顶点啊。
🆗,那这里呢还是用一个一维数组就可以搞定:
怎么做呢?
很简单,每个顶点映射的位置存储路径上在它前面的那个顶点映射的下标,如果把路径看成一棵树的话,就是存储它的父结点的下标
比如最开始就可以这样存
在这里插入图片描述
首先s自己就是起点,可以认为最短路径就是s->s,所以它存自己的下标,然后剩下的顶点都还没有更新最短路径,起始存一个-1
接着每走一步就去更新数组就行了(存路径上它前面的那个结点(可以认为是它的父结点)的下标,类似并查集那里用的双亲表示法存储),那到最后的时候,就是这样的
在这里插入图片描述
那这样存储路径的话我们想要获取一个顶点的最短路径的时候,就从这个顶点开始顺序它的父亲(路径中的上一个结点)往上找就行了,找到起点停止,就是一条完整的路径(类似并查集里面的findRoot顺着父结点向上找根)。

代码实现

那下面我们就来实现一下代码:

首先需要给一个起点,然后两个vector存储最短路径及对应的路径权值在这里插入图片描述
然后,按照我们上面分析的思路走就行了
在这里插入图片描述
注释写的比较清楚,相信大家应该很容易可以看懂,说一点就是我们现在用的是邻接矩阵结构,所有查找u相邻的结点是去邻接矩阵_matrix里面找,如果下标[u][v]的位置对应的权值不是MAX_W,那它们就相连的,v就是u的一个相邻顶点,然后再判断如果源节点s到结点u 的代价与u 到v 的代价之和(其实就是距离嘛)是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和(更新距离)

调式观察

那这就实现好了,我们可以先通过调式观察一下:

在这里插入图片描述
我这里已经写好了一个测试用例(最后会给大家分享源码),这个图就是我们上面讲解思想的时候对应的那个图
我们来调式观察一下
在这里插入图片描述
对比一下我们之前分析的
在这里插入图片描述
没有问题,一模一样

打印最短路径

那下面呢我们可以写一个大于路径的函数,把最终得到的起点到各顶点的最短路径以及权值都打印出来看一下,和上面图上的是否一样:

在这里插入图片描述
那我们拿到这两个数组就可以去打印

但是这里打印的时候有一个问题:

按我们上面说的,找路径的时候通过pPath数组顺着结点的父亲或者说它的路径上的前一个结点,往上找就行了,找到起点停止。
但是!
这样找出来的路径是不是反的啊,因为我们最后找到的是起点,而正常情况应该是从起点开始嘛。
所以我们要处理一下,也很好搞:
我们可以搞一个vector把路径上的结点保存下来,然后逆置一下,再去打印就行了

来实现一下:

在这里插入图片描述

然后我们来打印一下看看:

在这里插入图片描述
在这里插入图片描述
大家可以对照一下,没有问题

Dijkstra算法的缺陷

但是呢,Dijkstra算法是有一些缺陷的,对于带有负权值的边的图,Dijkstra算法是搞不定的!

这里呢也准备了一个测试用例,我们可以来看一下:

在这里插入图片描述
首先它对应的一个图应该是这样的:
在这里插入图片描述
那我们现在对这个图执行Dijkstra算法并打印出来看一下:
在这里插入图片描述
是这样一个结果
在这里插入图片描述
但是我们会发现如果按照这个图有负权值的话,其实有一条最短路径其实没有更新出来,s->t->y应该是3(10+(-7)=3)
也就是说3应该是起点s到y的最短距离,s->t->y是最短路径。
图中带有负权路径时,贪心策略就失效了。

那为什么会这样呢?

因为按照Dijkstra算法的话
在这里插入图片描述
这里起点是s,所以第一次选到s,放到S集合里面,然后对s的相邻顶点进行松弛操作,更新距离s->t为10,s-y为5,所以第二次选到y,那y就被放到S集合里面了,而S是已经确定最短路径的结点集合,所以它这里的贪心策略就认为此时的5就是s->y的最短路径距离了(当然如果没有负权值的话这样是肯定正确的),y的最短路径已经确定了。
所以后面再去对t的相邻顶点进行松弛的时候就不会判断st+ty的距离是否小于sy,也不会再更新y的最短路径了,所以上面s->t->y就没有更新出来。因为每次都是从Q里面选的,而y前面已经放到S集合里面了。
但是有了负权值的话,sy的最短路径就不一定是5了(如果全是正的话肯定没问题),后面绕到其它的路径如果遇到负权值就可能会比5还小。

所以对于有负权值的图,Dijkstra算法就不再适用,这种贪心策略就失效了。

那对于有负权值的图我们如何求最短路径呢?

bellman—ford算法可以解决负权图的单源最短路径问题

这个我们下一篇文章就会讲到…

3. 源码

void Dijkstra(const V& src, vector<int>& pPath, vector<W>& dist)
{//初始化一下记录路径和权值(距离)的数组size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();pPath.resize(n, -1);dist.resize(n, MAX_W);//集合S为已确定最短路径的顶点集合,这里我们用一个数组表示S集合就可以//,初始化全false(S中无结点),表示所有顶点都在Q中,//不在S就在Q(其余未确定最短路径的结点集合)vector<bool> s(n, false);pPath[srci] = srci;dist[srci] = 0;//n个结点,需要选择n次去更新路径for (size_t i = 0; i < n; i++){int u = 0;W minDist = MAX_W;//从Q 中找出一个从起点到该结点代价最小的结点ufor (size_t j = 0; j < n; j++){if (s[i] == false && dist[i] < minDist){u = i;minDist = dist[i];}}//将u 从Q 中移出,并放入S 中s[u] = true;//对u 的每一个相邻结点v 进行松弛操作,如果src->u + u->v < src->v ,就更新距离for (size_t v = 0; v < n; v++){if (s[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];//同时更新记录路径的数组pPathpPath[v] = u;}}}}void ptintMinPath(const V& src, const vector<int>& pPath, const vector<W>& dist)
{size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; i++){if (i != srci)//起点-》起点就没必要打印了{//定义一个vector保存路径上的结点vector<int> path;//从当前结点开始顺着父结点往上走size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);//逆置path数组reverse(path.begin(), path.end());//打印路径for (auto e : path){cout << _vertexs[e] << "->";}//打印权值cout << dist[i] << endl;}}
}
void TestGraphDijkstra()
{/*const char* str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 10);g.AddEdge('s', 'y', 5);g.AddEdge('y', 't', 3);g.AddEdge('y', 'x', 9);g.AddEdge('y', 'z', 2);g.AddEdge('z', 's', 7);g.AddEdge('z', 'x', 6);g.AddEdge('t', 'y', 2);g.AddEdge('t', 'x', 1);g.AddEdge('x', 'z', 4);vector<int> dist;vector<int> pPath;g.Dijkstra('s', dist, pPath);g.ptintMinPath('s', dist, pPath);*/// 图中带有负权路径时,贪心策略则失效了。// 测试结果可以看到s->t->y之间的最短路径没更新出来const char* str = "sytx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 10);g.AddEdge('s', 'y', 5);g.AddEdge('t', 'y', -7);g.AddEdge('y', 'x', 3);vector<int> dist;vector<int> pPath;g.Dijkstra('s', dist, pPath);g.ptintMinPath('s', dist, pPath);
}

相关文章:

图详解第四篇:单源最短路径--Dijkstra算法

文章目录 1. 最短路径问题2. 单源最短路径--Dijkstra算法算法思想图解如何存储路径及其权值代码实现调式观察打印最短路径Dijkstra算法的缺陷 3. 源码 1. 最短路径问题 最短路径问题&#xff1a; 从带权有向图&#xff08;求最短路径通常是有向图&#xff09;G中的某一顶点出发…...

CRMEB多商户商城系统阿里云集群部署教程

注意: 1.所有服务创建时地域一定要选择一致,这里我用的是杭州K区 2.文件/图片上传一定要用类似oss的云文件服务, 本文不做演示 一、 创建容器镜像服务&#xff0c;容器镜像服务(aliyun.com) ,个人版本就可以 先创建一个命名空间 然后创建一个镜像仓库 查看并记录镜像公网地址…...

Java第三方登录封装工具类

Java中可以使用第三方登录来简化用户登录流程&#xff0c;常见的第三方登录如QQ、微信、微博等。下面是一个Java封装第三方登录的工具类&#xff1a; import java.io.IOException; import java.util.HashMap; import java.util.Map;import org.apache.http.client.ClientProto…...

BUUCTF学习(四): 文件包含tips

1、介绍 2、hackBar插件安装 教程&#xff1a; Hackbar插件安装-CSDN博客 3、解题 php://filter/readconvert.base64-encode/resource要读取的文件 ?filephp://filter/readconvert.base64-encode/resourceflag.php Base64 在线编码解码 | Base64 加密解密 - Base64.us 结束...

德国人工智能公司【Kodex AI】完成160万欧元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国柏林的人工智能公司【Kodex AI】今日宣布已完成160万欧元融资。 本轮融资由Signals VC领投&#xff0c;Techstars、德意志银行等天使投资者参与&#xff0c;其中包括:most AI首席执行官…...

LeetCode 2 两数相加

题目描述 链接&#xff1a;https://leetcode.cn/problems/add-two-numbers/?envTypefeatured-list&envId2ckc81c?envTypefeatured-list&envId2ckc81c 难度&#xff1a;中等 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式…...

springboot项目启动失败,不打印报错详细信息(启动打印日记问题)

1&#xff1a;出现这种我问题一般都是日记的问题&#xff0c;查看控制台启动打印的第一句&#xff0c;为什么启动失败&#xff0c;需要用那个日记 2&#xff1a;如果使用的是log4j或者logback与slf4j都是默认在依赖web自带的如下 <dependency><groupId>org.springf…...

MyBatis (where、set、foreach)标签

where标签 在上一节SQL 语句中加入了一个条件“11”&#xff0c;如果没有加入这个条件&#xff0c;那么可能就会变成下面这样一条错误的语句。 SELECT id,name,url,age,country FROM website AND name LIKE CONCAT(%,#{name},%)显然以上语句会出现 SQL 语法异常&#xff0c;但…...

flutter开发之安装dart

1、在MacOS系统中打开终端&#xff0c;进入到官网Get the Dart SDK | Dart brew tap dart-lang/dartbrew install dart 注意&#xff1a;若显示没有brew&#xff0c;请先执行第二步骤&#xff0c;如下&#xff1a; 2、打开homebrew的官网Homebrew — The Missing Package Man…...

向量召回:深入评估离线体系,探索优质召回方法

向量召回:深入评估离线体系,探索优质召回方法 1.简介 近年来,基于向量进行召回的做法在搜索和推荐领域都得到了比较广泛的应用,并且在学术界发表的论文中,基于向量的 dense retrieve 的方法也在不少数据集上都战胜了 sparse retrieve,吸引了越来越多的关注。在内网的不…...

播放器缓存队列bug解决方案

背景 我在开发一个播放器的缓存队列时&#xff0c;遇到一个bug,导致包和帧无法被下一个模块读取 找了半天&#xff0c;原来是队列中的包和帧数据要进行内容的刷新暂存 包数据和帧数据不能直接放入队列 //入队&#xff0c;包进队列 int AVPacketQueue::Push(AVPacket *val,i…...

React拖拽实践

当涉及到前端开发中的用户体验时&#xff0c;拖拽功能是一个常见而重要的需求。在React中&#xff0c;实现拖拽功能可以通过多种方式完成&#xff0c;但通常需要深刻理解React的状态管理、事件处理和DOM操作。本文将探讨React中拖拽的实践&#xff0c;包括基本原理、拖拽库的使…...

Stable Diffusion绘图,lora选择

best quality, ultra high res, (photorealistic:1.4), 1girl, off-shoulder white shirt, black tight skirt, black choker, (faded ash gray hair:1), looking at viewer, closeup <lora:koreandolllikeness_v20:0.66> 最佳品质&#xff0c;超高分辨率&#xff0c;&am…...

kube-controller-manager和kube-scheduler不能正常启动

kube-controller-manager-k8s-worker01和kube-scheduler-k8s-worker01没有启动起来 原因&#xff1a; 解决&#xff1a;进入/etc/kubernetes/manifests 编辑 将镜像地址修改为 然后重启kubelet&#xff1a;systemctl restart kubelet.service...

Mac OS m1 下安装Gradle5.1

1. 下载、解压 1.1 下载地址 https://gradle.org 往下翻 选择 5.1 或者选择 任何 你想要的版本 ,点击 binary-only 即可下载 . 1.2 解压到指定目录 2. 配置环境变量 2.1 编辑环境文件 vi ~/.bash_profile #GRADLE相关配置 GRADLE_HOME/Users/zxj/Documents/devSoft/grad…...

JUC并发编程面试题(自用)

线程池 1 线程池的作用&#xff1a;提高线程的利用率&#xff0c;线程复用&#xff0c;频繁的创建和销毁线程非常浪费资源 线程池的七大参数&#xff1a; corePoolSize&#xff08;核心线程数&#xff09;&#xff1a;线程池中始终保持的活动线程数&#xff0c;即使它们处于空…...

Redis分布式会话

当探讨Redis分布式会话管理时&#xff0c;以下是更加详细的知识点&#xff1a; 1. 会话管理的挑战&#xff1a; 在分布式应用程序中&#xff0c;每个用户请求可能由不同的服务器处理。这导致了会话数据的分散性&#xff0c;需要一种方法来维护一致性的用户会话状态。 2. Redi…...

程序员大厂之鹅厂探秘

...

【Java 进阶篇】深入理解 JavaScript DOM Node 对象

在前端开发中&#xff0c;与HTML文档进行交互是一项基本任务。文档对象模型&#xff08;Document Object Model&#xff0c;简称DOM&#xff09;为开发者提供了一种以编程方式访问和操作HTML文档的方式。DOM的核心是节点&#xff08;Node&#xff09;对象&#xff0c;它代表了文…...

测试用例基础

测试用例的基本要素 测试环境, 操作步骤, 测试数据, 预期结果 测试用例的设计方法 基于需求的设计方法 需求文档 -> 梳理需求(掌握需求) -> 针对文档设计测试用例 只是针对需求进行大概的测试 具体的设计方法 等价类 等价类: 依据需求将输入&#xff08;特殊情况…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...