最短路径
来源:互联网 发布:关系数据库语言 编辑:程序博客网 时间:2024/05/16 13:07
很久没有练习最短路径的题目了,本以为掌握的不错,却忽略了每种算法都有它的拓展,它的变式,并非所有的题目都是让你求个最短路径那么简单(即使在一般的比赛中spfa也仅仅是用来求最短路径)。刷了几天的水题,也是时候做些总结了。
众所周知,最短路径的算法一共有四种,分别是(ASAP)Floyed,(SSSP)Dijkstra,(SSSP)BellmanFord,(SSSP)SPFA。个人认为SPFA仅仅是BellmanFord的强化版,应该归属于同一种算法。因此,以下只总结前三种算法。
Floyed算法:
Floyed算法较为简单,本质就是一个DP来求多源最短路,由于代码简单,常被用于距离的预处理中。不多说,先上标准代码
#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int MAXN = 101;int n,m,s,t;int map[MAXN][MAXN];int main(){ memset(map,127,sizeof(map));//距离赋为无穷大 scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { int from,to,dist; scanf("%d%d%d",&from,&to,&dist); map[from][to] = dist; } scanf("%d%d",&s,&t); //Floyed for (int k=1;k<=n;k++) //先枚举中间节点 for (int i=1;i<=n;i++) if (i != k) for (int j=1;j<=n;j++) if (j != k && j != i) map[i][j] = min(map[i][j],map[i][k]+map[k][j]); printf("%d\n",map[s][t]); return 0;}
那么,Floyed算法还有没有其它的用途呢?当然有,首先的一个作用便是判断负权回路,实现很简单,只需要在最后判断map[i][i](0<i<=n)是否小于0,若是,则存在负权回路。但这种判回路的方法时间复杂度为O(n^3),效率远远比不上BellmanFord,因此不经常用。
除此之外,Floyed最小环算法被广泛地使用,如果你要解决这样一个问题:在一个有向图中,可从任意一顶点出发,求最小回路是多少?
虽然本题可以使用Dijkstra来完成,但没有Floyed方便快捷。Floyed最小环算法在网上有介绍,这里不作详解,仅贴上核心代码
//Floyed最小环 //map[i][j]表示原距离 for (int k=1;k<=n;k++) //先枚举中间节点 for (int i=1;i<=n;i++) if (i != k) for (int j=1;j<=n;j++) if (j != k && j != i) ans = min(ans,f[i][j]+map[i][k]+map[k][j]);
Dijkstra算法:
Dijkstra是以贪心为基础的算法,它的原理很好理解,每次都找与起点距离最小的点,再不断更新,直到所有点都被更新过为止。强烈建议使用邻接表来进行存储,因为这样既省空间又省时间,在vector的辅助下,存边已经不需要链表的辅助。
#include <cstdio>#include <cmath>#include <cstring>#include <vector>using namespace std;const int MAXN = 10001;const int INF = 1000001;struct Node{int to,dist;};int n,m,s,t;int dist[MAXN];bool vis[MAXN];vector <Node> G[MAXN];int main(){scanf("%d%d",&n,&m);for (int i=1;i<=m;i++){int from,to,dist;scanf("%d%d%d",&from,&to,&dist);G[from].push_back((Node){to,dist});}scanf("%d%d",&s,&t);memset(dist,127,sizeof(dist));for (int i=0;i<G[s].size();i++) dist[G[s][i].to] = min(dist[G[s][i].to],G[s][i].dist);dist[s] = 0; vis[s] = true;for (int i=1;i<=n;i++){ int k,_min = INF;; for (int j=1;j<=n;j++) if (!vis[j] && dist[j] < _min) { k = j; _min = dist[j]; } if (_min == INF) break; vis[k] = true; for (int j=0;j<G[k].size();j++) if (!vis[G[k][i].to] && dist[G[k][i].to] > dist[k]+G[k][i].dist) dist[G[k][i].to] = dist[k]+G[k][i].dist;}printf("%d\n",dist[t]);return 0;}
从以上代码我们发现,Dijkstra算法的时间复杂度为O(n^2),而大部分时间都是用来查找最小边权。有什么数据结构可以来快速寻找最值呢?优先队列!实现方法较为简单,直接上代码
Heap Dijkstra:
#include <cstdio>#include <cmath>#include <cstring>#include <vector>#include <queue>using namespace std;const int MAXN = 10001;struct Node{int to,dist;bool operator < (const Node &x) const{ return dist > x.dist; }};int n,m,s,t;int dist[MAXN];bool done[MAXN];vector <Node> G[MAXN];int main(){scanf("%d%d",&n,&m);for (int i=1;i<=m;i++){int from,to,dist;scanf("%d%d%d",&from,&to,&dist);G[from].push_back((Node){to,dist});}scanf("%d%d",&s,&t);memset(dist,127,sizeof(dist)); priority_queue <Node> Q;Q.push((Node){s,0}); dist[s] = 0; while (!Q.empty()) { Node x = Q.top();Q.pop(); if (!done[x.to]) { done[x.to] = true; for (int i=0;i<G[x.to].size();i++) if (dist[G[x.to][i].to] > dist[x.to] + G[x.to][i].dist) { dist[G[x.to][i].to] = dist[x.to] + G[x.to][i].dist; Q.push((Node){G[x.to][i].to,dist[G[x.to][i].to]}); } } }printf("%d\n",dist[t]);return 0;}从上面这段代码我们可以发现,优先队列维护的Dijkstra的时间复杂度为O(n*logn),是一个稳定的优秀算法。正因为它的稳定性,它的作用甚至比SPFA还大——除了不能处理负权。在许多比赛中,正因为SPFA有着不稳定性,常常会被出题人卡数据,导致SPFA降为O(VE)的算法,因此强烈建议在竞赛中使用Heap Dijkstra。
BellmanFord算法:
在许多比赛中,边权常常有负值——甚至会出现负权回路。因此,最稳定的Heap Dijkstra便无法发挥出它的作用。于是,BellmanFord算法便展现出了它的威力。O(VE)的算法,可以很好的处理负权并有效地判断负权回路。
#include <cstdio>#include <cstring>#include <vector>using namespace std;const int MAXN=1001;struct Node{ int from,to,dist;};int n,m,s,t;int dist[MAXN];int cnt[MAXN];vector <Node> G;int main(){ scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { int from,to,dist; scanf("%d%d%d",&from,&to,&dist); G.push_back((Node){from,to,dist}); } scanf("%d%d",&s,&t); memset(dist,127,sizeof(dist)); for (int i=0;i<m;i++) if (G[i].from == s) dist[G[i].to] = min(dist[G[i].to],G[i].dist); dist[s] = 0; for (int i=1;i<=n;i++) { bool flag = false; for (int j=0;j<m;j++) { if (dist[G[j].to] > dist[G[j].from]+G[j].dist) { flag = true; dist[G[j].to] = dist[G[j].from]+G[j].dist; } } if (!flag) break;//避免冗余操作 } bool Negative_Ctyle = false; for (int i=0;i<m;i++) if (dist[G[i].to] > dist[G[i].from]+G[i].dist) { Negative_Ctyle = true; break; } if (!Negative_Ctyle) printf("%d\n",dist[t]);else printf("Negetive Ctyle Exists!\n"); return 0;}由代码我们可以发现,O(VE)的操作中存在许多冗余操作,即使是加了flag来进行判断,也仅仅是去除了少部分冗余,在效率上并没有太大的提升。因此,为了有效地提升效率,SPFA算法便产生了。它使用队列来维护松弛操作,将时间复杂度降为O(kE)(可以证明k的平均大小为2),代码如下
SPFA:
#include <cstdio>#include <cstring>#include <vector>#include <queue>#include <algorithm>using namespace std;const int MAXN=1001;const int INF=100000;struct Node{ int to,dist;};int n,m,s,t;int dist[MAXN];//离源点的最短距离 int cnt[MAXN]; //判断是否存在负权回路 bool inq[MAXN];//是否在队列中 vector <Node> G[MAXN];bool SPFA(int s){ for (int i=1;i<=n;i++) dist[i]=INF; memset(inq,false,sizeof(inq)); dist[s]=0;inq[s]=true; queue <int> Q; Q.push(s); while (!Q.empty()) { int x=Q.front();Q.pop(); inq[x]=false; for (int i=0;i<G[x].size();i++) if (dist[x]+G[x][i].dist<dist[G[x][i].to]) { dist[G[x][i].to]=dist[x]+G[x][i].dist; if (!inq[G[x][i].to]) { Q.push(G[x][i].to); inq[G[x][i].to]=true; if (++cnt[G[x][i].to] > n) return true; } } } return false;}int main(){ scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { int from,to,dist; scanf("%d%d%d",&from,&to,&dist); G[from].push_back((Node){to,dist}); } scanf("%d%d",&s,&t); bool Negative_Ctyle = SPFA(s); if (!Negative_Ctyle) printf("%d\n",dist[t]);else printf("Negative Ctyle Exist!\n"); return 0;}
以上代码还可以很好地实现差分约束系统,这里不作详解。
关于SPFA与BFS,这也是最近看书的时候看到的,其实很多BFS的题目都可以由SPFA解决,比如经典的迷宫问题,用SPFA效率会快很多。
后记:
为了更好地帮助新手实现模板,这里给出最短路径的模板题目
http://www.rqnoj.cn/problem/341
同时,推荐几道题,供大家学习。
https://www.vijos.org/p/1119(标准最短路,没什么难度但很累)
https://www.vijos.org/p/1046(Floyed最小环)
http://poj.org/problem?id=1860(BellmanFord判断负权回路)
https://www.vijos.org/p/1155(次小路)
- 最短路径算法
- 最短路径算法
- 最短路径理解
- 最短路径算法
- 最短路径算法
- 最短路径问题
- 最短路径
- 最短路径
- 最短路径算法
- 图@ 最短路径
- 最短路径
- 最短路径算法
- 最短路径
- hdu2544(最短路径)
- 最短路径问题
- 最短路径问题
- 最短路径算法
- 最短路径算法
- 编程之美之中国象棋将帅问题
- 很反感凡是叼杠时尚的风格
- IOS 画1px细线
- JAVA通过搜狗词库过滤指定词性
- poj 1390 Blocks(dp,黑书dp方块消除)
- 最短路径
- iOS面试考察点
- erlang中mnesia的动态加载
- putty连接BT5
- HDU 4753 Fishhead’s Little Game(博弈搜索)
- Lua中,assert的作用
- Java普通类获取Spring XML中Bean的方法总结
- Android 设置访问WebService的timeout
- css小技巧