【常用算法总结——最短路径四种方法】

博客围绕最短路径问题展开,介绍了深度或广度优先搜索、弗洛伊德、迪杰斯特拉、Bellman - Ford四种算法。深度或广度优先用于解决单源最短路径;弗洛伊德解决多源最短路径;迪杰斯特拉解决单源最短路径;Bellman - Ford可解决含负权边的单源最短路径问题,还能检测负权回路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下转自https://blog.youkuaiyun.com/weixin_42060896/article/details/82216379

例题:HDU 2544

最短路
Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 89730    Accepted Submission(s): 38892


Problem Description
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?


 

Input
输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。

 

Output
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
 

Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
 

Sample Output
3
2
 

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

从起始结点开始访问所有的深度遍历路径或广度优先路径,则到达终点结点的路径有多条,取其中路径权值最短的一条则为最短路径。
给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为
源。
现在要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通
常称为单源最短路径 [1] 问题。
从起始结点开始访问所有的深度遍历路径或广度优先路径,则到达终点结点的路径有多条,取其中路
径权值最短的一条则为最短路径

下面是核心代码:

 1 //题意:求1->n的最短路径
 2 #include<iostream>
 3 #include<string.h>
 4 #define inf 99999999
 5 using namespace std;
 6 int dis[111][111];
 7 bool vis[111];
 8 int n,cnt;//n为节点数,cnt为最短长度
 9 void init(int x){
10          for(int i=0;i<=n;i++){
11                   for(int j=0;j<=n;j++)
12                            dis[i][j]=inf;
13                   dis[i][i]=0;
14                   vis[i]=0;
15          }
16 }
17 void dfs(int st,int dst)
18 {
19          if(dst>cnt)return ;//距离大于最短路径,无需遍历
20          if(st==n){//到达终点
21                   cnt=cnt>dst?dst:cnt;
22                   return;
23          }
24          for(int i=1;i<=n;i++)
25          {
26                   if(!vis[i]&&dis[st][i]!=inf&&dis[st][i]){
27                            vis[i]=1;
28                            dfs(i,dst+dis[st][i]);
29                            vis[i]=0;
30                   }
31          }
32 }
33 int main()
34 {
35          int m;
36          while(~scanf("%d%d",&n,&m)&&n&&m)
37          {
38                   int x,y,len;
39                   cnt=inf;
40                   init(n);
41                   while(m--){
42                            scanf("%d%d%d",&x,&y,&len);
43                            dis[x][y]=min(dis[x][y],len);//两点之间距离重复输入取小距离
44                            dis[y][x]=dis[x][y];
45                   }
46                   vis[1]=1;
47                   dfs(1,0);
48                   printf("%d\n",cnt);
49          }
50          return 0;
51 }
52 Sample Input 2
53 5 14
54 2 2 262
55 5 3 403
56 4 2 456
57 1 5 289
58 3 1 1000
59 2 4 217
60 2 5 536
61 2 5 415
62 2 4 880
63 3 1 179
64 3 4 972
65 5 3 2
66 1 3 491
67 4 1 872
68 0 0
69 Sample Output 2
70 181

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

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

 1 //题意:求1->n的最短路径
 2 #include<iostream>
 3 #include<string.h>
 4 #define inf 99999999
 5 using namespace std;
 6 int n,dis[111][111];
 7 void init(){
 8          for(int i=0;i<=n;i++){
 9                   for(int j=0;j<=n;j++)
10                            dis[i][j]=inf;
11                   dis[i][i]=0;
12          }
13 }
14 int main()
15 {
16          int m;
17          while(~scanf("%d%d",&n,&m)&&n&&m)
18          {
19                   init();
20                   while(m--){
21                            int x,y,len;
22                            scanf("%d%d%d",&x,&y,&len);
23                            dis[x][y]=min(dis[x][y],len);
24                            dis[y][x]=dis[x][y];
25                   }
26                   for(int k=1;k<=n;k++)//要经过的点
27                            for(int i=1;i<=n;i++)
28                                     for(int j=1;j<=n;j++)
29                                              dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
30                   printf("%d\n",dis[1][n]);//可以选任意两点之间的距离
31          }
32          return 0;
33 }
34 Sample Input 2
35 5 14
36 2 2 262
37 5 3 403
38 4 2 456
39 1 5 289
40 3 1 1000
41 2 4 217
42 2 5 536
43 2 5 415
44 2 4 880
45 3 1 179
46 3 4 972
47 5 3 2
48 1 3 491
49 4 1 872
50 0 0
51 Sample Output 2
52 181

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

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

