最短路算法

例题:FroggerPOJ - 2253  链接 https://vjudge.net/problem/POJ-2253

1. Floyd算法

算法思想:用mp[i][j]来表示i到j之间的距离,先对mp数组初始化,然后根据输入录入已知的i和j之间的距离,然后三重循环,第一重用k表示i和j之外的数,第二重为i,第三重为j。改变的思路是如果i到j之间的距离比i到k加上k到j之间的距离,则改变大小。时间复杂度为O(n^3).适合n比较小的题。

代码:

 1 #include <cstdio>
 2 #include <fstream>
 3 #include <algorithm>
 4 #include <cmath>
 5 #include <deque>
 6 #include <vector>
 7 #include <queue>
 8 #include <string>
 9 #include <cstring>
10 #include <map>
11 #include <stack>
12 #include <set>
13 #include <sstream>
14 #include <iostream>
15 #define mod 998244353
16 #define eps 1e-6
17 #define ll long long
18 #define INF 0x3f3f3f3f
19 using namespace std;
20 
21 //ma用来存放两个石头之间的距离
22 double ma[210][210];
23 int main()
24 {
25     //x,y数组存放地点
26     double x[210],y[210];
27     int n,ans=1;
28     //n为0时退出
29     while(scanf("%d",&n)&&n)
30     {
31         int a,b;
32         for(int i=1;i<=n;i++)
33         {
34             scanf("%lf %lf",&x[i],&y[i]);
35         }
36         //初始化ma数组为0,是为了保证求的是最大距离中的小距离
37         memset(ma,0,sizeof(ma));
38         for(int i=i=1;i<=n;i++)
39         {
40             for(int j=1;j<=n;j++)
41             {
42                 //录入两个石头之间的距离
43                 ma[i][j]=ma[j][i]=sqrt(pow(x[i]-x[j],2.0)+pow(y[i]-y[j],2.0));
44             }
45         }
46         //Floyd算法核心
47         for(int k=1;k<=n;k++)
48         {
49             for(int i=1;i<=n;i++)
50             {
51                 for(int j=1;j<=n;j++)
52                 {
53                     //许多通路中最大距离中的最小距离
54                     ma[i][j]=min(ma[i][j],max(ma[i][k],ma[k][j]));
55                 }
56             }
57         }
58         printf("Scenario #%d\nFrog Distance = %.3lf\n\n",ans++,ma[1][2]);
59     }
60 
61 }

 

2.Digkstra算法

  迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

  问题描述:一个有权图,Dijkstra算法可以计算任意节点到其他节点的最短路径

