最小生成树、最短路径树

article/2025/9/16 11:13:28

一、最小生成树与最短路径树的区别

最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。

应用如网络部线,把所有的电脑(服务器?)都连起来用的网线(光纤?)最少,即用最小的代价让全村人都能上网

最短路径是从一点出发,到达目的地的路径最小。

应用如导航,两个地方怎么走距离最短。可以存在到不了的情况。

二、最小生成树

在图论中,无向图 G 的生成树(英语:Spanning Tree)是具有 G 的全部顶点,但边数最少的连通子图。[1]

一个图的生成树可能有多个。

带权图的生成树中,总权重最小的称为最小生成树。

它在实际中有什么应用呢?比如说有N个城市需要建立互联的通信网路,如何使得需要铺设的通信电缆的总长度最小呢?这就需要用到最小生成树的思想了。

求取最小生成树的算法:

2.1 Prim算法原理:

1)以某一个点开始,寻找当前该点可以访问的所有的边;
2)在已经寻找的边中发现最小边,这个边必须有一个点还没有访问过,将还没有访问的点加入我们的集合,记录添加的边;
3)寻找当前集合可以访问的所有边,重复2的过程,直到没有新的点可以加入;
4)此时由所有边构成的树即为最小生成树。

参考链接:https://wiki.jikexueyuan.com/project/step-by-step-learning-algorithm/prim-algorithm1.html

2.2 Kruskal算法原理:

现在我们假设一个图有m个节点,n条边。首先,我们需要把m个节点看成m个独立的生成树,并且把n条边按照从小到大的数据进行排列。在n条边中,我们依次取出其中的每一条边,如果发现边的两个节点分别位于两棵树上,那么把两棵树合并成为一颗树;如果树的两个节点位于同一棵树上,那么忽略这条边,继续运行。等到所有的边都遍历结束之后,如果所有的生成树可以合并成一条生成树,那么它就是我们需要寻找的最小生成树,反之则没有最小生成树。

参考链接:https://wiki.jikexueyuan.com/project/step-by-step-learning-algorithm/kruskal-algorithm.html

2.3 两算法对比

总的来说,Prim算法是以点为对象,挑选与点相连的最短边来构成最小生成树。而Kruskal算法是以边为对象,不断地加入新的不构成环路的最短边来构成最小生成树。
在这里插入图片描述

三、最短路径树

最短路径就是从一个指定的顶点出发,计算从该顶点出发到其他所有顶点的最短路径。
最短路径树SPT(Short Path Tree)是网络的源点到所有结点的最短路径构成的树。
常见的最短路径算法有三种:dijkstra,floyd,Bellman-Ford和SPFA。

Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
Floyd:每对节点之间的最短路径,时间复杂度O(V^3)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).

如果说边权中有负数怎么定义呢?如果有负边,就要分两种情况了。
第一种情况:如果从某点出发,可以到达一个权值和为负数的环,那么这个点到其它点的最短距离就是负无穷了,很明显,如果有负环,且从某点可以到达这个负环,那么我可以无限得走这个负环,每走一次,距离就小一些,这种情况下,我们可以定义这个点到达其它点的最短距离为负无穷。
第二种情况:如果说不存在一个这样的负环,那么就和没有负权边一样了,但是还不是完全一样,接下来我们介绍的四种算法中,有的是可以处理负权边不能处理负环的,有的是可以处理负环的,有的是既不能处理负权边也不能处理负环的。

3.1 Dijkstra

  • 不能适用于负权图
  • 只适用于单源最短路问题
    如果权重是负数,那就直接pass这个算法,在这里原因不表。

单源最短路径就是指只有一个出发点,到其它点的最短路径问题。

在这里插入图片描述
在这里插入图片描述
以上图G4为例,来对迪杰斯特拉进行算法演示(以第4个顶点D为起点)。以下B节点中23应为13。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法流程:
  • S1. 设置dis数组,dis[i]表示起点start到i的距离。
  • S2. 从点集V中弹出一个dis值最小且未以该点为起点进行松弛操作的点。
  • S3. 从该点松弛与其领接的各个点更新dis数组,返回S2,循环进行。
  • 通过优先队列的操作可以优化S2,之后详细说明。

