最短路算法略解

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         queue<HEHE> Q ;
49         while (!Q.empty()) Q.pop() ;
50         HEHE tmp ;
51         int u , v ;
52         Q.push(HEHE{st,0}) ;
53         while (!Q.empty()) {
54             tmp = Q.front() ;
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 }

Dijkstra

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 ;
    //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~_~

时间: 2024-11-07 11:05:28

最短路算法略解的相关文章

最短路SPFA 算法详解

最短路SPFA 算法详解 适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了. 我们约定有向加权图G不存在负权回路,即最短路径一定存在.当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点. 算法思想:我们用数组d记录每个结点的最短路径估计值,用邻接表来存储图G.我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并

最短路算法 :Bellman-ford算法 &amp; Dijkstra算法 &amp; floyd算法 &amp; SPFA算法 详解

 本人QQ :2319411771   邮箱 : [email protected] 若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接   :http://www.cnblogs.com/Yan-C/p/3916281.html . 很早就想写一下最短路的总结了,但是一直懒,就没有写,这几天又在看最短路,岁没什么长进,但还是加深了点理解. 于是就想写一个大点的总结,要写一个全的. 在本文中因为邻接表在比赛中不如前向星好写,而且前向星效率并

机器学习经典算法详解及Python实现--CART分类决策树、回归树和模型树

摘要: Classification And Regression Tree(CART)是一种很重要的机器学习算法,既可以用于创建分类树(Classification Tree),也可以用于创建回归树(Regression Tree),本文介绍了CART用于离散标签分类决策和连续特征回归时的原理.决策树创建过程分析了信息混乱度度量Gini指数.连续和离散特征的特殊处理.连续和离散特征共存时函数的特殊处理和后剪枝:用于回归时则介绍了回归树和模型树的原理.适用场景和创建过程.个人认为,回归树和模型树

机器学习经典算法详解及Python实现--元算法、AdaBoost

第一节,元算法略述 遇到罕见病例时,医院会组织专家团进行临床会诊共同分析病例以判定结果.如同专家团临床会诊一样,重大决定汇总多个人的意见往往胜过一个人的决定.机器学习中也吸取了'三个臭皮匠顶个诸葛亮'(实质上是由三个裨将顶个诸葛亮口误演化而来)的思想,这就是元算法的思想.元算法(meta-algorithm)也叫集成方法(ensemble method),通过将其他算法进行组合而形成更优的算法,组合方式包括:不同算法的集成,数据集不同部分采用不同算法分类后的集成或者同一算法在不同设置下的集成.

BM算法详解(转)

1977 年,Robert S.Boyer和J Strother Moore提出了另一种在O(n)时间复杂度内,完成字符串匹配的算法,其在绝大多数场合的性能表现,比KMP算法还要出色,下面我们就来详细了解一下这 一出色的单模式匹配算法,在此之前推荐读者读一下我的另一篇文章<KMP算法详解>,对于透彻理解BM算法大有裨益. 在讲解Boyer-Moore算法之前,我们还是要提一提KMP算法的老例子,当模式串与目标串匹配至如下位置时:  1  2  3  4  5  6  7  8  9 10 11

EM算法(3):EM算法详解

目录 EM算法(1):K-means 算法 EM算法(2):GMM训练算法 EM算法(3):EM算法详解

【啊哈!算法】算法7:Dijkstra最短路算法

上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”.例如求下图中的1号顶点到2.3.4.5.6号顶点的最短路径. <ignore_js_op> 与Floyd-Warshall算法一样这里仍然使用二维数组e来存储顶点之间边的关系,初始值如下. <ignore_js_op> 我们还需要用一个一维数组dis来存储1号顶点到其余各个顶点的初始路程,如下.

[转] KMP算法详解

转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段.    我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串).比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串.

[搜索]波特词干(Porter Streamming)提取算法详解(3)

 接上 [搜索]波特词干(Porter Streamming)提取算法详解(2) 下面分为5大步骤来使用前面提到的替换条件来进行词干提取. 左边是规则,右边是提取成功或者失败的例子(用小写字母表示). 步骤1 SSES -> SS                   caresses  ->  caress IES  -> I                          ponies    ->  poni ties      ->  ti SS   -> S