算法思路
  1. 指定一个节点,例如我们要计算 'A' 到其他节点的最短路径
  2. 引入两个集合(S、U),S集合包含已求出的最短路径的点(以及相应的最短长度),U集合包含未求出最短路径的点(以及A到该点的路径,注意 如上图所示,A->C由于没有直接相连 初始时为∞
  3. 初始化两个集合,S集合初始时 只有当前要计算的节点,A->A = 0
    U集合初始时为 A->B = 4, A->C = ∞, A->D = 2, A->E = ∞敲黑板!!!接下来要进行核心两步骤了
  4. 从U集合中找出路径最短的点,加入S集合,例如 A->D = 2
  5. 更新U集合路径,if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 则更新U
  6. 循环执行 4、5 两步骤,直至遍历结束,得到A 到其他节点的最短路径
算法图解
1.选定A节点并初始化,如上述步骤3所示
 
 

2.执行上述 4、5两步骤,找出U集合中路径最短的节点D 加入S集合,并根据条件 if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 来更新U集合

 
 

 

3.这时候 A->B, A->C 都为3,没关系。其实这时候他俩都是最短距离,如果从算法逻辑来讲的话,会先取到B点。而这个时候 if 条件变成了 if ( 'B 到 C,E 的距离' + 'AB 距离' < 'A 到 C,E 的距离' ) ,如图所示这时候A->B距离 其实为 A->D->B

 
 

 

  1. 思路就是这样,往后就是大同小异了
 
 
  1. 算法结束
 
 
 
代码:
 1 #include <cstdio>
 2 #include <fstream>
 3 #include <algorithm>
 4 #include <cmath>
 5 #include <deque>
 6 #include <vector>
 7 #include <queue>
 8 #include <string>
 9 #include <cstring>
10 #include <map>
11 #include <stack>
12 #include <set>
13 #include <sstream>
14 #include <iostream>
15 #define mod 998244353
16 #define eps 1e-6
17 #define ll long long
18 #define INF 0x3f3f3f3f
19 using namespace std;
20 
21 //dis用来存放到1之间的距离
22 double dis[310];
23 //ma用来存放两个石头之间的距离
24 double ma[210][210];
25 void dijkstra(int m)
26 {
27     int i;
28     //vis数组标记已经找过的最短路
29     bool vis[1010];
30     memset(vis,0,sizeof(vis));
31     for(int i=1;i<=m;i++)
32     {
33         dis[i]=INF;
34     }
35     dis[1]=0;
36     for(int i=1;i<=m;i++)
37     {
38         //mi记录当前到1距离最小的值
39         double mi=INF;
40         //k表示到1距离最小的点
41         int k=1;
42         for(int j=1;j<=m;j++)
43         {
44             //当未标记这个点,并且这个点到1的距离时最小的时侯成立,
45             if(!vis[j]&&mi>dis[j])
46             {
47                 mi=dis[j];
48                 k=j;
49             }
50         }
51         //已找到1到k点的最小值,所以标记这个点
52         vis[k]=1;
53         for(int j=1;j<=m;j++)
54         {
55             //所有通路中最大距离中的最小数
56             dis[j]=min(dis[j],max(dis[k],ma[k][j]));
57         }
58     }
59 }
60 
61 int main()
62 {
63     //x,y数组存放地点
64     double x[210],y[210];
65     int n,ans=1;
66     //n为0时退出
67     while(scanf("%d",&n)&&n)
68     {
69         int a,b;
70         for(int i=1;i<=n;i++)
71         {
72             scanf("%lf %lf",&x[i],&y[i]);
73         }
74         //初始化ma数组为0,是为了保证求的是最大距离中的小距离
75         memset(ma,0,sizeof(ma));
76         for(int i=i=1;i<=n;i++)
77         {
78             for(int j=1;j<=n;j++)
79             {
80                 //录入两个石头之间的距离
81                 ma[i][j]=ma[j][i]=sqrt(pow(x[i]-x[j],2.0)+pow(y[i]-y[j],2.0));
82             }
83         }
84         dijkstra(n);
85         printf("Scenario #%d\nFrog Distance = %.3lf\n\n",ans++,dis[2]);
86     }
87 
88 }

 

 3.SPFA算法

算法优点

        1.时间复杂度比普通的Dijkstra和Ford

        2.能够计算负权图问题。

        3.能够判断是否有负环 (即:每跑一圈,路径会减小,所以会一直循环跑下去)。

 

算法思想:

        我们用数组记录每个结点的最短路径估计值,用邻接表来存储图G。

        我们采取的方法是动态逼近法:

                1.设立一个先进先出的队列用来保存待优化的结点。

                2.优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。

                3.这样不断从队列中取出结点来进行松弛操作,直至队列空为止

 

期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

 

实现方法:

  1.存入图。可以使用链式前向星或者vocter

        2.开一个队列,先将开始的节点放入。

        3.每次从队列中取出一个节点X,遍历与X相通的Y节点,查询比对  Y的长度 和 X的长度+ X与Y的长度

            如果X的长度+ X与Y的长度 Y的长度,说明需要更新操作。

                    1).存入最短路。

                    2).由于改变了原有的长度,所以需要往后更新,与这个节点相连的最短路。(即:判断下是否在队列,在就不用重复,不在就加入队列,等待更新)。

                    3).在这期间可以记录这个节点的进队次数,判断是否存在负环。

        4.直到队空。

 

判断有无负环:如果某个点进入队列的次数超过N次则存在负环

 

模拟过程:

 

 

首先建立起始点a到其余各点的最短路径表格

                                  

首先源点a入队,当队列非空时:

        1、队首元素(a)出队,对以a为起始点的所有边的终点依次进行松弛操作(此处有b,c,d三个点),此时路径表格状态为:

                                  

 

在松弛时三个点的最短路径估值变小了,而这些点队列中都没有出现,这些点需要入队,此时,队列中新入队了三个结点b,c,d

   2,队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e点),此时路径表格状态为:

                                 

 

