最短路算法总结

来源:互联网 发布:小号手模型淘宝旗舰店 编辑:程序博客网 时间:2024/06/05 20:41

最近做POJ图论关于最短路径方面的题目,感觉需要总结一下:

参考链接:最短路算法

(1)松弛的意思就是用可以替代的更优的路径得到的值去更新原来的值。

比如:AB + BC  < AC, 说明从A到C的最短距离存在ABC这条更有的路径,那么AC = AB + BC。

牢记并灵活使用这个所谓的三角形不等式,是解题的关键。

(2)最短路径的估计值就是对dist[][](多源最短)和dist[](单源最短)的初始化。

根据题目的不同初始化不同,一般的最短路的初始化(有各种变形的不在此列)是:所有其他点到源点的距离是正无穷,源点到自身的距离是0。

(3)负环的意思是说存在一个环路,在V-1次松弛/更新之后,本来应该已经得到AC最小值了,但是下一次松弛还是可以得到更小的AC值,则此环路称之为负环。若存在w为负值,则很有可能存在负环。

所以Dijkstra算法(单源最短路径)不适用存在负环的题目,因为这个最短距离总是可以无限小下去。

(4)Bellman_ford算法:

也是求解单源最短路径,必须有个源点进行出发,但是比Dijkstra多了一层,它以用来判断是否存在负环。它的思想就是进行V-1次松弛,然后再来最后一次松弛,边松弛边判断,如果还存在可以松弛的点,那么必定存在负环。

poj3259虫洞(问主人公可否通过某些路径在出发前的时间前回到原地)这道题就是经典的Bell_ford算法题。poj3259 Bellman_ford算法

poj1860套现(问主人公可否通过汇率转换实现兑换回比原来更多的钱)(正环)是稍微变形的Bell_ford算法题:poj1860 Bellman_ford算法

这两道题都是通过不断地走正环或者负环来无限减少时间(增加钱)然后再回到源点,必然可以实现目的。所以正负环的判断是关键。

(5)Floyd-warshell算法:

这是一个非常强大的算法,可以计算任意两点的最短路径,同时适用于解决w<0的。而且非常非常容易理解,松弛,Bellman_ford算法都是从这里引申出来的。

d[i][j] = min(d[i][j],d[i][k]+d[k][j])

这里和单源不同,是使用矩阵而非一维数组。它是Bell_ford的增强版,如果需要判断负环,可以最后判断d[i][i]是否为负的就可以。

对于Floyd算法和Bellman_ford算法应用的最大区别就是有没有源点。对于poj3259 poj1860主人公是必须从源点出发的,但是对于没有源点要求的题目,则需要用Floyd算法解决,如果有负环的话,就是floyd+Bellman判断。

poj2240套现(问主人公可否通过汇率转换实现兑换回比原来更多的钱,没有源点)这道题就是经典的Floyd算法题,也是具有Bellman_ford性质的题目,只需要多次松弛之后汇率的矩阵d的对角线的值存在>1的值即可:poj2240 Floyd算法+Bellman_ford思想

poj1125消息传播(问通过谁传播消息可以让消息传播到所有人的时间最短)则是一道经典的Floyd算法,没有Bellman_ford思想:poj1125 经典Floyd算法

poj2253青蛙跳(问从A到B的路径中要求青蛙单次跳的距离最小的这个距离):是一道稍微变形的Floyd算法,虽然有源点,但是因为题目的奇葩要求,不能使用单源最短路径去解决,需要求出每两个点之间的“最短路径”,也没有Bellma_ford思想:poj2253 变形Floyd算法

(6)以上是对前面做过的POJ题目的总结,暂时没有用到Dijkstra算法

但是作为图论最短路的经典算法还是继续讲讲:

Dijkstra是求解单源最短路的,所以维护的是一个以源点为目标的一维的数组,另外还需要一个mark数组作为标记点是否被访问过了。

找到最近的点,利用这个最近的点,利用这个点作为跳板,更新所有未访问过的点距离源点的距离。

这个操作迭代V次即可,最后的数组就是各个点距离源点的最短路径。下面代码示例来自:http://www.cnblogs.com/Yan-C/p/3916281.html

void dijkstra(){    bool vis[203];//相当于集合Q的功能, 标记该点是否访问过    int dis[203];//保存最短路径    int i, j, k;    for(i=0;i<n;i++)//初始化        dis[i] = G[s][i];//s—>各个点的距离    memset(vis,false,sizeof(vis));//初始化为假 表示未访问过    dis[s] = 0;//s->s 距离为0    vis[s] = true;//s点访问过了,标记为真    for(i=1;i<n;i++)//G.V-1次操作+上面对s的访问 = G.V次操作    {        k = -1;        for(j=0;j<n;j++)//从尚未访问过的点中选一个距离最小的点            if(!vis[j] && (k==-1||dis[k]>dis[j]))//未访问过 && 是距离最小的                k = j;        if(k == -1)//若图是不连通的则提前结束            break;//跳出循环        vis[k] = true;//将k点标记为访问过了        for(j=0;j<n;j++)//松弛操作            if(!vis[j] && dis[j]>dis[k]+G[k][j])//该点为访问过 && 可以进行松弛                dis[j] = dis[k]+G[k][j];//j点的距离  大于当前点的距离+w(k,j) 则松弛成功,进行更新    }    printf("%d\n",dis[t]==MAX?-1:dis[t]);//输出结果}

(7)SPFA算法是对Bellman_ford算法的优化,Bellman_ford算法的缺点就是多次松弛,点很多的时候就慢了,虽然有个flag来提取判断。

SPFA算法是西南交通大学段凡丁于1994年发表的。
SPFA算法 :设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。SPFA 是这样判断负环的: 如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

关于SPFA,如果使用正常的Bellman_ford超时的话,或者判断题目是一个稀疏图,那么使用SPFA就是解题的关键了。

不过我听说由于证明有瑕疵,国际上不承认这个算法。。。关于SPFA算法现在理解大概意思,应用中还可以用到前向星+STL双向队列,真正用到时候再重新讨论。


0 0