zoukankan      html  css  js  c++  java
  • 最短路算法略解

    Part 0. 最短路是解决这类问题的,给定一个图 $ G = (V,E) $ ,以及图中各边的距离,询问两点间的最短路径是多少。常用的有以下几种算法.

    Part 1. Dijkstra算法

      能够计算一个节点到其他所有节点的最短路径.前提是 $ G $ 中的边的权值均不为负.

    下面是我的代码习惯:

    $ G[i][j] $ 表示节点 $ i $ 和节点 $ j $ 之间的距离

    $ N $ 表示节点个数. $ st $ 表示起点. $ inf $ 表示一个很大的数 若 $ G[i][j] = inf $ 则表示 节点 $ i $ 和 $ j $ 不可达

    $ low[i] $ 表示节点 $ i $ 到 $ st $ 的距离

    松弛操作 $ Relax(u,v,w) if (low[v] > low[u] + w) low[v] = low[u] + w; $ 也就是判断$ st ightarrow v $ 和 $ st $ $ ightarrow $ $ u $ $ ightarrow $ $ v $ 哪个路径更短.

    集合 $ S $ 中的节点均为已确定最短路的节点,并用 $ vis[i] = true $ 来标记

    $ path[i] $ 表示节点 $ i $ 在最短路径中的前驱

    算法过程:

      初始化操作

    for each vertex v
        low[v] = G[st][v] ;
        path[v] = -1;
    low[st] = path[st] = 0 ;
    

      求解步骤

    1. 刚开始时 $ S $ 中只有 $ st $,选择一个 $ S $ 外的节点 $ v $ ,且满足 $ low[v] <= low[u] && u in V - S  $,即 $ v $ 是所有未确定最小值的点中距 $ st $ 最近的,那么可以直接确定 $ v $ 不能再被松弛,若可以被松弛则满足 $ low[v] > low[u] + G[u][v] &&  u in V-S $ 这与 $ low[v] <= low[u] &&  u in V-s $ 矛盾。所以,令 $ S[2] = v , vis[v] = true $ 。
    2. 现在集合 $ S $ 中已经有了两个元素了,那么刚刚加入的元素有什么用处么?我们尝试用它来松弛其他每一个 $ S $ 外的节点。
    3. 刚刚加入的节点已经起到了它的作用,现在只需要重复步骤 1 即可,也就是从 $ V - S $ 中寻找 $ low[v] <= low[u] $ ,同样,节点 $ V $ 可以直接加入集合 $ S $ ,同样用反证法证明
    4. 不断重复步骤 1 , 2 直到所有节点加入到 $ S $

    代码如下:

     1 class Dijkstra
     2 {
     3 public:
     4     int N , R , st , G[maxn][maxn] , low[maxn] , path[maxn] ;
     5     bool vis[maxn] ;
     6     void Init(int n,int r,int sst) {
     7         N = n , R = r , st = sst ;
     8         memset(vis,false,sizeof(vis)) ;
     9         memset(G,inf,sizeof(G)) ;
    10     }
    11     void addedge(int u,int v,int w) {
    12         G[u][v] = G[v][u] = w ;
    13     }
    14     void DIJ() {
    15         rep(i,N) {
    16             G[i][i] = 0 ;
    17             low[i] = G[st][i] ;
    18             if (low[i] == inf) path[i] = -1 ;
    19             else path[i] = st ;
    20         }
    21         path[st] = 0 ;
    22         vis[st] = true ;
    23         int tmp , id ;
    24         rep(i,N-1) {
    25             tmp = inf ;
    26             rep(j,N) {
    27                 if (!vis[j] && low[j] < tmp) {
    28                     tmp = low[j] ;
    29                     id = j ;
    30                 }
    31             }
    32             vis[id] = true ;
    33             rep(j,N) {
    34                 if (!vis[j] && low[j] > low[id] + G[id][j]) {
    35                     low[j] = low[id] + G[id][j] ;
    36                     path[j] = id ;
    37                 }
    38             }
    39         }
    40     }
    41     void pathprint(int en) {
    42         int p = en ;
    43         while (p != st) {
    44             printf("%d ",p) ;
    45             p = path[p] ;
    46         }
    47         printf("%d
    ",st) ;
    48     }
    49 }ans;
    Dijkstra

    Dijkstra 算法还有一种队列优化的形式,道理是一样的.代码在下面:

    hdu 2544

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 #include <cstring>
     5 #include <algorithm>
     6 #include <vector>
     7 #include <queue>
     8 using namespace std ;
     9 #define rep(i,n) for (int i = 1 ; i <= n ; ++ i)
    10 #define LL long long
    11 const int inf = 0x3f3f3f3f ;
    12 const int maxn = 110 ;
    13 struct EDGE
    14 {
    15     int u , v , w ;
    16 };
    17 struct HEHE
    18 {
    19     int u , w ;
    20     friend bool operator < (const HEHE & P,const HEHE & T) {
    21         return T.w < P.w ;
    22     }
    23 };
    24 
    25 class DIJKSTRA
    26 {
    27 public:
    28     vector<EDGE> egs ;
    29     vector<int> G_id[maxn] ;
    30     bool vis[maxn] ;
    31     int low[maxn] , N , R , st , path[maxn] ;
    32     void Init(int n,int r,int sst) {
    33         N = n , R = r , st = sst ;
    34         rep(i,N) {
    35             G_id[i].clear() ;
    36             vis[i] = false ;
    37             low[i] = inf ;
    38             path[i] = -1 ;
    39         }
    40         egs.clear() ;
    41         low[st] = path[st] = 0 ;
    42     }
    43     void addedge(int u,int v,int w) {
    44         egs.push_back(EDGE{u,v,w}) ;
    45         G_id[u].push_back(egs.size()-1) ;
    46     }
    47     void DIJ() {
    48         priority_queue<HEHE> Q ;
    49         while (!Q.empty()) Q.pop() ;
    50         HEHE tmp ;
    51         int u ;
    52         Q.push(HEHE{st,0}) ;
    53         while (!Q.empty()) {
    54             tmp = Q.top() ;
    55             Q.pop() ;
    56             u = tmp.u ;
    57             for (int i = 0 ; i < G_id[u].size() ; ++ i) {
    58                 EDGE & e = egs[G_id[u][i]] ;
    59                 if (low[e.v] > low[u] + e.w) {
    60                     low[e.v] = low[u] + e.w ;
    61                     Q.push(HEHE{e.v,low[e.v]}) ;
    62                     path[e.v] = u ;
    63                 }
    64             }
    65         }
    66     }
    67     void pathprint(int en) {
    68         int p = en ;
    69         while (p != st) {
    70             printf("%d ",p);
    71             p = path[p] ;
    72         }
    73         printf("%d 
    ",st);
    74     }
    75 
    76 }ans;
    77 
    78 int main()
    79 {
    80     int N , R , u , v , x ;
    81     while (scanf("%d%d",&N,&R) == 2 && N && R) {
    82         ans.Init(N,R,1) ;
    83         rep(i,R) {
    84             scanf("%d%d%d",&u,&v,&x) ;
    85             ans.addedge(u,v,x) ;
    86             ans.addedge(v,u,x) ;
    87         }
    88         ans.DIJ() ;
    89         printf("%d
    ",ans.low[N]) ;
    90 //        while (cin >> v) {
    91 //            ans.pathprint(v) ;
    92 //        }
    93     }
    94     return 0 ;
    95 }
    HDU2544

    Part 2. Floyd算法

      能够计算任意两点之间的最短路径,同样无法处理有负权边的情况.

    算法过程

      初始化操作 $ G[i][j] = inf $

      求解步骤 三重循环

      $ Floyd $ 算法是一种动态规划的思想 令 $ G[k][i][j] $ 表示 $ i ightarrow j $ 的路径上经过的节点编号不大于 $ k $ 的最短距离,当 $ k = N $ 时即为最短路径。现在已知 $ G[k-1][i][j] $ 考虑怎么求解 $ G[k][i][j] $

    若经过 $ k $ 节点,则 $ G[k][i][j] = G[k-1][i][k] + G[k-1][k][j] $

    若不经过 $ k $ 节点,则 $ G[k][i][j] = G[k-1][i][j] $

    而最终 $ G[k][i][j] $ 就要取其中较小值.

    可以看出这是一个动态规划的转移方程,初始状态怎么确定呢,很简单, $ G[0][i][j] = distance[i][j] $

    从转移方程可以看出 $ k $ 要放在最外层循环,否则会出错的,下面有一个样例

     


     代码如下:

      

     1 class FLOYD
     2 {
     3 public:
     4     int N , R , G[maxn][maxn] , path[maxn][maxn] ;
     5     void Init(int n,int r) {
     6          N = n , R = r ;
     7          memset(G,inf,sizeof(G)) ;
     8     }
     9     void addedge(int u,int v,int w) {
    10         G[u][v] = G[v][u] = w ;
    11     }
    12     void floyd() {
    13         for (int i = 1 ; i <= N ; ++ i) {
    14             for (int j = 1 ; j <= N ; ++ j) {
    15                 if (G[i][j] == inf) path[i][j] = -1 ;
    16                 else path[i][j] = j ;
    17             }
    18         }
    19         for (int k = 1 ; k <= N ; ++ k) {
    20             for (int i = 1 ; i <= N ; ++ i) {
    21                 for (int j = 1 ; j <= N ; ++ j) {
    22                     if (G[i][j] > G[i][k] + G[k][j]) {
    23                         G[i][j] = G[i][k] + G[k][j] ;
    24                         path[i][j] = path[i][k] ;
    25                     }
    26                 }
    27             }
    28         }
    29     }
    30     void pathprint(int st,int en) {
    31      //   printf("now is solving %d and %d
    ",st,en) ;
    32         printf("%d ",st) ;
    33         while (st != en) {
    34             printf("%d ",path[st][en]) ;
    35             st = path[st][en] ;
    36         }
    37         puts("") ;
    38     }
    39 }ans;
    FLOYD

    Part 3. Bellman_Ford算法

      这个可以计算含负权边的图.若含负环则返回 $ false $

      还能用于差分约束系统.

    算法原理 最短路径一定不会含环(无论正环负环或者零),所以最短路径的最大长度是 $ |V| - 1 $ ,构造以 $ st $ 为树根的最短路径树,则树深不超过 $ |V| - 1 $。先看下代码:

    for (int i = 1 ; i < N ; ++ i) {
        for (int j = 1 ; j <= R ; ++ j) {
            EDGE & e = edges[j] ;
            if (low[e.to] > low[e.fr] + e.di) {
                low[e.to] = low[e.fr] + e.di ;
            }
        }
    }//初始化low[i] 为无穷大,low[st] = 0
    

     当内层循环循环了一遍之后,生成了树的第一层节点,至少能够找出所有与 $ st $ 直接相连的节点的最短路,

    内层循环第二遍之后,会生成深度为二的节点,也就是和 $ st $ 之间间隔了一个节点的最短路径

    依次执行下去就会得到最优解。

    比如下图,首先初始化 $$ low[st] = 0 , low[A] = inf , low[B] = inf , low[C] = inf $$ 假设存储的边的顺序是 (st,A),(st,B),(B,A),(B,C),那么执行一次内层循环过程后就是这样的: $$ low[st] = 0 , low[A] = 10 , low[B] = 3 , low[A] = 8 , low[C] = 13 $$居然一下子求出了最短路,不过这实在是巧合,比如边的顺序是这样的话(B,C),(B,A),(st,B),(st,A),一次循环后是:$$ low[C] = inf , low[A] = inf , low[B] = 3 , low[A] = 10 $$ 这是最坏的情况,不过即使是在这样的情况下它也求得了 st 直接到各个点的距离,第二次之后是 $$ low[C] = 13 , low[A] = 8 , low[B] = 3 , low[A] = 8 $$ 这个时候得到的是 st 到各个节点的路径上有不多于一个点的情况下的最短路,第三次之后还是这样(因为已经不能松弛了)

     poj 1860

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 #include <cstring>
     5 #include <algorithm>
     6 #include <cmath>
     7 #include <vector>
     8 using namespace std ;
     9 #define rep(i,n) for (int i = 1 ; i <= n ; ++ i)
    10 #define LL long long
    11 const int maxn = 110 ;
    12 struct Node
    13 {
    14     int u ;
    15     int v ;
    16     double r ;
    17     double c ;
    18 };
    19 
    20 class Bellman_Ford
    21 {
    22 public:
    23     int N , R , st , cnt ;
    24     double low[maxn] ;
    25     Node egs[maxn<<1] ;
    26     void Init(int n,int r,int sst,double my) {
    27         N = n , R = r , st = sst ;
    28         rep(i,N) low[i] = 0.0 ;
    29         low[st] = my , cnt = 0 ;
    30     }
    31     void addedge(int u,int v,double r,double c) {
    32         egs[++cnt] = Node{u,v,r,c} ;
    33     }
    34     bool bellmanford() {
    35         bool ok ;
    36         rep(i,N-1) {
    37             ok = false ;
    38             rep(j,cnt) {
    39                 Node & e = egs[j] ;
    40                 if ((low[e.u] - e.c)*e.r > low[e.v]) {
    41                     low[e.v] = (low[e.u] - e.c)*e.r ;
    42                     ok = true ;
    43                 }
    44             }
    45             if (!ok) break ;
    46         }
    47         rep(j,cnt) {
    48             Node & e = egs[j] ;
    49             if ((low[e.u] - e.c)*e.r > low[e.v]) {
    50                 return true ;
    51             }
    52         }
    53         return false ;
    54     }
    55 }ans;
    56 
    57 int main()
    58 {
    59     double c , r , w , my ;
    60     int N , R , st , u , v;
    61   //  freopen("in.txt","r",stdin) ;
    62     while (scanf("%d%d%d%lf",&N,&R,&st,&my) == 4) {
    63         ans.Init(N,R,st,my) ;
    64         rep(i,R) {
    65             scanf("%d%d%lf%lf",&u,&v,&r,&c) ;
    66             ans.addedge(u,v,r,c) ;
    67             scanf("%lf%lf",&r,&c) ;
    68             ans.addedge(v,u,r,c) ;
    69         }
    70         if (ans.bellmanford()) puts("YES") ;
    71         else puts("NO") ;
    72     }
    73     return 0;
    74 }
    Bellman_Ford

    Part 4.spfa 算法.

      求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm,是西南交通大学段凡丁于1994年发表的。

    ---摘自百度百科

     

     在刚才的 Bellman_Ford算法中,可以看到可能会做很多没必要的松弛尝试操作,比如,第一次边的顺序里,low[B] = inf 那么所有从 B 点出发的边都不用检查的,spfa算法就是把之前松弛过的节点放入队列,再用这些点尝试松弛其他节点(比如一个点没有松弛过,那从他出去的边也无法松弛其他节点),代码大致是这样的:

    void spfa()
    {
        rep(i,N) {
            vis[i] = false , low[i] = inf ;
        }
        low[st] = 0 , vis[st] = true ;
        queue<int> Q ;
      Q.push(st) ; //while (!Q.empty()) Q.pop() ; while (!Q.empty()) { u = Q.front() ; vis[u] = false ; Q.pop() ; for (int i = h[u] ; i != -1 ; i = G[i].nxt) { v = G[i].to , w = G[i].val ; if (low[v] > low[u] + w) { low[v] = low[u] + w ; if (!vis[v]) vis[v] = true , Q.push(v) ; } } } }

      

    参考:

    http://www.cnblogs.com/hxsyl/p/3270401.html

    http://www.cnblogs.com/hxsyl/p/3248391.html#top

    http://blog.csdn.net/zhongkeli/article/details/8832946

    http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html

    http://blog.csdn.net/niushuai666/article/details/6772706

    http://nopainnogain.iteye.com/blog/1047818

    http://blog.csdn.net/xu3737284/article/details/8973615

      

    end~_~

  • 相关阅读:
    [书目20160620]自媒体时代,我们该如何做营销
    [转]Oracle Form 触发器执行顺序
    [转]在ASP.NET开发中容易忽略的2个小问题 Cookie乱码存取异常 和 iframe弹框的login跳转
    [转]菜鸟程序员之Asp.net MVC Session过期异常的处理
    [转]Asp.net MVC使用Filter解除Session, Cookie等依赖
    [转]异步、多线程、任务、并行编程之一:选择合适的多线程模型
    [转]Membership 到 .NET4.5 之 ASP.NET Identity
    [转]前后端分离开发模式下后端质量的保证 —— 单元测试
    [转]Asp.net MVC 利用PartialView 构造自定义菜单
    [转]Membership三步曲之入门篇
  • 原文地址:https://www.cnblogs.com/smile-0/p/5344090.html
Copyright © 2011-2022 走看看