在最短路径表中,e的最短路径估值也变小了,e在队列中不存在,因此e也要入队,此时队列中的元素为c,d,e

  3,队首元素c点出队,对以c为起始点的所有边的终点依次进行松弛操作(此处有e,f两个点),此时路径表格状态为:

                                 

 

在最短路径表中,e,f的最短路径估值变小了,e在队列中存在,f不存在。因此e不用入队了,f要入队,此时队列中的元素为d,e,f

   4,队首元素d点出队,对以d为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

 

                               

在最短路径表中,g的最短路径估值没有变小(松弛不成功),没有新结点入队,队列中元素为f,g

  5,队首元素f点出队,对以f为起始点的所有边的终点依次进行松弛操作(此处有d,e,g三个点),此时路径表格状态为:

                               

 

在最短路径表中,e,g的最短路径估值又变小,队列中无e点,e入队,队列中存在g这个点,g不用入队,此时队列中元素为g,e

  6,队首元素g点出队,对以g为起始点的所有边的终点依次进行松弛操作(此处只有b点),此时路径表格状态为:

                           

 

在最短路径表中,b的最短路径估值又变小,队列中无b点,b入队,此时队列中元素为e,

  7,b队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

 

                          

 

在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b

  8,队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:

                         

 

在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了

最终a到g的最短路径为14

例题:

https://www.cnblogs.com/mzchuan/p/11521348.html

代码:

 1 #include <cstdio>
 2 #include <fstream>
 3 #include <algorithm>
 4 #include <cmath>
 5 #include <deque>
 6 #include <vector>
 7 #include <queue>
 8 #include <string>
 9 #include <cstring>
10 #include <map>
11 #include <stack>
12 #include <set>
13 #include <sstream>
14 #include <iostream>
15 #define mod 998244353
16 #define eps 1e-6
17 #define ll long long
18 #define INF 0x3f3f3f3f
19 using namespace std;
20 const int maxn=150010;
21 //n,m代表点数和边数
22 int n,m;
23 //x代表与之相连的点,y代表边的价值,next代表当前边起点的位置
24 struct node
25 {
26     int x,y,next;
27 };
28 //no数组存放边的数据
29 node no[maxn];
30 //head存放每条边的位置,ans表示第几条边
31 int head[maxn],ans;
32 //dis表示起点到其他边的最短长度
33 int dis[30010];
34 //vis表示此点是否已是最短路
35 bool vis[30010];
36 void spfa()
37 {
38     //stackbiqueue更节约时间,
39     stack<int> qu;
40     qu.push(1);
41     //初始化
42     memset(dis,INF,sizeof(dis));
43     memset(vis,0,sizeof(vis));
44     vis[1]=1;
45     dis[1]=0;
46     //为空时退出
47     while(!qu.empty())
48     {
49         int s=qu.top();
50         qu.pop();
51         //由于当钱点数据一边,所以要更新与之相连的所有点直接按的距离
52         vis[s]=0;
53         //从当前点开始直到与之相连的点
54         for(int i=head[s];i!=-1;i=no[i].next)
55         {
56             //en表示与起点相连的点
57             int en = no[i].x;
58             //更新最小操作
59             if(dis[en]>dis[s]+no[i].y)
60             {
61                 dis[en]=dis[s]+no[i].y;
62                 if(!vis[en])
63                 {
64                     vis[en]=1;
65                     qu.push(en);
66                 }
67             }
68         }
69     }
70 }
71 
72 int main()
73 {
74     ans=0;
75     scanf("%d %d",&n,&m);
76     int a,b,c;
77     //初始化
78     memset(head,-1,sizeof(head));
79     for(int i=0;i<m;i++)
80     {
81         scanf("%d %d %d",&a,&b,&c);
82         //存放边
83         no[ans].x=b;
84         no[ans].y=c;
85         no[ans].next=head[a];
86         head[a]=ans++;
87     }
88     spfa();
89     printf("%d\n",dis[n]);
90 }

 

 

 4.Bellman-Ford算法

算法作用:(主要用来求是否有正负环)

算法思想:Bellman-Ford 算法计算最短路径的过程中,使用了上述的松弛函数,通过对路径的不断松弛,来逐渐获取最短路径。

