最短路(基础未优化)

作者: opbnbjs | 来源:发表于2018-06-11 21:44 被阅读40次

    最短路(基础未优化)

    写在前面

    写最短路我犹豫了很久,因为最短路它涵盖的内容很多(四个基础算法),而且在基础算法上还有许多不同的优化,甚至存边都有几种方式,就显得特别复杂

    基本概念

    最短路就是求在一个图中一个点到指定点的最短路程(其实光看名字就猜的出来)。

    几个概念:

    
    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狄杰斯特拉算法

    这是一个很经典的单源最短路的算法。
    这个算法的描述我觉得在一本叫算法图解的书中描述的比较详细,在这里就附上书的照片,觉得讲得很通俗易懂,应该理解没有什么问题。(多图预警)


    1.jpg
    2.jpg
    3.jpg
    4.jpg
    5.jpg

    总之就是重复那四步,就可以找出最优解。
    算法效率比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,表示途中存在从源点可达的权为负的回路。
    

    松弛操作之前讲过,再看一遍复习一下


    a.png

    松弛计算之前,点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值(8)小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。
    当然,如果出现以下情况


    b.png

    则不会修改点B的值,因为3+4>6。

    spfa留着下次再讲(和优化一起,因为它比较难。。)。(留坑待补)
    会了Bellman-Ford算法和狄杰斯特拉算法就已经可以做一些最段路的题了,可以练习一下。

    重点理解:松弛操作(这玩意真的很重要,基本上最段路的精髓就是它了)

    tks!
    (参考文献:https://www.cnblogs.com/tanky_woo/archive/2011/01/17/1937728.html , 算法图解)

    相关文章

      网友评论

      • opbnbjs:可以去看看我后面写的优化

      本文标题:最短路(基础未优化)

      本文链接:https://www.haomeiwen.com/subject/xtlkeftx.html