举个例子。这个例子也是洛谷的单源最短路径的模板题,请求出1到各点的最短路?
在这里插入图片描述
很显然你用肉眼看,1到本身是0,1到2、3、4的最短路分别为2,4,3。那dijkstra的操作流程是什么呢?

首先我们先开一个dis数组,让数组的值足够大,dis[i]=0x7fffffff,从1开始出发,令dis[1]=0,发现与1相连的有三个点234,那我们一个个进行松弛操作,比较if (dis[1]+w[i]<dis[i]),w表示各边的权重,如果小于,那就让其覆盖本身的dis值,即dis[i]=dis[1]+w[i],这一波更新完后,234的值分别为2,5,4。
然后,我们需要让234全部入队,并选取dis值最小的数即2继续进行松弛操作,发现连接的是3和4,继续更新,这波结束,234的值分别为2,4,3。
接着,是上一轮dis值次小的点4,进行操作,但是4没有出的边,所以不进行操作。
最后就是剩下的一个3了,3和4还有一条权边,但是4最小的dis值依旧是3。

下面我们发现算法到这就截止了,为什么呢,因为S2的一句话,未进行松弛的点,早在第一轮234就已经全部进入过队列并且已经弹出过了,所以之后他们也不会再进入队列,我们可以设置一个bool类型的vis[i]数组代表第i个点是否被访问过了,如果访问过了就结束此循环,或者直接不push进入队列。

这就是整个dijkstra的算法。

总结:

Dijkstra算法是一种优秀的经典的最短路算法,在优先队列的优化下,它的时间复杂度也是可接受的,它在计算机网络等应用中广泛存在,然而它也有它的局限性,它的局限性甚至比Floyd算法都要大,Floyd算法允许有负权边,不允许有负权环,Dijkstra算法连负权边都不允许,因为Dijkstra的正确性基于以下假设:当前未找到最短路的点中,距离源点最短的那个点,无法继续被松弛,这时候他肯定是已经松弛到最短状态了。但是这个假设在有负权边的时候就不成立了,举一个经典例子:一个具有三个点三条边的图,0->1距离为2,0->2距离为3,2->1距离为-2,根据Dijkstra算法,我们第一次肯定会将节点1加入到已经松弛好的点集中,但是这时候是不对的,2并不是0->1最短的路径,而通过点2松弛过的路径:0->2->1才是最短的路径,因为负权边的存在,我们认为的已经被松弛好的点,在未来还有被松弛的可能。这个就是Dijkstra的局限性,然而这并不影响它的伟大,因为在实际应用中,没有负权的图是更为常见的。但是我们仍然需要可以处理负权,甚至负环的算法,这时候Bellman-Ford算法和SPFA算法就派上用场了。

3.2 Floyd

弗洛伊德算法是一种基于动态规划的多源最短路算法。是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。

算法流程:

通过Floyd计算图G=(V,E)中各个顶点的最短路径时,需要引入两个矩阵,矩阵S中的元素a[i][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素b[i][j],表示顶点i到顶点j经过了b[i][j]记录的值所表示的顶点。
算法描述
a. 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
b. 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

假设图G中顶点个数为N,则需要对矩阵D和矩阵P进行N次更新。初始时,矩阵D中顶点a[i][j]的距离为顶点i到顶点j的权值;如果i和j不相邻,则a[i][j]=∞,矩阵P的值为顶点b[i][j]的j的值。 接下来开始,对矩阵D进行N次更新。第1次更新时,如果”a[i][j]的距离” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i与j之间经过第1个顶点的距离”),则更新a[i][j]为”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新时,如果”a[i][j]的距离” > “a[i][k-1]+a[k-1][j]”,则更新a[i][j]为”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之后,操作完成!
举个例子
在这里插入图片描述
第一步,我们先初始化两个矩阵,得到下图两个矩阵:
在这里插入图片描述
在这里插入图片描述
第二步,以v1为中介,更新两个矩阵:
发现,a[1][0]+a[0][6] < a[1][6] 和a[6][0]+a[0][1] < a[6][1],所以我们只需要矩阵D和矩阵P,结果如下:
在这里插入图片描述
在这里插入图片描述
通过矩阵P,我发现v2–v7的最短路径是:v2–v1–v7

