康复计划之最短路

最短路问题:给你一张图(n个点,m条边),每条边有一个距离。问从一个点到另一个点的最短距离。

 

最短路主要关注两种算法: Dijkstra  O(n^2) 和 SPFA O(n*m)  (都是最坏复杂度) (其实SPFA几乎无法达到最坏复杂度)

 

Dijkstra:

 

适用范围(局限性):不存在负权边

 

思路:

我们取两个集合,一个集合M中为已确定最短距离的点集,另一个集合N为还没有确定最短距离的点集。

我们用集合M中的所有点去更新N中的点的距离,那么集合N中距离最小的点就可以确定该距离为最小距离了。

最初集合M中只有出发点,逐渐向外扩展。

 

对于思路中的第二行,我们予以证明:

假设用集合M中的点更新N中的点的距离后,N中的最小距离点o不能被确定为最短路径,那么N中必须存在一个点p,使得M->p->o为o点最短路径,即s(M->p->o) < s(M->o)。而s(M->p) > s(M->o) 且 s(p->o) > 0(不存在负权边),所以s(M->p->o) < s(M->o)不成立。推出矛盾。

故第二行得证。

 

然后我们考虑代码实现。

最容易想到的做法为每往集合M中加入一个元素x,更新由x可扩展到的点的最短距离。然后遍历一遍所有点,找到位于集合N中且距离最小的点,把它加入集合M中...

如此循环。确定复杂度O(n^2)

 

我们考虑一个可以优化的部分:遍历一遍所有点,找到位于集合N中且距离最小的点,把它加入集合M中。

我们只需要找到一个点,但我们遍历了所有点,这是不是没有必要呢?

我们可以用一个小根堆,去储存集合N中的点的距离,那么我们每次寻找最小距离的点的复杂度就可以降低到logn。

于是Dijkstra的复杂度就降低到了O(m+nlogn) 

(其实未优化Dijkstra的复杂度为O(m+n^2),由于 m <= n^2,故写为O(n^2) )

给个模板题:click here

Dijkstra:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<queue>
 6 using namespace std ;
 7 const int N = 100000 + 10 ;
 8 const int M = 100000 + 10 ;
 9 const int INF = 0x7ffffff ;
10 
11 inline int read() {
12     int k = 0, f = 1 ; char c = getchar() ;
13     for( ; !isdigit(c) ; c = getchar())
14       if(c == '-') f = -1 ;
15     for( ; isdigit(c) ; c = getchar())
16       k = k*10 + c-'0' ;
17     return k*f ;
18 }
19 
20 struct Edge {
21     int to, nex, val ;
22 }e[N] ;
23 int n, m, cnt = 0 ;  int head[N], dis[N] ;  
24 bool vis[N] ;
25 
26 struct HeapNode {
27     int d, u ;
28     bool operator <  (const HeapNode& rhs) const {
29         return d > rhs.d ;
30     }
31 };
32 priority_queue<HeapNode>q ;
33 
34 inline void add_edge(int x,int y,int z) {
35     e[++cnt].nex = head[x] ; head[x] = cnt ; e[cnt].to = y ; e[cnt].val = z ;
36 }
37 
38 inline void djsk() {
39     dis[1] = 0 ;
40     for(int i=2;i<=n;i++) dis[i] = INF ;
41     memset(vis,0,sizeof(vis)) ;
42     q.push((HeapNode){0,1}) ;
43     while(!q.empty()) { 
44         HeapNode xx = q.top() ; q.pop() ;
45         int x = xx.u ;
46         if(vis[x]) continue ;  vis[x] = 1 ;
47         for(int i=head[x];i;i=e[i].nex) {
48             int y = e[i].to ;  if(vis[y]) continue ;
49             if(dis[y] > dis[x]+e[i].val) {
50                 dis[y] = dis[x] + e[i].val ;
51                 q.push((HeapNode){dis[y],y}) ;
52             }
53         }
54     }
55 }
56 
57 int main() {
58     n = read(), m = read() ;
59     for(int i=1;i<=m;i++) {
60         int x, y, z ;  x = read(), y = read(), z = read() ;
61         add_edge(x,y,z) ;
62     }
63     djsk() ;
64     printf("%d\n",dis[n]) ;
65     return 0 ;
66 }

 

SPFA:

适用范围:不存在负权环 (可以存在负权边) (可以检验图中是否存在负权环)

其实SPFA是Bellman-Ford算法的优化,所以我们先介绍Bellman-Ford算法。

 

Bellman-Ford:

思路:

采用bfs的方法一层一层往外扩展。

每扩展一层,用该扩展层去更新更下面一层,并对于可更新的点,将其加入下一层要扩展的集合中。

第零层为出发点。

最坏复杂度:O(n*m)    (其实难以达到)

 

SPFA:

用一个先进先出队列储存可更新的点。

每次从队首取出一个点x,用x去松弛其它点,遇到能松弛的点x,就将x加入队尾。

直至队列为空。

一个小优化:

如果dis[y] < dis[q.front()] 就将其加入队首,而不是队尾。

SPFA代码(双向队列优化):

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<queue>
 6 using namespace std ;
 7 const int N = 100000 + 10 ;
 8 const int M = 100000 + 10 ; 
 9 const int INF = 0x7ffffff ;
10 
11 inline int read() {
12     int k = 0, f = 1 ; char c = getchar() ;
13     for( ; !isdigit(c) ; c = getchar())
14       if(c == '-') f = -1 ;
15     for( ; isdigit(c) ; c = getchar())
16       k = k*10 + c-'0' ;
17     return k*f ;
18 }
19 
20 struct Edge {
21     int to, nex, cos ;
22 }e[M] ;
23 
24 int n, m ;  int head[N], dis[N] ;
25 bool inq[N] ;
26 deque<int>q ;
27 
28 inline void add_edge(int x,int y,int z) {
29     static int cnt = 0 ;
30     e[++cnt].to = y, e[cnt].nex = head[x], head[x] = cnt, e[cnt].cos = z ;
31 }
32 
33 int main() {
34     int n, m ;
35     n = read(), m = read() ;
36     for(int i=1;i<=m;i++) {
37         int x = read(), y = read(), z = read() ;
38         add_edge(x,y,z) ;
39     }
40     for(int i=2;i<=n;i++) dis[i] = INF ;
41     q.push_back(1) ; dis[1] = 0 ;
42     while(!q.empty()) {
43         int x = q.front() ; q.pop_front() ; inq[x] = 0 ;
44         for(int i=head[x];i;i=e[i].nex) {
45             int y = e[i].to ;
46             if(dis[y] > dis[x]+e[i].cos) {
47                 dis[y] = dis[x]+e[i].cos ;
48                 if(!inq[y]) {
49                     if(!q.empty() && dis[y] < dis[q.front()]) q.push_front(y) ;
50                     else q.push_back(y) ;
51                     inq[y] = 1 ;
52                 }
53             }
54         }
55     }
56     printf("%d",dis[n]) ;
57     return 0 ;
58 }

 

posted @ 2019-09-30 15:30  zubizakeli  阅读(132)  评论(0编辑  收藏  举报