关于dijkstra的优化 及 多源最短路

先来看这样一道题目 

给你N个点,M条双向边,要求求出1号点到其他所有点的距离。其中 2 <= N <= 1e5,  1 <=M <= 1e6.

对于这样的一道题目 我们当然不可能开一个数组edge[N][N]来记录边的信息,根本不可能开的下。

假如开下了也会有很多边为-1,浪费了很多空间。 所以可以对存边的方式进行优化。

 

优化1: 对边进行优化。

因为edge[N][N]的空间需要N^2大小,当N稍微大一点点的时候,就没办法开这么大的空间。

并且由于当边的分布比较散的时候,我们每次找到一个新的最小点之后,都要遍历他所有的边去更新d[]数组,

然而当边分布分散的时候,我们会花大部分时间在不存在的边上。

在这里我们用链式前向星来存边,这样可以使得存边的空间大大减小,并且每次更新的时候遍历的边都是真正存在的边,不会在访问那些不存在的边。

链式前向星优化 

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N = 105;
 7 const int M = 10010 * 2;
 8 const int inf = 0x3f3f3f3f;
 9 int d[N];
10 bool vis[N];
11 int head[N];
12 int nt[M], to[M], w[M];
13 int tot;
14 void init(){
15     memset(head, -1, sizeof(head));
16     tot = 0;
17 }
18 void add(int u, int v, int val){
19     to[tot] = v;
20     w[tot] = val;
21     nt[tot] = head[u];
22     head[u] = tot++;
23 }
24 int main()
25 {
26     int n, m;
27     while(~scanf("%d%d",&n,&m)&& (n || m)){
28         int a,b,c;
29         init();
30         memset(d, inf, sizeof(d));
31         memset(vis, 0, sizeof(vis));
32         while (m--){
33             scanf("%d%d%d", &a, &b, &c);
34             add(a,b,c);
35             add(b,a,c);
36         }
37         d[1] = 0;
38         while (1){
39             int min1 = inf,z = -1;
40             for (int j = 1;j <= n; j++)
41                 if(!vis[j] && min1 > d[j])
42                     z = j, min1 = d[j];
43             if(z == -1) break;
44             vis[z] = 1;
45             for (int j = head[z]; ~j; j = nt[j]){
46                 d[to[j]] = min(d[to[j]], d[z] + w[j]);
47             }
48         }
49         printf("%d\n", d[n]);
50     }
51     return 0;
52 }
View Code

 

 当然,也可以用vector来存边的信息

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N = 105;
 7 const int inf = 0x3f3f3f3f;
 8 int d[N];
 9 bool vis[N];
10 struct Node{
11     int to;
12     int w;
13 };
14 vector<Node> G[N];
15 int main()
16 {
17     int n, m;
18     while(~scanf("%d%d",&n,&m)&& (n || m)){
19         int a,b,c;
20         for(int i = 1; i <= n; i++)
21             G[i].clear();
22         memset(d, inf, sizeof(d));
23         memset(vis, 0, sizeof(vis));
24         while (m--){
25             scanf("%d%d%d", &a, &b, &c);
26             G[a].push_back({b,c});
27             G[b].push_back({a,c});
28         }
29         d[1] = 0;
30         while (1){
31             int min1 = inf,z = -1;
32             for (int j = 1;j <= n; j++)
33                 if(!vis[j] && min1 > d[j])
34                     z = j, min1 = d[j];
35             if(z == -1) break;
36             vis[z] = 1;
37             for(int j = 0; j < G[z].size(); j++){
38                 int v = G[z][j].to, dis = G[z][j].w;
39                 d[v] = min(d[v], d[z] + dis);
40             }
41         }
42         printf("%d\n", d[n]);
43     }
44     return 0;
45 }
View Code

 

我个人还是更喜欢用链式前向星,虽然要写add可是链式前向星的的常数小一点。

经过优化之后,他的复杂度就变成了 N*N + 2*E,虽然还是N^2级别,可是他的常数比没优化前的小。

 

当然对于开头的那个题目来说,我们可以存下边的信息了,但是N^2的复杂度还是没办法接受的。

 

优化2:对时间进行优化(需要先明白优先队列)

我们发现 在优化1后的代码实现中,我们需要 1 找到d最小的点 2用最小的点去更新d数组。3 重复1->2的过程,直到所有的点都不会发生改变。