基本步骤:1.开容器v,储存子节点、距离、花费;2、开数组dis记录起始点到各点距离;3、进行n-1次松弛操作(先找出未标记点中离起始点最近的点,标记该点,然后求出该点子节点到起始点的最短距离(优先)与最短花费);4、输出到终点的最短距离与花费;

 1 //题意:求两点之间最短路径
 2 #include<iostream>
 3 #include<string.h>
 4 #include<vector>
 5 #include<algorithm>
 6 #define N 999999999
 7 using namespace std;
 8 struct node{
 9          int er,len,cost;
10 };
11 vector<node>v[1111];
12 int main()
13 {
14          int n,m;
15          while(~scanf("%d%d",&n,&m)&&n&&m)
16          {
17                   int dis[1111],spend[1111];
18                   bool vis[1111];
19                   node tmp;
20                   int x,y;
21                   for(int i=0;i<1111;i++)
22                            v[i].clear();
23                   while(m--){
24                            scanf("%d%d%d%d",&x,&y,&tmp.len,&tmp.cost);
25                            tmp.er=x;
26                            v[y].push_back(tmp);
27                            tmp.er=y;
28                            v[x].push_back(tmp);
29                   }
30                   scanf("%d%d",&x,&y);//起点和终点
31                   for(int i=1;i<=n;i++){
32                            vis[i]=0;
33                            dis[i]=spend[i]=N;
34                   }
35                   for(int i=0;i<v[x].size();i++){
36                            dis[v[x][i].er]=v[x][i].len;
37                            spend[v[x][i].er]=v[x][i].cost;
38                   }
39                   vis[x]=1;
40                   for(int k=1;k<=n-1;k++)
41                   {
42                            int id,mi=N;
43                            for(int i=1;i<=n;i++){
44                                     if(!vis[i]&&dis[i]<mi){//查询并记录离x最近的点
45                                              id=i;mi=dis[i];
46                                     }
47                            }
48                            vis[id]=1;//标记过的点已经是最短
49                            for(int i=0;i<v[id].size();i++)
50                            {
51                                     int vv=v[id][i].er;
52                                     if(!vis[vv]&&dis[vv]>dis[id]+v[id][i].len)//未标记、直接距离大于通过id点的距离
53                                              dis[vv]=dis[id]+v[id][i].len,
54                                              spend[vv]=spend[id]+v[id][i].cost;
55                                     else if(!vis[vv]&&dis[vv]==dis[id]+v[id][i].len&&spend[vv]>spend[vv]+v[id][i].cost)//未标记、距离相等找花费更小的
56                                              spend[vv]=spend[id]+v[id][i].cost;
57                            }
58                   }
59                   printf("%d %d\n",dis[y],spend[y]);
60          }
61          return 0;
62 }
63 /*
64 3 2
65 1 2 5 6
66 2 3 4 5
67 1 3
68 3 2
69 1 3 5 6
70 2 1 3 5
71 3 2
72 9 11
73 8 11
74 */

4),Bellman-Ford算法(解决负权边,解决单源最短路径,前几种方法不能求含负权边的图)::时间复杂度O(nm),空间复杂度O(m)

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

此外,Bellman_Ford还可以检测一个图是否含有负权回路:POJ1860

 1 /*
 2 题意:有多种汇币,汇币之间可以交换,这需要手续费,当你用100A币
 3 交换B币时,A到B的汇率是29.75,手续费是0.39,那么你可以得到
 4 (100 - 0.39) * 29.75 = 2963.3975 B币。问s币的金额经过交换最终
 5 得到的s币金额数能否增加
 6 货币的交换是可以重复多次的,所以我们需要找出是否存在
 7 正权回路,且最后得到的s金额是增加的
 8 怎么找正权回路呢?(正权回路:在这一回路上,顶点的权值能不断增加即能一直进行松弛)
 9 分析:
10 反向利用Bellman-Ford算法
11 单源最短路径算法,因为题目可能存在负边,所以用Bellman Ford算法,
12 原始Bellman Ford可以用来求负环,这题需要改进一下用来求正环
13 一种货币就是图上的一个点
14 一个“兑换点”就是图上两种货币之间的一个兑换环,相当于“兑换方式”M的个数,是双边
15 唯一值得注意的是权值,当拥有货币A的数量为V时,A到A的权值为K,即没有兑换
16 而A到B的权值为(V-Cab)*Rab
17 本题是“求最大路径”,之所以被归类为“求最小路径”是因为本题题恰恰
18 与bellman-Ford算法的松弛条件相反,求的是能无限松弛的最大正权路径,
19 但是依然能够利用bellman-Ford的思想去解题。
20 因此初始化d(S)=V   而源点到其他店的距离(权值)初始化为无穷小(0),
21 当s到其他某点的距离能不断变大时,说明存在最大路径
22 */
23 #include<iostream>
24 #include<string.h>
25 #include<algorithm>
26 using namespace std;
27 struct node
28 {
29          int x,y;
30          double r,c;
31 }num[222];
32 int n,m,s,ans;
33 double v;
34 void add(int x,int y,double r,double c)
35 {
36          num[ans].x=x;
37          num[ans].y=y;
38          num[ans].r=r;
39          num[ans].c=c;
40          ans++;
41 }
42 bool bellon()
43 {
44          double dis[111];
45          for(int i=0;i<=n;i++)
46                   dis[i]=0;
47          dis[s]=v;
48          for(int j=1;j<n;j++)
49          {
50                   bool flag=0;
51                   for(int i=0;i<ans;i++){
52                            if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
53                                     dis[num[i].y]=(dis[num[i].x]-num[i].c)*num[i].r,flag=1;
54                   }
55                   if(!flag)
56                            return 0;
57          }
58          for(int i=0;i<ans;i++)
59                   if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
60                            return 1;
61          return 0;
62 }
63 int main()
64 {
65          int a,b;
66          ans=0;
67          double ra,rb,ca,cb;
68          scanf("%d%d%d%lf",&n,&m,&s,&v);
69          while(m--)
70          {
71                   scanf("%d%d%lf%lf%lf%lf",&a,&b,&ra,&ca,&rb,&cb);
72                   add(a,b,ra,ca);
73                   add(b,a,rb,cb);
74          }
75          if(bellon())
76                   printf("YES\n");
77          else
78                   printf("NO\n");
79          return 0;
80 }

 

转载于:https://www.cnblogs.com/hualian/p/11171370.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值