图的五种最短路径算法

本文总结了图的几种最短路径算法的实现:深度或广度优先搜索算法,费罗伊德算法,迪杰斯特拉算法,Bellman-Ford 算法。

1)深度或广度优先搜索算法(解决单源最短路径)

从起点开始访问所有深度遍历路径或广度优先路径,则到达终点节点的路径有多条,取其中路径权值最短的一条则为最短路径。

下面是核心代码:


  
  
  1. void dfs(int cur,int dst){
  2. if(minpath<dst) return; //当前走过的路径大雨之前的最短路径,没有必要再走下去了
  3. if(cur==en){ //临界条件,当走到终点n
  4. if(minpath>dst){
  5. minpath=dst;
  6. return;
  7. }
  8. }
  9. for( int i= 1;i<=n;i++){
  10. if(mark[i]== 0&&edge[cur][i]!=inf&&edge[cur][i]!= 0){
  11. mark[i]= 1;
  12. dfs(i,dst+edge[cur][i]);
  13. mark[i]= 0; //需要在深度遍历返回时将访问标志置0
  14. }
  15. }
  16. return;
  17. }

例:先输入n个节点,m条边,之后输入有向图的m条边,边的前两个元素表示起点和终点,第三个值表示权值,输出1号城市到n号城市的最短距离。


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define nmax 110
  4. #define inf 999999999
  5. int minpath,n,m,en,edge[nmax][nmax],mark[nmax]; //最短路径,节点数,边数,终点,邻接矩阵,节点访问标记
  6. void dfs(int cur,int dst){
  7. if(minpath<dst) return; //当前走过的路径大雨之前的最短路径,没有必要再走下去了
  8. if(cur==en){ //临界条件,当走到终点n
  9. if(minpath>dst){
  10. minpath=dst;
  11. return;
  12. }
  13. }
  14. for( int i= 1;i<=n;i++){
  15. if(mark[i]== 0&&edge[cur][i]!=inf&&edge[cur][i]!= 0){
  16. mark[i]= 1;
  17. dfs(i,dst+edge[cur][i]);
  18. mark[i]= 0; //需要在深度遍历返回时将访问标志置0
  19. }
  20. }
  21. return;
  22. }
  23. int main ()
  24. {
  25. while( cin>>n>>m&&n!= 0){
  26. //初始化邻接矩阵
  27. for( int i= 1;i<=n;i++){
  28. for( int j= 1;j<=n;j++){
  29. edge[i][j]=inf;
  30. }
  31. edge[i][i]= 0;
  32. }
  33. int a,b;
  34. while(m--){
  35. cin>>a>>b;
  36. cin>>edge[a][b];
  37. }
  38. minpath=inf;
  39. memset(mark, 0, sizeof(mark));
  40. mark[ 1]= 1;
  41. en=n;
  42. dfs( 1, 0);
  43. cout<<minpath<< endl;
  44. }
  45. }

程序运行结果如下:


2)弗洛伊德算法(解决多源最短路径):时间复杂度o(n^3),空间复杂度o(n^2)

基本思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转.......允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短距离。即求从i号顶点到j顶点只经过前k号点的最短距离。

分析如下:1,首先构建邻接矩阵edge[n+1][n+1],假如现在只允许经过1号节点,求任意两点间的最短距离,很显然edge[i][j]=min(edge[i][j],edge[i][1]+edge[1][j]),代码如下:


  
  
  1. for( int i= 1;i<=n;i++){
  2. for( int j= 1;j<=n;j++){
  3. if(edge[i][j]>edge[i][ 1]+edge[ 1][j]){
  4. edge[i][j]=edge[i][ 1]+edge[ 1][j];
  5. }
  6. }
  7. }

2.接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短距离,在已经实现了从i号顶点到j号顶点只经过前1号点的最短路程的前提下,现在插入第2号节点,来看看能不能更新最短路径,因此只需在步骤一求得的基础上,进行edge[i][j]=min(edge[i][j],edge[i][2]+edge[2][j]);.......

3.很显然,需要n次这样的更新,表示依次插入了1号2号.......n号节点,最后求得的edge[i][j]是从i号顶点到j号顶点只经过前n号点的最短路程。因此核心代码如下:


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define inf 999999999
  4. int main ()
  5. {
  6. for( int k= 1;k<=n;k++){
  7. for( int i= 1;i<=n;i++){
  8. for( int j= 1;j<=n;j++){
  9. if(edge[k][j]<inf&&edge[i][k]<inf&&edge[i][j]>edge[i][k]+edge[k][j]){
  10. edge[i][j]=edge[i][k]+edge[k][j];
  11. }
  12. }
  13. }
  14. }
  15. }
例1:寻找最短的从商场到赛场的路线。其中商店在1号节点处,赛场在n号节点处,1~n节点中有m条线路双向连接。

