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 }
View Code

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 }
View Code

(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;
}
View Code

 

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 }
View Code

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 }
View Code

DFS版:

  由于DFS实测最短路超时orz... 因此DFS版主要用于判负环存在性。

 

posted @ 2015-01-04 23:52  Naturain  阅读(254)  评论(0编辑  收藏  举报