第三步:以v2作为中介,来更新我们的两个矩阵,使用同样的原理,扫描整个矩阵,得到如下图的结果:
在这里插入图片描述
在这里插入图片描述
OK,到这里我们也就应该明白Floyd算法是如何工作的了,他每次都会选择一个中介点,然后,遍历整个矩阵,查找需要更新的值,下面还剩下五步,就不继续演示下去了,理解了方法,我们就可以写代码了。

总结

Floyd算法只能在不存在负权环的情况下使用,因为其并不能判断负权环,上面也说过,如果有负权环,那么最短路将无意义,因为我们可以不断走负权环,这样最短路径值便成为了负无穷。
但是其可以处理带负权边但是无负权环的情况。
以上就是求多源最短路的Floyd算法,基于动态规划,十分优雅。
但是其复杂度确实是不敢恭维,因为要维护一个路径矩阵,因此空间复杂度达到了O(n^ 2 ) ,时间复杂度达到了O(n^ 3),只有在数据规模很小的时候,才适合使用这个算法。

3.3 Bellman-Ford

Bellman-Ford是最常规下的单源最短路问题,对边的情况没有要求,不仅可以处理负权边,还能处理负环。可谓是来者不拒。

算法描述:

对于一个图G(v,e)(v代表点集,e代表边集),执行|v|-1次边集的松弛操作,所谓松弛操作,就是对于每个边e1(v,w),将源点到w的距离更新为:原来源点到w的距离 和 源点到v的距离加上v到w的距离 中较小的那个。v-1轮松弛操作之后,判断是否有源点能到达的负环,判断的方法就是,再执行一次边集的松弛操作,如果这一轮松弛操作,有松弛成功的边,那么就说明图中有负环。算法复杂度为O(ne),e为图的边数

优化(SPFA)

Bellman-Ford算法虽然可以处理负环,但是时间复杂度为O(ne),在图为稠密图的时候,是不可接受的。复杂度太高。我们分析一下能不能进行优化,Bellman-Ford算法的正确性保证依赖于路径松弛性质,我们只要能够保证最短路径中的边的松弛顺序即可,Bellman-Ford算法属于一种暴力的算法,即,每次将所有的边都松弛一遍,这样肯定能保证顺序,但是仔细分析不难发现,源点s到达其他的点的最短路径中的第一条边,必定是源点s与s的邻接点相连的边,因此,第一次松弛,我们只需要将这些边松弛一下即可。第二条边必定是第一次松弛的时候的邻接点与这些邻接点的邻接点相连的边(这句话是SPFA算法的关键,请读者自行体会),因此我们可以这样进行优化:设置一个队列,初始的时候将源点s放入,然后s出队,松弛s与其邻接点相连的边,将松弛成功的点放入队列中,然后再次取出队列中的点,松弛该点与该点的邻接点相连的边,如果松弛成功,看这个邻接点是否在队列中,没有则进入,有则不管,这里要说明一下,如果发现某点u的邻接点v已经在队列中,那么将点v再次放到队列中是没有意义的。因为即时你不放入队列中,点v的邻接点相连的边也会被松弛,只有松弛成功的边相连的邻接点,且这个点没有在队列中,这时候稍后对其进行松弛才有意义。因为该点已经更新,需要重新松弛。

总结:

Bellman-Ford算法和SPFA算法,都是基于松弛操作,以及路径松弛性质的,不同之处在于,前者是一种很暴力的算法,为了保证顺序,每次把所有的边都松弛一遍,复杂度很高,而后者SPFA,则是在Bellman-Ford算法的基础上进行了剪枝优化,只松弛那些可能是下一条路径的边,但是关于SPFA算法的复杂度,现在还没有个定论,不过达成的共识是:算法的时间复杂度为O(me),其中m为入队的平均次数,其发明者,西安交大的段凡丁大神的论文中,m是一个常数(这个结论是错的),我们“姑且”认为该算法的时间复杂度为O(e)【滑稽】,的确该算法的效率是很高的,博主在oi中经常使用这个算法,除非题目数据特意卡SPFA。或者的确是稠密图,一般情况下不会超时。关于其判断负环,我们可以认为,在某个节点入队次数达到n的时候,就可以说明遇到了负环。SPFA有两个优化策略:SLF:Small Label First 策略,设要加入队列的节点是j,队首元素为i,若d[j]<dist[i],则将j插入队首,否则插入队尾; LLL:Large Label Last 策略,设队首元素为i,队列中所有d值的平均值为x,若d[i]>x则将i插入到队尾,查找下一元素,直到找到某一i使d[i]<=x,则将i出队进行松弛操作。其实,实际应用中,SPFA的时间复杂度是很不稳定的,因此我们能用Dijkstra+优先队列,就用Dijkstra+优先队列为好。