/***先输入n,m,在输入m个三元组,n为路口数,m表示有几条路,其中1为商店,n为赛场,三元组分别表示起点终点,和该路径长,输出1到n的最短距离***/


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define inf 999999999
  4. #define nmax 110
  5. int n,m,edge[nmax][nmax];
  6. int main ()
  7. {
  8. int a,b;
  9. while( cin>>n>>m&&n!= 0){
  10. for( int i= 1;i<=n;i++){
  11. for( int j= 1;j<=n;j++){
  12. edge[i][j]=inf;
  13. }
  14. edge[i][i]= 0;
  15. }
  16. while(m--){
  17. cin>>a>>b;
  18. cin>>edge[a][b];
  19. edge[b][a]=edge[a][b];
  20. }
  21. for( int k= 1;k<=n;k++){
  22. for( int i= 1;i<=n;i++){
  23. for( int j= 1;j<=n;j++){
  24. if(edge[k][j]<inf&&edge[i][k]<inf&&edge[i][j]>edge[i][k]+edge[k][j]){
  25. edge[i][j]=edge[i][k]+edge[k][j];
  26. }
  27. }
  28. }
  29. }
  30. cout<<edge[ 1][n]<< endl;
  31. }
  32. }

程序运行结果如下:


3)迪杰斯特拉算法(解决单源最短路径)

基本思想:每次找到离源点(如1号节点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。

基本步骤:1,设置标记数组book[]:将所有的顶点分为两部分,已知最短路径的顶点集合P和未知最短路径的顶点集合Q,很显然最开始集合P只有源点一个顶点。book[i]为1表示在集合P中;

2,设置最短路径数组dst[]并不断更新:初始状态下,dst[i]=edge[s][i](s为源点,edge为邻接矩阵),很显然此时dst[s]=0,book[s]=1.此时,在集合Q中可选择一个离源点s最近的顶点u加入到P中。并依据以u为新的中心点,对每一条边进行松弛操作(松弛是指由顶点s-->j的途中可以经过点u,并令dst[j]=min(dst[j],dst[u]+edge[u][j])),并令book[u]=1;

3,在集合Q中再次选择一个离源点s最近的顶点v加入到P中。并依据v为新的中心点,对每一条边进行松弛操作(即dst[j]=min(dst[j],dst[v]+edge[v][j])),并令book[v]=1;

4,重复3,直至集合Q为空。

核心代码如下:


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define nmax 110
  4. #define inf 999999999
  5. /***构建所有点最短路径数组dst[],且1为源点***/
  6. int u; /***离源点最近的点***/
  7. int minx;
  8. for( int i= 1;i<=n;i++) dst[i]=edge[ 1][i];
  9. for( int i= 1;i<=n;i++) book[i]= 0;
  10. book[ 1]= 1;
  11. for( int i= 1;i<=n -1;i++){
  12. minx=inf;
  13. for( int j= 1;j<=n;j++){
  14. if(book[j]== 0&&dst[j]<minx){
  15. minx=dst[j];
  16. u=j;
  17. }
  18. }
  19. book[u]= 1;
  20. /***更新最短路径数组***/
  21. for( int k= 1;k<=n;k++){
  22. if(book[k]== 0&&dst[k]>dst[u]+edge[u][k]&&edge[u][k]<inf){
  23. dst[k]=dst[u]+edge[u][k];
  24. }
  25. }
  26. }

例1:给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s,终点t,要求输出起点到终点的最短距离及其花费,如果最短距离是有多条路线,则输出花费最少的。

输入:输入n,m,点的编号是1~n,然后是m行,每行4个数a,b,d,p,表示a和b之间有一条边,且长度为d,花费为p。最后一行是两个数s,t,起点s,终点t。n和m为0时输入结束。(1<n<=1000,0<m<100000,s!=t)

输出:输出一行,有两个数,最短距离及其花费。

分析:由于每条边有长度d和花费p,最好构建变结构体存放。

使用邻接矩阵求解:


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define nmax 110
  4. #define inf 999999999
  5. struct Edge{
  6. int len;
  7. int cost;
  8. }edge[nmax][nmax];
  9. int u,n,m,book[nmax],s,t,dst[nmax],spend[nmax];
  10. int minx;
  11. int main (){
  12. while( cin>>n>>m&&n!= 0&&m!= 0){
  13. for( int i= 1;i<=n;i++){
  14. for( int j= 1;j<=n;j++){
  15. edge[i][j].len=inf;
  16. edge[i][j].cost= 0;
  17. }
  18. edge[i][i].len= 0;
  19. }
  20. int a,b;
  21. while(m--){
  22. cin>>a>>b;
  23. cin>>edge[a][b].len>>edge[a][b].cost;
  24. edge[b][a].len=edge[a][b].len;
  25. edge[b][a].cost=edge[a][b].cost;
  26. }
  27. cin>>s>>t;
  28. for( int i= 1;i<=n;i++) {dst[i]=edge[s][i].len;spend[i]=edge[s][i].cost;}
  29. for( int i= 1;i<=n;i++) book[i]= 0;
  30. book[s]= 1;
  31. for( int i= 1;i<=n -1;i++){
  32. minx=inf;
  33. for( int j= 1;j<=n;j++){
  34. if(book[j]== 0&&dst[j]<minx){
  35. minx=dst[j];
  36. u=j;
  37. }
  38. }
  39. book[u]= 1;
  40. for( int k= 1;k<=n;k++){
  41. if(book[k]== 0&&(dst[k]>dst[u]+edge[u][k].len||(dst[k]==dst[u]+edge[u][k].len&&spend[k]>spend[u]+edge[u][k].cost))&&edge[u][k].len<inf){
  42. dst[k]=dst[u]+edge[u][k].len;
  43. spend[k]=spend[u]+edge[u][k].cost;
  44. }
  45. }
  46. }
  47. cout<<dst[t]<< ' '<<spend[t]<< endl;
  48. }
  49. }

程序运行结果如下:



4)Bellman-Ford算法(解决负权边,解决单源最短路径,前几种方法不能解决负权边)

