图的算法专题——最短路径

概要:

  1. Dijkstra算法
  2. Bellman-Ford算法
  3. SPFA算法
  4. Floyd算法

 


 

 

1、Dijkstra算法用于解决单源最短路径问题,严格讲是无负权图的最短路径问题。

 

邻接矩阵版

 1 const int maxv=1000;
 2 const int INF=1000000000;
 3 int n,G[maxv][maxv];
 4 int d[maxv];  //起点到各点的最短路径长度
 5 bool vis[maxv]={false};
 6 
 7 void Dijkstra(int s){ //s为起点
 8     fill(d,d+maxv,INF);
 9     d[s]=0;
10     for(int i=0;i//循环n次
11         int u=-1,MIN=INF;   //u使d[u]最小,MIN存放最小d[u]
12         for(int j=0;j){
13             if(vis[j]==false && d[j]//未收录的顶点中到起点距离最小者
14                 u=j;
15                 MIN=d[j];
16             }
17         }
18         //找不到小于INF的d[u],说明剩下的顶点和起点s不连通
19         if(u==-1) return;
20         vis[u] =true;
21         for(int v=0;v){
22             if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){
23                 d[v]=d[u]+G[u][v];
24             }
25         }
26     }
27 }

 

邻接表版

 1 struct Node{
 2     int v,dis; //v为边的目标顶点, dis为边权
 3 };
 4 vector Adj[maxv];
 5 int n,d[maxv];
 6 bool vis[maxv]={0};
 7 
 8 void Dijkstra(int s){
 9     fill(d,d+maxv,INF);
10     d[s]=0;
11     for(int i=0;i) {
12         int u=-1,MIN=INF;
13         for(int j=0;j){
14             if(d[j]false){
15                 u=j;
16                 MIN=d[j];
17             }
18         }
19         if(u==-1) return;
20         vis[u]=true;
21         for(int j=0;j){
22             int v=Adj[u][j].v;
23             if(vis[v]==false && d[u] +Adj[u][j].dis <d[v]){
24                 d[v]=d[u]+Adj[u][j].dis;
25             }
26         }
27     }
28 }

 

 若要求输出最短路径,以邻接矩阵为例:

 1 const int maxv=1000;
 2 const int INF=1000000000;
 3 int n,G[maxv][maxv];
 4 int d[maxv];  //起点到各点的最短路径长度
 5 bool vis[maxv]={false};
 6 int pre[maxv];
 7 
 8 void Dijkstra(int s){ //s为起点
 9     fill(d,d+maxv,INF);
10     for(int i=0;i11     d[s]=0;
12     for(int i=0;i//循环n次
13         int u=-1,MIN=INF;   //u使d[u]最小,MIN存放最小d[u]
14         for(int j=0;j){
15             if(vis[j]==false && d[j]//未收录的顶点中到起点距离最小者
16                 u=j;
17                 MIN=d[j];
18             }
19         }
20         //找不到小于INF的d[u],说明剩下的顶点和起点s不连通
21         if(u==-1) return;
22         vis[u] =true;
23         for(int v=0;v){
24             if( G[u][v]!=INF && vis[v]==false && d[u]+G[u][v] < d[v]){
25                 d[v]=d[u]+G[u][v];
26                 pre[v]=u;
27             }
28         }
29     }
30 }
31 
32 void DFS(int s,int v){  //从终点开始递归
33     if(v==s){ //如果当前已经到达起点,输出起点并返回
34         printf("%d\n",s);
35     }
36     DFS(s,pre[v]);
37     printf("%d\n",v);
38 }

 

另外还有一种情况,如果某个结点存在多个前驱结点,那上面这种pre数组的方法就不再适用,改成vector即可:

 1 const int maxv=1010;
 2 const int INF=1000000000;
 3 vector<int> pre[maxv];
 4 void Dijkstra(int s){
 5     fill(d,d+maxv,INF);
 6     d[s]=0;
 7     for(int i=0;i){
 8         int u=-1,MIN=INF;
 9         for(int j=0;j){
10             if(vis[j]==false && d[j]<MIN){
11                 u=j;
12                 MIN=d[j];
13             }
14         }
15         if(u==-1) return;
16         vis[u]=true;
17         for(int v=0;v){
18             if(vis[v]==false &&G[u][v]!=INF){
19                 if(d[u]+G[u][v]<d[v]){
20                     d[v]=d[u]+G[u][v];
21                     pre[v].clear(); 
22                     pre[v].push_back(u);
23                 }
24                 else if(d[u]+G[u][v]==d[v]){
25                     pre[v].push_back(u);
26                 }
27             }
28         }
29     }
30 }

 

 当访问的结点是路径起点st时(边界),此时tempPath里存了整条路径(倒序),这时需要计算第二标尺value的值,并与optValue比较,若更优则更新optValue并把path覆盖。

 1 const int maxv=1010;
 2 const int INF=1000000000;
 3 int optValue;
 4 vector<int> path,tempPath;
 5 vector<int> pre[maxv];
 6 
 7 void Dijkstra(int s){
 8     fill(d,d+maxv,INF);
 9     d[s]=0;
10     for(int i=0;i){
11         int u=-1,MIN=INF;
12         for(int j=0;j){
13             if(vis[j]==false && d[j]<MIN){
14                 u=j;
15                 MIN=d[j];
16             }
17         }
18         if(u==-1) return;
19         vis[u]=true;
20         for(int v=0;v){
21             if(vis[v]==false &&G[u][v]!=INF){
22                 if(d[u]+G[u][v]<d[v]){
23                     d[v]=d[u]+G[u][v];
24                     pre[v].clear();
25                     pre[v].push_back(u);
26                 }
27                 else if(d[u]+G[u][v]==d[v]){
28                     pre[v].push_back(u);
29                 }
30             }
31         }
32     }
33 }
34 
35 void DFS(int v){ //v为当前访问结点
36     if(v==st){
37         tempPath.push_back(v);
38         int value;
39         (计算路径的value)
40         if(value优于optValue){
41             path=tempPath;
42             optValue=value;
43         }
44         tempPath.pop_back(); //将刚加入的结点删除
45         return;
46     }
47     tempPath.push_back(v);
48     for(int i=0;i){
49         DFS(pre[v][i]);
50     }
51     tempPath.pop_back();
52 }

 

 

除此之外,还会碰到第二标尺,常见有以下三种:(具体代码见晴神算法笔记,写的很清楚)

  • 新增边权(如增加开销)
  • 新增点权(如收集到的物资)
  • 求最短路径条数

 

 


 

 2、Bellman-Ford 算法

算法流程:

(1)初始化:将除起点s外所有顶点的距离数组置无穷大 d[v] = INF, d[s] = 0 
(2)迭代:遍历图中的每条边,对边的两个顶点分别进行一次松弛操作,直到没有点能被再次松弛 
(3)判断负圈:如果迭代超过V-1次,则存在负圈

 算法优点:

(1)可以检测负环,最坏情况下是进行n-1轮操作,若超过n-1轮后还有松弛说明有负环

算法缺点:

(1)在无负环的情况下,效率比Dijkstra低

 

 1 #include 
 2 #include 
 3 #include 
 4 #include 
 5 #include <set>
 6 #include 
 7 #include 
 8 #include 
 9 using namespace std;
10 const int MAXV=1000;
11 const int INF=1000000000;
12 struct Node{
13     int v,dis;  //v为邻接边的目标顶点,dis为邻接边的边权 
14 };
15 vector Adj[MAXV];   //图G的邻接表 
16 int n;//顶点数
17 int d[MAXV];//起点到达各点的最短路径长度
18 bool flag;//用于优化 
19 
20 bool Bellman(int s){
21     fill(d,d+MAXV,INF);
22     d[s]=0;
23     
24     for(int i=0;i1;i++){   //进行n-1轮
25         flag=false; //标记为假 
26         for(int u=0;u//枚举每个边进行松弛 
27             for(int j=0;j//松弛操作 
28                 int v=Adj[u][j].v;
29                 int dis=Adj[u][j].dis;
30                 if(d[u]+dis<d[v]){
31                     d[v]=d[u]+dis;
32                     flag=true;  //松弛成功则标记为真 
33                 }
34             }
35         }
36         if(flag==false){ //优化:若所有的边i的循环中没有松弛成功的,break 
37             break;
38         }
39     }
40     //进行n-1轮操作后如果还能松弛,说明存在负环 (可省略) 
41     for(int u=0;u){
42         for(int j=0;j){
43             int v=Adj[u][j].v;
44             int dis=Adj[u][j].dis;
45             if(d[u]+dis<d[v]){
46                 return false;
47             }
48         }
49     }
50     return true;
51 } 

 

 

 

 3、SPFA算法

SPFA算法是在Bellman-ford算法的基础上进行改进,依据是每次松弛只会影响被松弛结点的相邻结点,于是将那些顶点加入队列,一直松弛到队列为空。

 1 //bellman算法的改进SPFA
 2 //由于bellman算法的每轮操作都需要操作所有的边,但只有当u的d[u]改变时,才会改变他的邻接点v的d[v]。 
 3 //设立一个队列保存待优化的节点,
 4 //优化时每次取出队首节点u,并用u点当前的最短路径估计值对离开u点所指向的节点v进行松弛操作,
 5 //且v点不在当前的队列中,就将v放入队尾,直到队列空 
 6 
 7 #include 
 8 #include 
 9 #include 
10 #include 
11 #include <set>
12 #include 
13 #include 
14 #include 
15 using namespace std;
16 struct Node{
17     int v,dis;  //v为邻接边的目标顶点,dis为邻接边的边权 
18 };
19 const int MAXV=510;
20 const int INF=1000000000;
21 vector Adj[MAXV];
22 int n,d[MAXV],num[MAXV];
23 bool inq[MAXV];
24 
25 bool SPFA(int s){
26     memset(inq,false,sizeof(inq));
27     memset(num,0,sizeof(num));
28     fill(d,d+MAXV,INF);
29     
30     queue<int> Q;
31     Q.push(s);
32     inq[s]=true;
33     num[s]++;   //源点入队次数加1
34     d[s]=0;
35     //主体部分
36     while(!Q.empty()){
37         int u=Q.front();//队首顶点编号为u 
38         Q.pop();  
39         inq[u]=false;//设置u不在队列中
40         //遍历u的所有邻接边v
41         for(int j=0;j){   
42             int v=Adj[u][j].v;
43             int dis=Adj[u][j].dis;
44             if(d[u]+dis<d[v]){
45                 d[v]=d[u]+dis;
46                 if(!inq[v]){  //v不在当前队列,v入队 
47                     Q.push(v);
48                     inq[v]=true;
49                     num[v]++;
50                     if(num[v]>=n) return false;   //若v入队次数大于n-1说明有负环 
51                 }
52             }
53         } 
54         
55     } 
56 }

 

 

4、Floyd算法

算法流程:

枚举结点k(1到n),以k为中介点,枚举所有顶点对 i 和 j 进行松弛。

 即 if  (dis[i][k]+dis[k][j]

 

 1 //floyd算法:解决全源最短路问题 n^3复杂度,n限制约为200以内 
 2  #include
 3  #include
 4  using namespace std;
 5  const int INF=1000000000;
 6  const int MAXV=200;
 7  int n,m;//n为顶点数,m为边数
 8  int dis[MAXV][MAXV];
 9  
10 void Floyd(){
11      for(int k=0;k//顶点k为中介点,枚举所有顶点对i,j 
12          for(int i=0;i){
13              for(int j=0;j){
14                  if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j]){
15                      dis[i][j]=dis[i][k]+dis[k][j];
16                  }    
17             }
18         }
19     }
20 } 
21 
22 int main(){
23     int u,v,w;
24     fill(dis[0],dis[0]+MAXV*MAXV,INF);
25     scanf("%d%d",&n,&m);
26     for(int i=0;i){
27         dis[i][i]=0;//初始化 
28     }
29     for(int i=0;i){
30         scanf("%d%d%d",&u,&v,&w);
31         dis[u][v]=w;  //有向图输入为例 
32     }
33     Floyd();
34     for(int i=0;i){
35         for(int j=0;j){
36             printf("%d ",dis[i][j]);
37         }
38         printf("\n");
39     }
40     return 0;
41 }

 

转载于:https://www.cnblogs.com/Mered1th/p/10418946.html

你可能感兴趣的:(图的算法专题——最短路径)