https://zhuanlan.zhihu.com/p/34922624
https://zhuanlan.zhihu.com/p/150434472
https://zhuanlan.zhihu.com/p/33162490
https://zhuanlan.zhihu.com/p/40338107


http://chatgpt.dhexx.cn/article/MnHuocQP.shtml

相关文章

【图解算法】最小生成树

今天我们介绍图论中的另一部分&#xff0c;最小生成树。 对图的最短路感兴趣的同学可以看&#xff1a; 【图解算法】一次解决最短路径问题 目录 1. 最小生成树简介2. Prim算法2.1 模板题2.2 思路模板2.3 代码实现2.3 prim 算法的优化 3. Kruskal算法3.1 模板题3.2 思路模板3.3…

数据结构—最小生成树

目录 一、生成树 二、最小生成树&#xff08;代价最小树&#xff09; 三、求最小生成树 1、Prim算法&#xff08;普里姆&#xff09; 2.Kruskal 算法&#xff08;克鲁斯卡尔&#xff09; 3.Prim算法和Kruskal算法对比 一、生成树 连通图的生成树是包含图中全部顶点的一个…

最小生成树详解

目录 最小生成树简介 什么是树 什么是生成树 什么是最小生成树 最小生成树的做法 Kruscal&#xff08;克鲁斯卡尔&#xff09;算法 思路 代码 其他算法 最小生成树简介 什么是树 树&#xff08;tree&#xff09;是一种特殊的图&#xff0c;一个图要成为树要满足三个条…

最小生成树的实现(C语言)

今天做洛谷的时候刷到好多图论的题&#xff0c;发现自己在这一方面算法的掌握还是有待提高啊。在这就先介绍最小生成树的算法吧。 最小生成树 最小生成树&#xff08;minimum spanning tree&#xff09;是由n个顶点&#xff0c;n-1条边&#xff0c;将一个连通图连接起来&…

详解: 最小生成树

详解: 最小生成树算法 最小生成树&#xff08;Minimum Spanning Tree, MST&#xff09;是在一个给定的无向图G(V, E)中求一棵树&#xff0c;使得这棵树有图G的所有顶点&#xff0c;且所有边都来自图G中的边&#xff0c;并且满足整棵树的边权之和最小. 最下生成树满足如下性质…

最小生成树

目录 一、最小生成树定义&#xff1a; 二、普里姆算法&#xff08;Prim&#xff09; 三、Kruskal算法&#xff08;克鲁斯卡尔&#xff09; 小结&#xff1a; 下一篇&#xff1a;最短路径算法 问题&#xff1a; 一、最小生成树定义&#xff1a; 对于一个带权连通无向图G(V,E)&am…

最小生成树——Prim算法(详细图解)

目录 最小生成树的概念 经典题目 prim算法简介 prim算法解析 &#xff08;详细图解&#xff09; 代码实现 代码实战 最小生成树的概念 在一给定的无向图G (V, E) 中&#xff0c;(u, v) 代表连接顶点 u 与顶点 v 的边&#xff0c;而 w(u, v) 代表此的边权重&#xff0c;若…

最小生成树入门(图解)

1.什么是最小生成树 来看百度百科的定义 一个有 n 个结点的连通图的生成树是原图的极小连通子图&#xff0c;且包含原图中的所有 n个结点&#xff0c;并且有保持图连通的最少的边。最小生成树可以用kruskal&#xff08;克鲁斯卡尔&#xff09;算法或prim&#xff08;普里姆&am…

最小生成树(Prim、Kruskal)算法,秒懂!

前言 在数据结构与算法的图论中&#xff0c;(生成)最小生成树算法是一种常用并且和生活贴切比较近的一种算法。但是可能很多人对概念不是很清楚&#xff0c;什么是最小生成树? 一个有 n 个结点的连通图的生成树是原图的极小连通子图&#xff0c;且包含原图中的所有 n 个结点&…

最小生成树详解(模板 + 例题)

