最短路径问题是图论研究中的一个经典算法问题,旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。
dijkstra(单源最短路径)
Dijkstra单源最短路算法,即计算从起点出发到每个点的最短路。Dijkstra常常作为其他算法的预处理。
- 使用邻接矩阵的时间复杂度为O(n^2)
- 用邻接表+优先队列(堆)的时间复杂度为O((m+n)logn)近似为O(mlogn)
这个算法只能计算单元最短路,而且不能计算负权值,这个算法是贪心的思想, dis数组用来储存起始点到其他点的最短路,但开始时却是存的起始点到其他点的初始路程。通过n-1遍的遍历找最短。
比如1到3的最短路就是比较dis[3]与dis[2]+e[2][3],如果大于的话就更新dis[3]位dis[2]+e[2][3],这个专业术语叫松弛,这种算法的核心思想就是通过边来松弛一号顶点到其他定点的路程,这也就能解释为什么要遍历n-1遍了。
book数组用来标记,被标记的是已经找过的最短路,没被标记的没有被找过的最短路,当全部找过以后算法结束,也就是说dis数组中的数是起始点到其他所有点的最短路 。
以下为以 HDU 1874 畅通工程续 完整代码:
- dijkstra+邻接矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <iostream> #include <string> const int inf=0x3f3f3f3f; const int MAXN=200+10; using namespace std;
int map[MAXN][MAXN]; int dis[MAXN]; bool vis[MAXN]; int n,m;
void dijkstra(int s){ memset(vis, 0, sizeof(vis)); int cur=s; dis[cur]=0; vis[cur]=1;
for(int i=0; i<n; i++){ for(int j=0; j<n; j++){ if(!vis[j] && dis[j] > dis[cur] + map[cur][j]){ dis[j]=dis[cur] + map[cur][j]; } } int mini=inf; for(int j=0; j<n; j++){ if(!vis[j] && dis[j]<mini){ mini=dis[j]; cur = j; } } vis[cur]=1; } }
int main(){ while(~scanf("%d%d",&n,&m)){ for(int i=0; i<n; i++){ dis[i]=inf; for(int j=0; j<n; j++){ map[i][j]=inf; } }
int from,to,val; for(int i=1; i<=m; i++){ scanf("%d%d%d",&from,&to,&val); map[from][to] = map[to][from] = val; }
int s,t; scanf("%d%d",&s,&t); dijkstra(s);
if(dis[t]==inf) printf("-1\n"); else printf("%d\n",dis[t]); } system("pause"); return 0; }
|
- dijkstra+优先队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <iostream> #include <string> #include <queue> const int inf=0x3f3f3f3f; const int MAXN=200+10; using namespace std;
int head[MAXN],len; int dis[MAXN]; bool vis[MAXN]; int n,m;
struct edge{ int to, val, next; }e[MAXN];
void add(int from, int to, int val){ e[len].to=to; e[len].val=val; e[len].next=head[from]; head[from]=len++; }
struct point{ int val, id; point(int id,int val):id(id),val(val){} bool operator <(const point &x)const{ return val>x.val; } };
void dijkstra(int s){ memset(vis, 0, sizeof(vis)); for(int i=0; i<n; i++) dis[i] = inf; priority_queue<point> q; q.push(point(s,0)); dis[s]=0; while(!q.empty()){ int cur=q.top().id; q.pop(); if(vis[cur]) continue; vis[cur]=true; for(int i=head[cur]; i!=-1; i=e[i].next){ int id=e[i].to; if(!vis[id] && dis[cur]+e[i].val < dis[id]){ dis[id]=dis[cur]+e[i].val; q.push(point(id, dis[id])); } } } }
int main(){ while(~scanf("%d%d",&n,&m)){ len=0; memset(head, -1, sizeof(head)); for(int i=0; i<m; i++){ int from, to, val; scanf("%d%d%d",&from,&to,&val); add(from, to, val); add(to, from, val); } int s,t; scanf("%d%d",&s,&t); dijkstra(s);
if(dis[t]==inf) printf("-1\n"); else printf("%d\n",dis[t]); } system("pause"); return 0; }
|
SPFA
SPFA 是 bellman-ford 算法的队列实现版本(貌似也改进了点)
SPFA 的实现如下:
用数组 dis 记录更新后的状态,cnt 记录更新的次数,队列 q 记录更新过的顶点,算法依次从 q 中取出顶点 v,按照 dis(k)[u]=min{dis(k-1)[v]+e(v,u)} 的递归式更新。在计算过程中,一旦发现顶点 K 有 cnt[k]>n,说明有一个从顶点 K 出发的负权圈,此时没有最短路,应终止算法。否则,队列为空的时候,算法得到G的各顶点的最短路径长度。
下面举一个实例来说明SFFA算法是怎样进行的:


