zoukankan      html  css  js  c++  java
  • 关于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
    ", 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
    ", 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
    ", 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
    ", cas, Ans);
    93     }
    94 
    95     return 0;
    96 }
    View Code
  • 相关阅读:
    ASP的URL重写技术 IIS的ISAPI
    关于如何去勾引百度谷歌的蜘蛛爬虫
    如何让百度和google的蜘蛛爬虫迅速爬过来
    本机不安装Oracle客户端,使用PL/SQL Developer和 Instant Client 工具包连接oracle 11g远程数据库
    Java小知识点总结
    Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)
    OracleDBconsoleorcl无法启动解决方案
    Java中PreparedStatement的错误使用
    Oracle 中行列转换问题总结
    Java中 Interger 的自动装箱
  • 原文地址:https://www.cnblogs.com/MingSD/p/9741591.html
Copyright © 2011-2022 走看看