作为一个伪ACMer,先来首广为人知的打油诗: 模拟只会猜题意&#xff0c;贪心只能过样例&#xff0c;数学上来先打表&#xff0c;规律一般是DP&#xff0c;组合数学碰运气&#xff0c;计算几何瞎暴力&#xff0c;图论一顿套模板&#xff0c;数论只会GCD,递归递推伤不起&#xff0…

Linux开启/关闭防火墙

一、查看防火墙状态&#xff1a; systemctl status firewalldinactive表示防火墙为关闭状态。 二、开启防火墙&#xff1a; systemctl start firewalld启动后无任何提示&#xff0c;再次查看防火墙状态&#xff0c;可以看到变成active&#xff0c;成功启动。 三、关闭防火墙…

linux服务器查看防火墙是否关闭,linux查看防火墙是否关闭了的方法

linux查看防火墙是否关闭了的方法 发布时间&#xff1a;2020-04-02 10:49:28 来源&#xff1a;亿速云 阅读&#xff1a;62 作者&#xff1a;小新 今天小编给大家分享的是linux查看防火墙是否关闭了的方法&#xff0c;很多人都不太了解&#xff0c;今天小编为了让大家更加了解li…

linux操作系统关闭防火墙,linux操作系统关闭防火墙的方法

防火墙是一项协助确保信息安全的设备,会依照特定的规则,允许或是限制传输的数据通过。简单的来说防火墙的作用就是保护你的网络免受非法用户的侵入,虽然防火墙是为了你网络安全而存在,但是同时也限制了你上网操作,有很多人会选择关闭防火墙,windows操作系统的防火墙好关闭…

linux为什么要关闭防火墙,Linux怎么样关闭防火墙

Linux系统下面自带了防火墙iptables&#xff0c;iptables可以设置很多安全规则。但是如果配置错误很容易导致各种网络问题&#xff0c;那么如果要关闭禁用防火墙怎么操作呢?下面就让学习啦小编给大家说说Linux怎么关闭防火墙吧。 Linux关闭防火墙的方法 如果启动的iptables防火…

linux防火墙的开启、关闭、永久关闭

防火墙是什么&#xff1f;我们为什么需要关闭防火墙&#xff1f; 防火墙就是一个保护我们系统的软件服务&#xff0c;默认开启&#xff0c;但是我们在实际开发中&#xff0c;如果需要使用宿主机来连接虚拟机里面的redis, mysql&#xff0c;nginx&#xff0c;tomcat等服务&#…

linux防火墙能关吗,linux防火墙怎么样关闭

有没有什么方法可以关闭linux防火墙呢?方法是有的&#xff0c;小编来告诉你!下面由学习啦小编给你做出详细的linux防火墙关闭方法介绍!希望对你有帮助! linux防火墙关闭方法一&#xff1a; 1) 重启后生效 开启&#xff1a; chkconfig iptables on 关闭&#xff1a; chkconfig …

Linux防火墙关闭(重启)操作(centos)

1:查看防火墙状态 systemctl statu firewalld 此状态表示防火墙禁用状态 此状态表示防火墙处于开启状态 2:暂时关闭防火墙 systemctl stop firewalld 3:启用防火墙&#xff08;关闭状态使用&#xff09; systemctl start firewalld 4:重启防火墙&#xff08;输入命令后防火墙先…

Linux 开启关闭防火墙操作

1. linux中防火墙相关操作 1.1 查看防火墙状态 systemctl status firewalld如下所示表示防火墙是运行状态&#xff0c; 8080&#xff0c; 3306&#xff0c; 6379表示我开放了这个端口给外部访问 或者 firewall-cmd --state1.2 暂时关闭防火墙(重启服务器防火墙会重新开启) …

【Linux】如何关闭Linux防火墙

在访问linux时&#xff0c;如果linux防火墙是开启状态&#xff0c;则无法访问其提供的服务&#xff0c;为此&#xff0c;需要将Linux的防火墙关闭&#xff0c;命令如下[1]&#xff1a; 查看防火墙状态 firewall -cmd --state关闭防火墙 systemctl stop firewalld.serv…

linux防火墙关闭启用增减端口号命令

查看防火墙信息 firewall-cmd --list-all 查看防火墙状态 firewall-cmd --state systemctl status firewalld 添加端口号到防火墙中&#xff0c;永久有效是写入配置文件&#xff0c;可以不加--permanent但重启服务器后会失效 firewall-cmd --permanent --add-port18082/tcp …