对于操作2来说,我们进行了优化1之后,操作2做的已经是最优了,他所干的事情没有一个是没意义的,

对于操作3来说,从dijkstra来说,只有进行了n次之后,才能保证每个点都到了最短的距离。

所以我们只能优化操作1,找到d[]值最小的点。

在这里我们使用优先队列对于时间进行。

#define pll pair<int,int>

priority_queue<pll,vector<pll> ,greater<pll> >que;

que.push(pll(0,1));//左边为dis 右边为点

优先队列本来是优先把大的元素放在顶上,我们可以使用top()函数来获取优先队列的优先级最高的元素。

优先队友可以自定义优先级,在这里,我们将优先级定义为

pair的第一维越小就在队列的最前面,我们把距离放在第一维,把点放在第二维。

这样每次我们从优先队列中取出一个pair,都是队列中离原点距离最小的点了。

这样我们只需要lgn的复杂度就可以找到最小的那个点了,而不是每次都n的代价扫一遍d[]的数组找到最小的那个点了。

当我们每次找到一个点之后,假设找到点为u,我们都先判断一下u 是不是被标记过了,如果被标记过了,那么我就继续再找下一个点。

如果没有被标记过,那么我们就从u点出发,看一下附近的点能不能通过这个点出发使得他的d更小。

加入现在存在点v, d[v] > d[u] + w。那么我们就更新d[v],然后把 pll(d[v], v)放进队列中,等待选取。

直到优先队列为空,那么就结束更新。

 

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #include<iostream>
 4 #include<cstring>
 5 #include<cstdio>
 6 #include<algorithm>
 7 typedef pair<int,int> pll;
 8 using namespace std;
 9 const int N = 105;
10 const int M = 10010 * 2;
11 const int inf = 0x3f3f3f3f;
12 int d[N];
13 bool vis[N];
14 int head[N];
15 int nt[M], to[M], w[M];
16 int tot;
17 void init(){
18     memset(head, -1, sizeof(head));
19     tot = 0;
20 }
21 void add(int u, int v, int val){
22     to[tot] = v;
23     w[tot] = val;
24     nt[tot] = head[u];
25     head[u] = tot++;
26 }
27 void dijkstra(){
28     priority_queue<pll,vector<pll> ,greater<pll> >que;
29     que.push(make_pair(0,1));//左边为dis 右边为点
30     while(!que.empty()){
31         pll temp = que.top();
32         que.pop();
33         int dis = temp.first;
34         int u = temp.second;
35         //if(dis != d[u]) continue;
36         if(vis[u]) continue;
37         vis[u] = 1;
38         for(int i = head[u]; ~i; i = nt[i]){
39             int v = to[i];
40             if(d[v] > d[u] + w[i]){
41                 d[v] = d[u] + w[i];
42                 que.push(pll(d[v],v));
43             }
44         }
45     }
46 
47 }
48 int main()
49 {
50     int n, m;
51     while(~scanf("%d%d",&n,&m)&& (n || m)){
52         int a,b,c;
53         init();
54         memset(d, inf, sizeof(d));
55         memset(vis, 0, sizeof(vis));
56         while (m--){
57             scanf("%d%d%d", &a, &b, &c);
58             add(a,b,c);
59             add(b,a,c);
60         }
61         d[1] = 0;
62         dijkstra();
63         printf("%d\n", d[n]);
64     }
65     return 0;
66 }
View Code

 

代码中有一个被注释的地方 

if(dis != d[u]) continue;

我们可以用这一句话代替vis数组。

假设优先队列中存在 pll(10,5) pll(100,5) 2个pair, 通过上面我们可以知道, 第一个肯定是先把(10,5)的这一个pair取出来,并且d[5] = 10,以为d[n]会被更新成最小的值。

我们取出(10,5)的时候,d[5] = 10, 我们通过 d[5] = 10 去更新别的点, 更新完了之后, 假设我们接下来取出的是 (100,5) d[5] != 100, 说明5号点已经通过最优的距离更新过其他点了, 就不需要再更新一次了,从而达到标记的效果。

现在代码的复杂度就是 n*lg(n) + 2*E了。就可以解决一开始的问题了。

 

关于多源点最短路的问题。

现在有n个地点,m条双向边,现在有p个商店,小明想知道从任意一个点出发,到附近最近的商店的距离至少是多少。 1 <= n <= 1e5    1 <= m <= 1e6