主要思想:所有的边进行n-1轮松弛,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边。换句话说,第1轮在对所有的边进行松弛操作后,得到从1号顶点只能经过一条边到达其余各定点的最短路径长度,第2轮在对所有的边进行松弛操作后,得到从1号顶点只能经过两条边到达其余各定点的最短路径长度,........

此外,Bellman-Ford算法可以检测一个图是否含有负权回路:如果经过n-1轮松弛后任然存在dst[e[i]]>dst[s[i]]+w[i].

例1:对图中含有负权的有向图,输出从1号节点到各节点的最短距离,并判断有无负权回路。


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define nmax 1001
  4. #define inf 999999999
  5. int n,m,s[nmax],e[nmax],w[nmax],dst[nmax];
  6. int main ()
  7. {
  8. while( cin>>n>>m&&n!= 0&&m!= 0){
  9. for( int i= 1;i<=m;i++){
  10. cin>>s[i]>>e[i]>>w[i];
  11. }
  12. for( int i= 1;i<=n;i++)
  13. dst[i]=inf;
  14. dst[ 1]= 0;
  15. for( int i= 1;i<=n -1;i++){
  16. for( int j= 1;j<=m;j++){
  17. if(dst[e[j]]>dst[s[j]]+w[j]){
  18. dst[e[j]]=dst[s[j]]+w[j];
  19. }
  20. }
  21. }
  22. int flag= 0;
  23. for( int i= 1;i<=m;i++){
  24. if(dst[e[i]]>dst[s[i]]+w[i]){
  25. flag= 1;
  26. }
  27. }
  28. if(flag) cout<< "此图有负权回路"<< endl;
  29. else{
  30. for( int i= 1;i<=n;i++){
  31. if(i== 1) cout<<dst[i];
  32. else cout<< ' '<<dst[i];
  33. }
  34. cout<< endl;
  35. }
  36. }
  37. }

程序运行结果如下:


5)SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford队列优化,它是一种十分高效的最短路算法。

实现方法:建立一个队列,初始时队列里只有起始点s,在建立一个数组记录起始点s到所有点的最短路径(初始值都要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里的点去刷新起始点s到所有点的距离的距离,如果刷新成功且刷新的点不在队列中,则把该点加入到队列,重复执行直到队列为空。

此外,SPFA算法可以判断图中是否有负权欢=环,即一个点的入队次数超过N。


  
  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int n,m,len;
  4. struct egde{
  5. int to,val,next;
  6. }e[ 200100];
  7. int head[ 200100],vis[ 200100],dis[ 200100];
  8. void add(int from,int to,int val){
  9. e[len].to=to;
  10. e[len].val=val;
  11. e[len].next=head[from];
  12. head[from]=len;
  13. len++;
  14. }
  15. void spfa()
  16. {
  17. queue< int>q;
  18. q.push( 1);
  19. vis[ 1]= 1;
  20. while(!q.empty())
  21. {
  22. int t=q.front();
  23. q.pop();
  24. vis[t]= 0;
  25. for( int i=head[t];i!= -1;i=e[i].next){
  26. int s=e[i].to;
  27. if(dis[s]>dis[t]+e[i].val){
  28. dis[s]=dis[t]+e[i].val;
  29. if(vis[s]== 0){
  30. q.push(s);
  31. vis[s]= 1;
  32. }
  33. }
  34. }
  35. }
  36. }
  37. int main(){
  38. int from,to,val;
  39. while( cin>>n>>m){
  40. memset(head, -1, sizeof(head));
  41. memset(vis, 0, sizeof(vis));
  42. /* for(int i=1;i<=n;i++){
  43. dis[i]=99999999;
  44. }*/
  45. memset(dis, 0x3f, sizeof(dis));
  46. dis[ 1]= 0;len= 1;
  47. for( int i= 0;i<m;i++){
  48. cin>>from>>to>>val;
  49. add(from,to,val);
  50. }
  51. spfa();
  52. for( int i= 1;i<=n;i++){
  53. cout<<dis[i]<< " ";
  54. }
  55. cout<< endl;
  56. }
  57. }



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值