1.邻接矩阵的SPFA HDU 1874 畅通工程续
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include <iostream> #include <string> #include <queue> const int inf=0x3f3f3f3f; const int MAXN=200+10; using namespace std;
int map[MAXN][MAXN]; int dis[MAXN]; bool vis[MAXN]; int n,m;
void SPFA(int s){ for(int i=0; i<n; i++) dis[i]=inf; memset(vis, 0, sizeof(vis)); vis[s]=1; dis[s]=0;
queue<int> q; q.push(s);
while(!q.empty()){ int cur=q.front(); q.pop(); vis[cur]=false; for(int i=0; i<n; i++){ if(dis[i] > dis[cur] + map[cur][i]){ dis[i] = dis[cur] + map[cur][i]; if(!vis[i]){ q.push(i); vis[i]=true; } } } } }
int main(){ while(~scanf("%d%d",&n,&m)){
for(int i=0; i<n; i++){ for(int j=0; j<n; j++){ map[i][j]=inf; } }
int from,to,val; for(int i=1; i<=m; i++){ scanf("%d%d%d",&from,&to,&val); if(map[from][to]>val) map[from][to] = map[to][from] = val; }
int s,t; scanf("%d%d",&s,&t); SPFA(s);
if(dis[t]==inf) printf("-1\n"); else printf("%d\n",dis[t]); } system("pause"); return 0; }
|
- 邻接表的SPFA(推荐)HDU 1874 畅通工程续
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <iostream> #include <string> #include <queue> const int inf=0x3f3f3f3f; const int MAXN=200+10; using namespace std;
int head[MAXN],len; int dis[MAXN]; //dis用来记录初始点到个点的位置 bool vis[MAXN]; //标记是否被访问 int n,m; //n表示顶点个数,m表示边的条数
struct edge{ int to, val, next; }e[MAXN];
void add(int from, int to, int val){ e[len].to=to; e[len].val=val; e[len].next=head[from]; head[from]=len++; }
void SPFA(int s){ memset(vis, 0, sizeof(vis)); //初始化判断数组 for(int i=0; i<n; i++) dis[i] = inf;
queue<int> q; q.push(s); vis[s]=true; dis[s]=0;
while(!q.empty()){ int cur=q.front(); q.pop(); vis[cur]=false; for(int i=head[cur]; i!=-1; i=e[i].next){ int id=e[i].to; if(dis[id] > dis[cur]+e[i].val){ dis[id] = dis[cur]+e[i].val; if(!vis[id]){ vis[id]=true; q.push(id); } } } } }
int main(){ while(~scanf("%d%d",&n,&m)){ len=0; memset(head, -1, sizeof(head));
for(int i=0; i<m; i++){ int from, to, val; scanf("%d%d%d",&from,&to,&val); add(from, to, val); add(to, from, val); } int s,t; scanf("%d%d",&s,&t); //输入起点和终点 SPFA(s);
if(dis[t]==inf) printf("-1\n"); else printf("%d\n",dis[t]); } system("pause"); return 0; }
|
floyd
Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall 算法的时间复杂度为$O(N^3)$,空间复杂度为$O(N^2)$。
这是一个dp(动态规划的过程)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
即从顶点i到j且经过顶点k的最短路径长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream> #include <string> #include <queue> const int inf=0x3f3f3f3f; const int MAXN=200+10; using namespace std;
int dis[MAXN][MAXN]; int n,m;
void floyd(){ for(int k=0; k<n; k++) for(int i=0; i<n; i++) for(int j=0; j<n; j++) dis[i][j]=min(dis[i][j], dis[i][k]+dis[k][j]); }
int main(){ while(~scanf("%d%d",&n,&m)){
for(int i=0; i<n; i++) for(int j=0; j<n; j++) dis[i][j]=inf;
for(int i=0; i<m; i++){ int from, to, val; scanf("%d%d%d",&from,&to,&val); if(dis[from][to] > val) dis[to][from]=dis[from][to]=val; } int s,t; scanf("%d%d",&s,&t); if(s==t){ printf("0\n"); continue; } floyd();
if(dis[s][t]==inf) printf("-1\n"); else printf("%d\n",dis[s][t]); } system("pause"); return 0; }
|
本文整理自:
http://blog.csdn.net/acm_1361677193/article/details/48211319
http://blog.csdn.net/xunalove/article/details/70045815