Book--追溯 最短路径各类算法及优化
2015-01-04 16:54:53
以Hdu 2544 为测试平台,浅结回顾最短路各类算法。
First:Bellman-Ford(边权可为负,可找负圈,复杂度:O(V*E))
思路回顾:如果不存在负圈,那么最短路不会经过一个点两次,那么最短路长度<= V-1,对全图进行V-1次松弛,每次松弛检查每条边,如果dis[to] > dis[from] + cost则更新。
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N,M,ecnt; 24 int dis[maxn]; 25 26 struct edge{ 27 int u,v,cost; 28 }e[maxn * maxn]; 29 30 int Bellman_ford(int s){ 31 for(int i = 1; i <= N; ++i) 32 dis[i] = INF; 33 dis[s] = 0; 34 for(int k = 1; k < N; ++k){ 35 bool flag = false; 36 for(int i = 1; i <= ecnt; ++i){ 37 int u = e[i].u; 38 int v = e[i].v; 39 if(dis[u] != INF && dis[v] > dis[u] + e[i].cost){ 40 dis[v] = dis[u] + e[i].cost; 41 flag = true; 42 } 43 } 44 if(!flag) break; 45 } 46 return dis[N]; 47 } 48 49 void Add_edge(int u,int v,int c){ 50 e[++ecnt].u = u; 51 e[ecnt].v = v; 52 e[ecnt].cost = c; 53 54 e[++ecnt].u = v; 55 e[ecnt].v = u; 56 e[ecnt].cost = c; 57 } 58 59 int main(){ 60 int a,b,c; 61 while(scanf("%d%d",&N,&M) != EOF){ 62 if(N == 0 && M == 0) break; 63 ecnt = 0; 64 for(int i = 1; i <= M; ++i){ 65 scanf("%d%d%d",&a,&b,&c); 66 Add_edge(a,b,c); 67 Add_edge(b,a,c); 68 } 69 printf("%d\n",Bellman_ford(1)); 70 } 71 return 0; 72 }
Second:Dijstra(边权须为正,复杂度:O(V*V),堆优化:O((E+V)logV)
思路回顾:BF算法中显然存在很多多余的检查,Dij算法是从一个只包含起点的点集开始,不断找出距离点集外距离起点最近的点,并加入点集,同时用新加入的点对dis[]进行松弛。
(1)朴素Dijstra
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N,M; 24 int first[maxn],next[maxn * maxn],ver[maxn * maxn],ecnt; 25 int dis[maxn],used[maxn]; 26 struct edge{ 27 int v,cost; 28 }e[maxn * maxn]; 29 30 void Add_edge(int u,int v,int c){ 31 next[++ecnt] = first[u]; 32 e[ecnt].v = v; 33 e[ecnt].cost = c; 34 first[u] = ecnt; 35 } 36 37 int Dijstra(int s){ 38 memset(used,0,sizeof(used)); 39 fill(dis + 1,dis + N + 1,INF); 40 dis[s] = 0; 41 int p; 42 for(int k = 1; k <= N; ++k){ 43 int tmin = INF; 44 for(int i = 1; i <= N; ++i) 45 if(!used[i] && dis[i] < tmin) tmin = dis[p = i]; 46 used[p] = 1; 47 for(int i = first[p]; i != -1; i = next[i]){ 48 int v = e[i].v; 49 if(!used[v] && dis[v] > dis[p] + e[i].cost) 50 dis[v] = dis[p] + e[i].cost; 51 } 52 } 53 return dis[N]; 54 } 55 56 57 int main(){ 58 int a,b,c; 59 while(scanf("%d%d",&N,&M) != EOF){ 60 if(N == 0 && M == 0) break; 61 memset(first,-1,sizeof(first)); 62 ecnt = 0; 63 for(int i = 1; i <= M; ++i){ 64 scanf("%d%d%d",&a,&b,&c); 65 Add_edge(a,b,c); 66 Add_edge(b,a,c); 67 } 68 printf("%d\n",Dijstra(1)); 69 } 70 return 0; 71 }
(2)堆(优先队列)优化Dijstra
细节:注意Dij里面的优先队列里面存的是dis[]数组的元素,即起点到某点的距离,与prim的堆中存边是不一样的。
#include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <vector> #include <map> #include <set> #include <stack> #include <queue> #include <iostream> #include <algorithm> using namespace std; #define lp (p << 1) #define rp (p << 1|1) #define getmid(l,r) (l + (r - l) / 2) #define MP(a,b) make_pair(a,b) typedef long long ll; typedef unsigned long long ull; typedef pair<int,int> pii; const int INF = (1 << 30) - 1; const int maxn = 110; int N,M; int first[maxn],nxt[maxn * maxn],ver[maxn * maxn],ecnt; int dis[maxn]; struct edge{ int v,cost; friend bool operator < (edge a,edge b){ return a.cost > b.cost; } }e[maxn * maxn]; void Add_edge(int u,int v,int c){ nxt[++ecnt] = first[u]; e[ecnt].v = v; e[ecnt].cost = c; first[u] = ecnt; } struct cmp{ bool operator ()(pii a,pii b){ return a.first > b.first; } }; int Dijstra(int s){ priority_queue<pii,vector<pii >,cmp> PQ; fill(dis + 1,dis + N + 1,INF); dis[s] = 0; PQ.push(MP(dis[s],s)); int cnt = 0; while(!PQ.empty()){ pii x = PQ.top(); PQ.pop(); if(dis[x.second] < x.first) continue; //当前的x并非最短路径,舍弃 for(int i = first[x.second]; i != -1; i = nxt[i]){ int v = e[i].v; if(dis[v] > dis[x.second] + e[i].cost){ dis[v] = dis[x.second] + e[i].cost; PQ.push(MP(dis[v],v)); } } } return dis[N]; } int main(){ int a,b,c; while(scanf("%d%d",&N,&M) != EOF){ if(N == 0 && M == 0) break; memset(first,-1,sizeof(first)); ecnt = 0; for(int i = 1; i <= M; ++i){ scanf("%d%d%d",&a,&b,&c); Add_edge(a,b,c); Add_edge(b,a,c); } printf("%d\n",Dijstra(1)); } return 0; }
Third:Floyd(边权可为负,可判是否有负圈,复杂度:O(V*V*V)
思路回顾:全图V次松弛
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N,M; 24 int g[maxn][maxn]; 25 26 void Floyd(){ 27 for(int k = 1; k <= N; ++k) 28 for(int i = 1; i <= N; ++i) 29 for(int j = 1; j <= N; ++j) 30 g[i][j] = min(g[i][j],g[i][k] + g[k][j]); 31 } 32 33 int main(){ 34 int a,b,c; 35 while(scanf("%d%d",&N,&M) != EOF){ 36 if(N == 0 && M == 0) break; 37 for(int i = 1; i <= N; ++i) 38 fill(g[i] + 1,g[i] + N + 1,INF); 39 for(int i = 1; i <= M; ++i){ 40 scanf("%d%d%d",&a,&b,&c); 41 g[a][b] = g[b][a] = min(g[a][b],c); 42 } 43 Floyd(); 44 printf("%d\n",g[1][N]); 45 } 46 return 0; 47 }
Fourth:SPFA(边权可为负,可判是否有负圈,复杂度:O(E),关于SPFA的研究讨论,推荐这篇论文:http://wenku.baidu.com/view/f22d0d36ee06eff9aef807e9.html)
思路回顾:队列优化Bellman-Ford,建立队列,初始先将起点入队,然后不断地取队首点,并且用该点的邻边去松弛,将能起到松弛作用边的另一点加入队列(当该点还不在队列中时),可以发现dis[]会不断变小,直至队列为空。
BFS版SPFA判负环:可以知道若不存在负环,那么一个点最多入队V次(根据度数)。那么若某点入队超过V次,则有负环。
DFS版SPFA判负环:根据最短路原理,若一个点入队多次,则有负环。判负环速度比BFS版快?倍!
BFS版:
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N,M; 24 int first[maxn],next[maxn * maxn],ver[maxn * maxn],ecnt; 25 int inq[maxn],dis[maxn]; 26 27 struct edge{ 28 int v,cost; 29 }e[maxn * maxn]; 30 31 void Add_edge(int u,int v,int c){ 32 next[++ecnt] = first[u]; 33 e[ecnt].v = v; 34 e[ecnt].cost = c; 35 first[u] = ecnt; 36 } 37 38 int Spfa(int s){ 39 queue<int> Q; 40 memset(inq,0,sizeof(inq)); 41 fill(dis + 1,dis + N + 1,INF); 42 Q.push(s); 43 inq[s] = 1; 44 dis[s] = 0; 45 while(!Q.empty()){ 46 int x = Q.front(); Q.pop(); 47 inq[x] = 0; 48 for(int i = first[x]; i != -1; i = next[i]){ 49 int v = e[i].v; 50 if(dis[v] > dis[x] + e[i].cost){ 51 dis[v] = dis[x] + e[i].cost; 52 if(inq[v] == 0){ 53 inq[v] = 1; 54 Q.push(v); 55 } 56 } 57 } 58 } 59 return dis[N]; 60 } 61 62 int main(){ 63 int a,b,c; 64 while(scanf("%d%d",&N,&M) != EOF){ 65 if(N == 0 && M == 0) break; 66 memset(first,-1,sizeof(first)); 67 ecnt = 0; 68 for(int i = 1; i <= M; ++i){ 69 scanf("%d%d%d",&a,&b,&c); 70 Add_edge(a,b,c); 71 Add_edge(b,a,c); 72 } 73 printf("%d\n",Spfa(1)); 74 } 75 return 0; 76 }
DFS版:
由于DFS实测最短路超时orz... 因此DFS版主要用于判负环存在性。