最短路算法
例题: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算法可以计算任意节点到其他节点的最短路径
算法思路
- 指定一个节点,例如我们要计算 'A' 到其他节点的最短路径
- 引入两个集合(S、U),S集合包含已求出的最短路径的点(以及相应的最短长度),U集合包含未求出最短路径的点(以及A到该点的路径,注意 如上图所示,
A->C
由于没有直接相连 初始时为∞) - 初始化两个集合,S集合初始时 只有当前要计算的节点,
A->A = 0
,
U集合初始时为A->B = 4, A->C = ∞, A->D = 2, A->E = ∞
,敲黑板!!!接下来要进行核心两步骤了 - 从U集合中找出路径最短的点,加入S集合,例如
A->D = 2
- 更新U集合路径,
if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' )
则更新U - 循环执行 4、5 两步骤,直至遍历结束,得到A 到其他节点的最短路径
算法图解
1.选定A节点并初始化,如上述步骤3所示![](https://upload-images.jianshu.io/upload_images/9949918-6f62eed1ff1c65d3.png)
2.执行上述 4、5两步骤,找出U集合中路径最短的节点D 加入S集合,并根据条件 if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' )
来更新U集合
![](https://upload-images.jianshu.io/upload_images/9949918-8e95032f57175718.png)
3.这时候 A->B, A->C
都为3,没关系。其实这时候他俩都是最短距离,如果从算法逻辑来讲的话,会先取到B点。而这个时候 if 条件变成了 if ( 'B 到 C,E 的距离' + 'AB 距离' < 'A 到 C,E 的距离' )
,如图所示这时候A->B
距离 其实为 A->D->B
![](https://upload-images.jianshu.io/upload_images/9949918-dc82c336b6ffe10e.png)
- 思路就是这样,往后就是大同小异了
![](https://upload-images.jianshu.io/upload_images/9949918-78981bc4a932cb78.png)
- 算法结束
![](https://upload-images.jianshu.io/upload_images/9949918-8f65977beebd6b4c.png)
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
算法可以检测带权有向图中是否存在负权回路,由松弛函数可知正常情况下,如果图中不存在负权回路,那么即使在最坏情况下,也只需要执行次迭代松弛,即可获得从起点到各顶点的最短路径。
若图中存在负权回路,当回路较小时,例如顶点自身或者两个顶点之间的负权回路,则在次迭代过程中,可能多次通过了该负权回路;若回路较大,例如从起点出发,串联所有顶点最后回到起点,即通过
条边构成一个圆形,如下图所示。则
次迭代过程中,可能一次也不会通过该负权回路,但是当再执行一次迭代松弛,即可将
值更新为负值,所以可以多执行一次迭代,通过判断是否更新从起点到某个顶点的最短路径权值,来判断图中是否存在负权回路。
![](https://upload-images.jianshu.io/upload_images/9738807-7fe774b417018222.png?imageMogr2/auto-orient/strip|imageView2/2/w/137/format/webp)
例题: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 }