这个问题咋一看,需要求出每个点到附近的商店的最短路是多少,没有任何头绪,然后我们转化思路,求出所有商店到任意一点的最短路。也还是多个点到多个点的最短路。

难道跑p遍dijkstra 还是跑一遍flody?

都不对,其实我们可以发现,从第1个商店走到x点的和第2个商店走到x点的距离,他的性质是不发生变化的,也就是说,都是某一个商店到x的距离。

我们转化一下思路, 开一个假的节点 s, 然后s和每个商店都存在着一条距离为0的边,我们再以s为起点,跑出s点到任意点的最短路,那么我们就可以通过一遍dijkstra得到每个点到s的最短路是多少,也是任意一点到商店的最短路径是多少,就解出来了。

 

HDU - 6166

题意:就是有n个城市,m条单向边,现在有p个特殊城市,求这p个特殊城市中 两两之间的最小距离是多少。

题解:这个题目的思路和上面是一样的,就是我们走路的时候带一个从特殊城市出发的标记,每次往前走的时候,遇到相同的标记就不再走,遇到不同的标记,就更新一下答案。最后找到答案然后输出就好了。

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
 4 #define LL long long
 5 #define ULL unsigned LL
 6 #define fi first
 7 #define se second
 8 #define pb push_back
 9 #define lson l,m,rt<<1
10 #define rson m+1,r,rt<<1|1
11 #define lch(x) tr[x].son[0]
12 #define rch(x) tr[x].son[1]
13 #define max3(a,b,c) max(a,max(b,c))
14 #define min3(a,b,c) min(a,min(b,c))
15 typedef pair<int,int> pll;
16 const int inf = 0x3f3f3f3f;
17 const LL INF = 0x3f3f3f3f3f3f3f3f;
18 const LL mod =  (int)1e9+7;
19 const int N = 4e5 + 100;
20 struct Node{
21     int fa;
22     LL dis;
23     int o;
24     bool operator < (const Node & x) const{
25         return dis > x.dis;
26     }
27 };
28 LL dis[N], ans[N], vis[N];
29 int a[N];
30 int head[N], nt[N], to[N]; LL d[N];
31 int tot;
32 void add(int u, int v, LL val){
33     to[tot] = v;
34     d[tot] = val;
35     nt[tot] = head[u];
36     head[u] = tot++;
37 }
38 priority_queue<Node> q;
39 int n, m, p;
40 void dijk(){
41     for(int i = 1; i <= p; i++){
42         q.push({a[i],0,a[i]});
43         dis[a[i]] = 0;
44         vis[a[i]] = a[i];
45     }
46     while(!q.empty()){
47         LL dd = q.top().dis; int rt = q.top().fa, u = q.top().o;
48         q.pop();
49         if(dis[u] < dd) continue;
50         vis[u] = rt;
51         for(int i = head[u]; ~i; i = nt[i]){
52             int v = to[i];
53             LL w = d[i];
54             if(vis[v] == vis[u]) continue;
55             if(vis[v]) {
56                 LL tmp = dis[v] + dd + w;
57                 ans[rt] = min(ans[rt], tmp);
58                 ans[vis[v]] = min(ans[vis[v]], tmp);
59             }
60             if(w + dis[u] < dis[v]){
61                 dis[v] = dis[u] + w;
62                 q.push({rt, dis[v], v});
63             }
64         }
65     }
66 }
67 void init(){
68     memset(dis, INF, sizeof(dis));
69     memset(head, -1, sizeof(head));
70     memset(ans, INF, sizeof(ans));
71     memset(vis, 0, sizeof(vis));
72     tot = 0;
73 }
74 int main(){
75     int T;
76     scanf("%d", &T);
77     for(int cas = 1; cas <= T; cas++){
78         scanf("%d%d", &n, &m);
79         int u, v, w;
80         init();
81         for(int i = 1; i <= m; i++){
82             scanf("%d%d%d", &u, &v, &w);
83             add(u, v, w);
84         }
85         scanf("%d", &p);
86         for(int i = 1; i <= p; i++)
87             scanf("%d", &a[i]);
88         dijk();
89         LL Ans = INF;
90         for(int i = 1; i <= p; i++)
91             Ans = min(Ans, ans[a[i]]);
92         printf("Case #%d: %lld\n", cas, Ans);
93     }
94 
95     return 0;
96 }
View Code

 

posted @ 2018-10-04 09:26  Schenker  阅读(543)  评论(0编辑  收藏  举报