Bellman-Ford 算法可以检测带权有向图中是否存在负权回路,由松弛函数可知正常情况下,如果图中不存在负权回路,那么即使在最坏情况下,也只需要执行 |V|-1次迭代松弛,即可获得从起点到各顶点的最短路径。

若图中存在负权回路,当回路较小时,例如顶点自身或者两个顶点之间的负权回路,则在 |V|-1 次迭代过程中,可能多次通过了该负权回路;若回路较大,例如从起点出发,串联所有顶点最后回到起点,即通过 |V|-1 条边构成一个圆形,如下图所示。则 |V|-1 次迭代过程中,可能一次也不会通过该负权回路,但是当再执行一次迭代松弛,即可将 d[s] 值更新为负值,所以可以多执行一次迭代,通过判断是否更新从起点到某个顶点的最短路径权值,来判断图中是否存在负权回路。

 
例题:https://www.cnblogs.com/mzchuan/p/11490895.html

代码:

  1 #include <cstdio>
  2 #include <fstream>
  3 #include <algorithm>
  4 #include <cmath>
  5 #include <deque>
  6 #include <vector>
  7 #include <queue>
  8 #include <string>
  9 #include <cstring>
 10 #include <map>
 11 #include <stack>
 12 #include <set>
 13 #include <sstream>
 14 #include <iostream>
 15 #define mod 998244353
 16 #define eps 1e-6
 17 #define ll long long
 18 #define INF 0x3f3f3f3f
 19 using namespace std;
 20 
 21 //用于存放货币的转换
 22 struct node
 23 {
 24     //x源来的钱的种类,b转换后钱的种类
 25     int x,y;
 26     //s表示汇率
 27     double s;
 28 };
 29 //ve表示有多少个汇率
 30 vector<node> ve;
 31 //mp用于给货币种类编号
 32 map<string,int> mp;
 33 //dis表示起点转换成其他点后的钱数
 34 double dis[35];
 35 //bellman用于判断是否有正环
 36 //n表示有了n个点,v表示起点
 37 bool bellman(int n,int v)
 38 {
 39     //初始钱数为0;
 40     for(int i=1;i<=n;i++)
 41     {
 42         dis[i]=0;
 43     }
 44     //将起点的钱数设为1,为方便计算
 45     dis[v]=1;
 46     //n-1次遍历
 47     for(int i=1;i<n;i++)
 48     {
 49         //对每个汇率进行遍历
 50         for(int j=0;j<ve.size();j++)
 51         {
 52             int a=ve[j].x;
 53             int b=ve[j].y;
 54             //更新b之间的钱数
 55             if(dis[b]<dis[a]*ve[j].s)
 56             {
 57                 dis[b]=dis[a]*ve[j].s;
 58             }
 59         }
 60     }
 61     //在进行一次遍历,判断是否有正环
 62     for(int j=0;j<ve.size();j++)
 63     {
 64         int a=ve[j].x;
 65         int b=ve[j].y;
 66         //如果钱还能增加,则有正环
 67         if(dis[b]<dis[a]*ve[j].s)
 68         {
 69             return true;
 70         }
 71     }
 72     return false;
 73 }
 74 int main()
 75 {
 76     int n,ans=1;
 77     while(scanf("%d",&n)&&n!=0)
 78     {
 79         string str;
 80         for(int i=1;i<=n;i++)
 81         {
 82             cin>>str;
 83             //对钱的种类进行标记
 84             mp[str]=i;
 85         }
 86         int m;
 87         scanf("%d",&m);
 88         string a,b;
 89         node no;
 90         for(int i=0;i<m;i++)
 91         {
 92             cin>>a>>no.s>>b;
 93             no.x=mp[a];
 94             no.y=mp[b];
 95             //记录汇率
 96             ve.push_back(no);
 97         }
 98         printf("Case %d: ",ans++);
 99         //枚举所有的起点
100         for(int i=1;i<=n;i++)
101         {
102             //判断有正环
103             if(bellman(n,i))
104             {
105                 printf("Yes\n");
106                 break;
107             }//如果到最后一个起点后海没有正环,则表示财富无法增加
108             else if(i==n)
109             {
110                 printf("No\n");
111             }
112         }
113         //清除STL容器的内存
114         ve.clear();
115         mp.clear();
116     }
117 }

 

转载于:https://www.cnblogs.com/mzchuan/p/11468783.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值