• dij算法为什么不能处理负权,以及dij算法变种


    对于上面那张图,是可以用dij算法求解出正确答案,但那只是巧合而已。

    我们再看看下面这张图。

    dist[4] 是不会被正确计算的。 因为dij算法认为从队列出来的点,(假设为u)肯定是已经求出最短路的点,标记点u。并用点u更新其它点。

    所以如果存在负权使得这个点的权值更小,那么会更新dist[u], 但是因为这个点已经被标记了,所以dij算法不会用这个点来更新其它点,所以就导致了算法的错误。

    归结原因,dij算法在存在负权的时候,过早得确立某个点最短路,以至于如果这个点不是最短路,就会导致错误。

     如下面的算法所示

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 #pragma warning(disable:4996)
    15 typedef long long LL;                   
    16 const int INF = 1<<30;
    17 
    18 const int N = 1000 + 10;
    19 struct Edge
    20 {
    21     int to,dist;
    22     bool operator<(const Edge&rhs)const
    23     {
    24         return dist > rhs.dist;
    25     }
    26 };
    27 vector<Edge> g[N];
    28 int dist[N];
    29 bool vis[N];
    30 void dij(int start, int n)
    31 {
    32     memset(vis,0,sizeof(vis));
    33     for(int i=1; i<=n; ++i)
    34         dist[i] = INF;
    35     priority_queue<Edge> q;
    36     Edge tmp,cur;
    37     dist[start] = cur.dist = 0;
    38     cur.to = start;
    39     q.push(cur);
    40     while(!q.empty())
    41     {
    42         cur = q.top(); q.pop();
    43         int u = cur.to;
    44 
    45         /*
    46         如果u被用来更新过其它点,那么即使存在负权使得dist[u]变小,
    47         那么dij算法也不会再用u来更新其它点,这就是dij不能处理负权回路的原因
    48         */
    49         if(vis[u]) continue;
    50         vis[u] = true;
    51         for(int i=0; i<g[u].size(); ++i)
    52         {
    53             int v = g[u][i].to;
    54             if(dist[v] > dist[u] + g[u][i].dist)
    55             {
    56                 tmp.dist = dist[v] = dist[u] + g[u][i].dist;
    57                 tmp.to = v;
    58                 q.push(tmp);
    59             }
    60         }
    61     }
    62     
    63 }
    64 
    65 int main()
    66 {
    67     int n,m,a,b,c,i;
    68     Edge tmp;
    69     while(scanf("%d%d",&n,&m)!=EOF)
    70     {
    71         for(i=0; i<m; ++i)
    72         {
    73             scanf("%d%d%d",&a,&b,&c);
    74             tmp.to = b;
    75             tmp.dist=  c;
    76             g[a].push_back(tmp);
    77         }
    78         dij(1,n);
    79         for(i=1; i<=n; ++i)
    80             printf("%d ",dist[i]);
    81         puts("");
    82     }
    83     return 0;
    84 }
    View Code

    4 4
    1 2 3
    1 3 2
    2 3 -2
    3 4 2
    dist[0->4] = 0 3 1 4; wrong

    那么我们可以对上面的算法进行改进,上面算法的问题在于,一个点如果被标记以后,那么这个点是不会用来更新其它点的,哪怕到这个点的最短路径减小了,也不会用来更新其它点。

    所以新的改进是我们允许一个点出队列多次,只要这个点对最短路的更新有贡献。

    只要dist[u]>=cur.dist   ,  那么我们就认为点u可能对最短路的更新有贡献,所以让点u去更新最短路。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 #pragma warning(disable:4996)
    15 typedef long long LL;                   
    16 const int INF = 1<<30;
    17 /*
    18 3 3
    19 1 2 3
    20 1 3 2
    21 2 3 -2
    22 
    23 
    24 4 4
    25 1 2 3
    26 1 3 2
    27 2 3 -2
    28 3 4 2
    29 dist[0->4] = 0 3 1 3; correct
    30 
    31 */
    32 const int N = 1000 + 10;
    33 struct Edge
    34 {
    35     int to,dist;
    36     bool operator<(const Edge&rhs)const
    37     {
    38         return dist > rhs.dist;
    39     }
    40 };
    41 vector<Edge> g[N];
    42 int dist[N];
    43 //允许一个点入队列多次,是spfa算法, 可以看做是dij的变种或者bellman的变种
    44 void spfa(int start, int n)
    45 {
    46     for(int i=1; i<=n; ++i)
    47         dist[i] = INF;
    48     priority_queue<Edge> q;
    49     Edge tmp,cur;
    50     dist[start] = cur.dist = 0;
    51     cur.to = start;
    52     q.push(cur);
    53     while(!q.empty())
    54     {
    55         cur = q.top(); q.pop();
    56         int u = cur.to;
    57         if(dist[u]<cur.dist) continue; //这里就是允许点u用来多次更新其它点的关键
    58         for(int i=0; i<g[u].size(); ++i)
    59         {
    60             int v = g[u][i].to;
    61             if(dist[v] > dist[u] + g[u][i].dist)
    62             {
    63                 tmp.dist = dist[v] = dist[u] + g[u][i].dist;
    64                 tmp.to = v;
    65                 q.push(tmp);
    66             }
    67         }
    68     }
    69     
    70 }
    71 
    72 int main()
    73 {
    74     int n,m,a,b,c,i;
    75     Edge tmp;
    76     while(scanf("%d%d",&n,&m)!=EOF)
    77     {
    78         for(i=0; i<m; ++i)
    79         {
    80             scanf("%d%d%d",&a,&b,&c);
    81             tmp.to = b;
    82             tmp.dist=  c;
    83             g[a].push_back(tmp);
    84         }
    85         dij(1,n);
    86         for(i=1; i<=n; ++i)
    87             printf("%d ",dist[i]);
    88         puts("");
    89     }
    90     return 0;
    91 }
    View Code

    4 4
    1 2 3
    1 3 2
    2 3 -2
    3 4 2
    dist[0->4] = 0 3 1 3; correct

  • 相关阅读:
    【转】In ASP.NET using jQuery Uploadify upload attachment
    golang 初体验
    Node.js(express) + MongoDB(mongoose) 简单开发(二)
    Node.js(express) + MongoDB(mongoose) 简单开发(一)
    windows下修改mysql的root密码
    新网站添加百度官方认证
    手写单例模式
    浏览器内核
    IE内核浏览器
    没有猜中开头,更加没有预料结尾的我,正努力走在向程序媛发展的道路上……
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4511418.html
Copyright © 2020-2023  润新知