最小生成树、最短路径树

最小生成树

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

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

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

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

求取最小生成树的算法:

Prim算法原理:

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

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

Kruskal算法原理:

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

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

两算法对比

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

最短路径树

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

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

扫描二维码关注公众号,回复: 11755509 查看本文章

Dijkstra

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

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

算法流程:
  • 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算法就派上用场了。

Floyd

弗洛伊德算法是一种基于动态规划的多源最短路算法。

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

猜你喜欢

转载自blog.csdn.net/Anne033/article/details/108703039