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 \rightarrow v $ 和 $ st $ $ \rightarrow $ $ u $ $ \rightarrow $ $ 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\n",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 \n",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\n",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 \rightarrow 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\n",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~_~