最短路(基础未优化)
写在前面
写最短路我犹豫了很久,因为最短路它涵盖的内容很多(四个基础算法),而且在基础算法上还有许多不同的优化,甚至存边都有几种方式,就显得特别复杂
基本概念
最短路就是求在一个图中一个点到指定点的最短路程(其实光看名字就猜的出来)。
几个概念:
1.出发的点叫源点
2.一个点到另一个点的路程(也可以理解为连接这两个点的线段的长度)叫权
3.图分两种:有向图和无向图。有向图就像单行道,只能去不能回。而无向图就像双行道,可以去也可以回。
还有一些例如松弛操作这些的概念会在下面进行解释
基础算法
存边
对于一个图,我们需要把边存储下来。而存储有两种方式。
邻接矩阵
用一个二维数组来存储。假如用数组k来存储边,则
k[i][j]
表示有一条从i到j的边,权值为
k[i][j]
邻接表
思路是用3个数组,(这里假设为a,b,c)(可能有点难理解)
当
a[m]=I
b[m]=j
c[m]=n(其中m为常数)
就是表示从i到j有一条边,权值为n。
但是假如要排序的话,开三个数组很麻烦,所以我建议用结构体。而结构体的排序方法我在讲最小生成树之kruskarl的时候讲过,这里就不再赘述。
前向星
前向星比邻接矩阵省空间。
链式前向星
这个是一种链式的存边方法。假如要细讲的话,可能又要讲一篇文章了,下文也不会使用这个。
感兴趣的话可以去看这篇博客
https://blog.csdn.net/acdreamers/article/details/16902023/
无向边
以上讲的都是存有向边的方式。存无向边其实区别不是很大。举个例子相信一下就懂了。假如你要存一条从i到j的无向边,你可以这么写
k[i][j]=n;
k[j][i]=n;
就是把终点和起点换一下位置,把终点当起点,把起点当终点。
Floyd
这个算法有一大特点,那就是写起来极其简单(核心代码只有5行),但是运行效率极低(o(n^3)),就让它显得很毒瘤,而且数据基本都会卡这种算法。
假如用二维数组
k[i][j]
来存储从i到j的最段路。
初始化:先把k数组全部赋一个很大很大的值(方便后面比较)
floyd算法如下
for(int k=1;k<=n;k++)//k枚举途经的节点
for(int i=1;i<=n;i++)//i枚举起点
for(int j=1;j<=n;j++)//j枚举终点
if(d[i][j]!=inf&&d[i][k]!=inf)//假如i到k没有边,那么假设不成立,同理,假如i到j没有边,这个假设也不成立
d[i][j]=min(d[i][k]+d[k][j],d[i][j]);//拿现有的从i到j的最段路和i途经k再到j的总路程做比较。(这就是为什么要把d[i][j]的初始值赋为一个很大的数)
可以看一下上面的注释。。
floyd就是靠三重循环来枚举起点、途经的点和终点。然后做松弛操作。(松弛操作就是比大小那一步,有点像三角形的三边关系)拿现有的从i到j的最段路和i途经k再到j的总路程做比较。也算是一个dp。
这个算法没啥优化(也没啥可优化的)。可以求单源最段路(有向图中的最段路),也可以求无向图的最段路。不能对付负权
这里顺便引入两个新概念:负权&环
负权:一条权值为负的路(于现实生活中不存在)。
环:有些时候在有向图中会同时有从i到j和从j到i的两条路。他们就组成了一个环。
负权环就是一个很恐怖的东西了,你可以靠负权环在环里面不停的刷,这样你的最短路。。。就成了无限的更短路。。。
dijkstra狄杰斯特拉算法
这是一个很经典的单源最短路的算法。
这个算法的描述我觉得在一本叫算法图解的书中描述的比较详细,在这里就附上书的照片,觉得讲得很通俗易懂,应该理解没有什么问题。(多图预警)
总之就是重复那四步,就可以找出最优解。
算法效率比floyd不知道高到哪里去了。。
不能对付负权
bellman-ford贝尔曼福德算法
这是一个可以对付负权的算法
算法:
给你一个由v个点,e条边的图,和原点s
数组Distant[i]记录从源点s到顶点i的路径长度,初始化
数组Distant[n]为, Distant[s]为0;
以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。
可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).
Bellman-Ford算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。
松弛操作之前讲过,再看一遍复习一下
松弛计算之前,点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值(8)小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。
当然,如果出现以下情况
则不会修改点B的值,因为3+4>6。
spfa留着下次再讲(和优化一起,因为它比较难。。)。(留坑待补)
会了Bellman-Ford算法和狄杰斯特拉算法就已经可以做一些最段路的题了,可以练习一下。
重点理解:松弛操作(这玩意真的很重要,基本上最段路的精髓就是它了)
tks!
(参考文献:https://www.cnblogs.com/tanky_woo/archive/2011/01/17/1937728.html , 算法图解)