康复计划之最短路

Posted zub23333

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了康复计划之最短路相关的知识,希望对你有一定的参考价值。

最短路问题:给你一张图(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 

 

以上是关于康复计划之最短路的主要内容,如果未能解决你的问题,请参考以下文章

leetcode之最短路刷题总结1

最短路径之最短路径问题

图论之最短路径floyd算法

中考数学压轴题系列之最短路径问题

NLP之最短路径分词

贪心算法之最短路径(Dijkstra算法)