最短路径:BellmanFord / SPFA / Dijkstra
来源:互联网 发布:淘宝花呗的客服电话 编辑:程序博客网 时间:2024/06/05 17:14
Dijkstra算法只适用于边的权值非负的情况,一般复杂度为O(n*n)。
Bellman-Ford是最简单最粗暴最慢的,复杂度为O(nm),比O(n*n)更复杂(因为边m比点n多),但是适用范围比Dijistra更广,可以应用于边权值为负的情况,但是不能出现负环,要不然就永远收敛不了了。
//BellmanFord只需要存储边,直接将边放到足够大的数组里#include<stdio.h>#include<string.h>#define N 100010#define INF 0x3ffffffftypedef struct Edge{ int begin, end, value;}ELink;ELink edge[N]; //存储边int dist[N], n, m;void BellmanFord(int start){ int i, j; dist[start] = 0; //源点到自己的距离为0 for(i=1; i<=n; i++) //外层是扫n遍,n个点 { for(j=1; j<=m+m; j++) //内层是扫2m遍,因为存储了2m条边 { if(dist[ edge[j].end ] > dist[ edge[j].begin ] + edge[j].value) //松弛 { dist[ edge[j].end ] = dist[ edge[j].begin ] + edge[j].value; } } }}int main(){ int i, a, b, c; scanf("%d%d", &n, &m); for(i=1; i<=n; i++) //存储 dist[i] = INF; for(i=1; i<=m; i++) { scanf("%d%d%d", &a, &b, &c); edge[i].begin = a; //存储边 edge[i].end = b; edge[i].value = c; edge[m+i].begin = b; //反向存储一遍,否则相当于存的是有向边!!! edge[m+i].end = a; edge[m+i].value = c; } BellmanFord(1); //不妨将点1作为源点 for(i=1; i<=n; i++) printf("%d\n", dist[i]); //输出源点到第n个点的最短距离 return 0;}
当然,这种无脑松弛会造成很多浪费,比如当前已经松弛完毕了,但是还没有达到n*m次,程序依旧会继续在n*m的循环里走下去,而此后并不会再发生松弛了。所以我们可以设置一个flag,一旦发现没有松弛,直接break,会大大提升效率。
void BellmanFord(int start){ int i, j; dist[start] = 0; //源点到自己的距离为0 for(i=1; i<=n; i++) //外层是扫n遍,n个点 { int change = 0; for(j=1; j<=m+m; j++) //内层是扫2m遍,因为存储了2m条边 { if(dist[ edge[j].end ] > dist[ edge[j].begin ] + edge[j].value) //松弛 { dist[ edge[j].end ] = dist[ edge[j].begin ] + edge[j].value; change = 1; } } if(!change) { break; } }}
最后再说明强调一点——如果是无向图,存储边的话一定要正反存两遍,否则会导致无向图存成了有向图,这样就会导致最后的求解错误。
而且,如果想更简单一点的话,直接存成邻接矩阵也是可以的,会简单很多,不过浪费了不少存储空间。
SPFA是最好使相对来说写起来也不复杂,一般正常的情况下复杂度为O(km),也可以理解为O(m),性价比最好!力荐!!!想有更多更深的理解,可以参考如下文章http://blog.sina.com.cn/s/blog_a46817ff01015g9h.html。
不必一直不停地松弛下去,如果当前节点A被松弛了(也就是说当前节点到Source Point的距离变小了),那么A的邻接点B(有可能B通过A得到到达Source Point的最短路径)就需要重新松弛一下:如果B经由A到达Source Point为最短路径,那么B一定会被松弛,否则B不会被松弛。
所以,我们需要构建一个队列,当A被松弛之后,所有A的邻接点都要被重新检测一下,看看需不需要被松弛。于是所有A的邻接点入队。之后再从队列中取出一个节点,进行相同的操作,直到队空,证明所有能松弛的都松弛完了,结束。这样就避免了Bellman-Ford盲目不停地松弛n*m次,使得算法效率大大提高。
//由于SPFA对BellmanFord进行了优化,只更新一个点周围的点,//所以需要知道和一个点相连的点都有哪些,因此采用邻接表的形式#include<stdio.h>#include<stdlib.h>#define V 200#define N 10010#define INF 0x3ffffffftypedef struct Edge //邻接表的链节点构造{ int to, value; struct Edge *link;}ELink;typedef struct Ver //邻接表的顶点节点构造{ int vertex; ELink *link; //链节点指针}VLink;VLink G[V]; //顶点节点型数组,最多V个int queue[N], inqueue[N], dist[N]; //队;判断是否在队里;各个点到源点的距离void SPFA(int start){ int top = 0, rear = -1; ELink *tmp; dist[start] = 0; //此三句对源点进行操作 queue[++rear] = start; inqueue[start] = 1; while(top <= rear) //直到队空(有点儿像广度搜索) { for(tmp = G[queue[top]].link; tmp != NULL; tmp = tmp->link) //遍历队中顶点节点的所有链节点 { if(dist[tmp->to] > dist[queue[top]] + tmp->value) //符合条件则松弛 { dist[tmp->to] = dist[queue[top]] + tmp->value; if(inqueue[tmp->to] != 1) //如果没有入队则入队,并标记 { queue[++rear] = tmp->to; inqueue[tmp->to] = 1; } } } inqueue[queue[top]] = 0; //出队,并更改标记 ++top; }}void append(int a, int b, int c){ ELink *p, *q; G[a].vertex = a; p = (ELink *)malloc(sizeof(ELink)); p->to = b; p->value = c; p->link = NULL; if(G[a].link == NULL) G[a].link = p; else { q = G[a].link; while(q->link) q = q->link; q->link = p; }}int main(){ int n, m, i, a, b, c; scanf("%d%d", &n, &m); for(i=1; i<=n; i++) //从1开始初始化,距离赋INF dist[i] = INF; for(i=0; i<m; i++) //构造邻接表 { scanf("%d%d%d", &a, &b, &c); append(a, b, c); //eg:构造1-->3的链节点 append(b, a, c); //eg:构造3-->1的链节点 } SPFA(3); //所有点到第一个点的最短距离 for(i=1; i<=n; i++) //输出所有的最短距离 printf("%d ", dist[i]); return 0;}
上面的算法因为用到了邻接表,所以在邻接表的构造上还是费了不少功夫的。当然不用邻接表也是可以的,使用邻接矩阵会使算法变得更简单易写,不过多浪费了存储空间:
#include <iostream>using namespace std;#define N 10000 // 边的最大个数#define V 200 // 顶点的最大个数#define INF 0x3fffffffint edge[N][N]; // 邻接矩阵,记录边int dist[V], queue[V], inqueue[V]; // 到源点的距离;队列;是否入队int pre[V]; // 记录每个点的前导点,用以输出路径int n, m; // 顶点个数;边的个数void init() // 初始化edge/dist/pre{ cin >> n >> m; for(int i = 1; i <=n; i++){ dist[i] = INF; pre[i] = i; // 每个点的终结点为自己 } for(int i = 1; i <= n; i++){ for(int j = 1; j <=n; j++){ edge[i][j] = INF; } } int row, col, value; while(m--){ cin >> row >> col >> value; edge[row][col] = edge[col][row] = value; }}void SPFA(int start){ int top = 0, rear = -1; dist[start] = 0; // 初始化source point queue[++rear] = start; inqueue[start] = 1; while(top <= rear){ int vertex = queue[top]; for(int i = 1; i <= n; i++){ int value = edge[vertex][i]; if(value < INF && dist[i] > dist[vertex] + value){ dist[i] = dist[vertex] + value; pre[i] = vertex; if(!inqueue[i]){ queue[++rear] = i; inqueue[i] = 1; } } } ++top; inqueue[vertex] = 0; }}void printPath(int v) // 按照pre逆序输出路径 { while(v != pre[v]){ cout << v << "<--"; v = pre[v]; } cout << v << endl;}int main(){ init(); cout << "Input a source point: "; int start; cin >> start; SPFA(start); for(int i = 1; i <=n; i++){ cout << "Vertex: " << i << endl << "Dist to Source Point: " << dist[i] << endl << "Path: "; printPath(i); } return 0;}
算法运行结果如下:
pArch➜ tmp ᐅ ./spfa7 91 2 101 3 22 5 13 4 23 6 114 5 44 6 65 7 76 7 3Input a source point: 1Vertex: 1Dist to Source Point: 0Path: 1Vertex: 2Dist to Source Point: 9Path: 2<--5<--4<--3<--1Vertex: 3Dist to Source Point: 2Path: 3<--1Vertex: 4Dist to Source Point: 4Path: 4<--3<--1Vertex: 5Dist to Source Point: 8Path: 5<--4<--3<--1Vertex: 6Dist to Source Point: 10Path: 6<--4<--3<--1Vertex: 7Dist to Source Point: 13Path: 7<--6<--4<--3<--1
最后说的是Dijkstra算法,如果不优化则复杂度为O(n*n),相对来说最难写,且复杂度还不如SPFA好,所以性价比不是最好,但是跟Prim求最小生成树极其相似,两个算法可以想通。详情请参考http://blog.csdn.net/puppylpg/article/details/41440459,这里就不再贴代码了。
而且,在这些点存储的过程中,点从1到n,所以我更倾向于从数组的[1]开始存储,感觉这样的话在很多方面写起来都比较容易。
0 0
- 最短路径:BellmanFord / SPFA / Dijkstra
- 单源点最短路径Dijkstra和Bellmanford
- 最短路径算法—Dijkstra算法和BellmanFord算法
- hdu3790 最短路径问题 (Dijkstra & SPFA)
- 单源最短路径Dijkstra、BellmanFord、SPFA【模板】
- hdoj-2544 最短路【最短路径--dijkstra&&spfa&&floyd】
- hdu2544 最短路径三种方法 FLoyd dijkstra spfa
- 最短路径问题的Dijkstra和SPFA算法总结
- 最短路径:Dijkstra,Bellman,SPFA,Floyd算法的实现
- [图论] 最短路径(Bellman-Ford , SPFA , Floyed , Dijkstra)
- 最短路径问题 Floyd SPFA Dijkstra 效率比较
- 最短路径算法 dijkstra + floyd + spfa 【记录 总结】
- [图论] 最短路径(Bellman-Ford , SPFA , Floyed , Dijkstra)
- HDU 2112 HDU Today 【最短路径 dijkstra & floyed & SPFA 】
- 最短路径 -- Floyd-Warshall & Dijkstra & spfa 学习和思考
- 最短路径算法Dijkstra && SPFA && Floyd 代码实现模板
- 最短路径算法总结(Floyd,bellmen-ford,dijkstra,Spfa)
- 最短路径算法总结(Floyd,bellmen-ford,dijkstra,Spfa)
- Activity的生命周期一
- The type java.lang.Object cannot…
- android生命周期
- delete void*形式的指针是非常危险的
- struts2中页面表示国际化的方法
- 最短路径:BellmanFord / SPFA / Dijkstra
- Spring 2.5笔记:applicatio…
- 关于解决struts2中xml没有提示的问…
- sj
- zoj3497Mistwald
- Oracle存储过程学习
- 如何在spring中读取properti…
- Toad for Oracle工具的使用
- [分享]Java 线程池的原理与实现