最短路基础与习题
目录
A.POJ-2387 Til the Cows Come Home
C.POJ-1797 Heavy Transportation
前言
最短路算法主要分为Floyd,Dijkstra,Spfa(由Bellman-Ford 算法优化而来),而且这三种算法都是非常重要的。最短路求值问题主要分为单源最短路与多源最短路。单源最短路即在图中求出给定顶点到其它任一顶点的最短路径,而多源最短路要在途中求出任意两个顶点的最短路径。
Floyd算法
是用来求任意两个结点之间的最短路的。复杂度比较高,但是常数小,容易实现。(我能说只有三个 for
吗?)
适用于任何图,不管有向无向,边权正负,但是最短路必须存在。(不能有个负环)
我们定义一个数组 f[k][x][y]
,表示只允许经过结点 1到 k,结点 x到结点 y的最短路长度。
很显然, f[n][x][y]
就是结点x到结点 y的最短路长度。
由此可以得到:f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k]+f[k-1][k][y])。
这个做法空间是 。
但我们发现数组的第一维是没有用的,于是可以直接改成 f[x][y] = min(f[x][y], f[x][k]+f[k][y])
.
for (k = 1; k <= n; k++) {
for (i = 1; i <= n; i++) {
for (j = 1; j <= n; j++) {
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
}
}
Dijkstra算法
Dijkstra 算法,又叫迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到其他顶点的最短路径问题。举例来说,如果图中的顶点表示城市,而边上的权重表示著城市间开车行经的距离,Dijkstra 算法可以用来找到两个城市之间的最短路径。附上大佬的详解Dijkstra算法图文详解与我的一个模板。
详解
指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”。例如求下图中的1号顶点到2、3、4、5、6号顶点的最
短路径。
下面我们来模拟一下:
这就是Dijkstra算法的基本思路。
模板
void dijkstra(int u){ //u结点到n节点的最短路
int i,j,min1,v;
int dis[MAXV];
bool vis[MAXV];
for(i=1;i<=n;i++){
vis[i]=0;
dis[i]=map1[u][i];
}
vis[u]=1;
for(i=1;i<=n;i++){
min1=inf;
for(j=1;j<=n;j++)
if(!vis[j] && dis[j]<min1){
v=j;
min1=d[j];
}
vis[v]=1;
for(j=1;j<=n;j++)
if(!vis[j] && dis[j]>map1[v][j]+dis[v])
dis[j]=map1[v][j]+dis[v];
}
printf("%d\n",dis[n]);
}
Spfa算法
SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。
但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。另附另一个大佬的Spfa算法图文详解与模板(谁让我不会写呢),这里只介绍下过程,证明可以参考下大佬的博客。
详解
首先建立起始点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,b
7、队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b
8、队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:
在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了
最终a到g的最短路径为14
模板
void SPFA()
{
queue<int>q;
for(int i=2;i<=n;i++){
dis[i]=INF;
vis[i]=0;
}
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int s=q.front();
q.pop();
vis[s]=0;
for(int j=1;j<=n;j++){
if(dis[j]>dis[s]+a[s][j]){
dis[j]=dis[s]+a[s][j];
if(!vis[j]){
q.push(j);
vis[j]=1;
}
}
}
}
}
例题(大部分取自kuangbin带你飞)
A.POJ-2387 Til the Cows Come Home:简单的模板题,AC题解:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
#include <map>
#define INF 0x3f3f3f3f
#define PI 3.1415926
#define MOD 1e9+7
#define E 1e-6
#define LL long long
#define maxn 200007 //元素总个数
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
#define inf 1<<29
#define MAXV 1005
int map1[MAXV][MAXV];
int n,m;
void dijkstra(){
int i,j,min1,v;
int d[MAXV];
bool vis[MAXV];
for(i=1;i<=n;i++){
vis[i]=0;
d[i]=map1[1][i];
}
vis[1]=1;
for(i=1;i<=n;i++){
min1=inf;
for(j=1;j<=n;j++)
if(!vis[j] && d[j]<min1){
v=j;
min1=d[j];
}
vis[v]=1;
for(j=1;j<=n;j++)
if(!vis[j] && d[j]>map1[v][j]+d[v])
d[j]=map1[v][j]+d[v];
}
printf("%d\n",d[n]);
}
int main(){
int i,j,a,b,c;
while(~scanf("%d%d",&m,&n)){
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)
map1[i][i]=0;
else map1[i][j]=map1[j][i]=inf;
for(i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
if(map1[a][b]>c) map1[a][b]=map1[b][a]=c;
}
dijkstra();
}
return 0;
}
B.POJ-2253 Frogger:最短路的变形,问从一个点到另一个点使经过的边中最长的边尽可能短,最短为多少。主要是理解if(dis[j]>max(dis[s],a[s][j])) dis[j]=max(dis[s],a[s][j]);举个例子假如假设d[3]=3,d[2]=1,a[2][3]=2。那么根据题意显然dis[3]>max(dis[2],a[2][3])。根据题意理解即应该为dis[3]=max(dis[2],a[2][3]).AC题解:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
#include <map>
#define INF 0x3f3f3f3f
#define PI 3.1415926
#define MOD 1e9+7
#define E 1e-6
#define LL long long
#define maxn 200007 //元素总个数
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
using namespace std;
double a[205][205],dis[205];
int vis[205];
void SPFA(int n)
{
queue<int>q;
for(int i=2;i<=n;i++){
dis[i]=INF;
vis[i]=0;
}
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int s=q.front();
q.pop();
vis[s]=0;
for(int j=1;j<=n;j++){
if(dis[j]>max(dis[s],a[s][j])){
dis[j]=max(dis[s],a[s][j]);
if(!vis[j]){
q.push(j);
vis[j]=1;
}
}
}
}
}
int main()
{
int x[205],y[205];
int k=0,n;
while(scanf("%d",&n)!=EOF){
if(n==0)
break;
k++;
printf("Scenario #%d\n",k);
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++){
a[i][j]=a[j][i]=sqrt(double(x[i]-x[j])*(x[i]-x[j])+double(y[i]-y[j])*(y[i]-y[j]));
}
SPFA(n);
printf("Frog Distance = %.3lf\n\n",dis[2]);
}
return 0;
}
C.POJ-1797 Heavy Transportation:与上一题类似的变形题,只不过松弛条件换了,刚开始一直把载重量当成汽车要超过这个重量了,迷了很久,AC代码:
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<cmath>
using namespace std;
#define MAXV 1010
#define min(a,b) (a<b?a:b)
int map[MAXV][MAXV],n,m;
int spfa(){
queue <int>q;
int i,j,v;
int vis[MAXV],d[MAXV];
for(i=1;i<=n;i++){
vis[i]=0;
d[i]=0;
}
q.push(1);
vis[1]=1;
while(!q.empty()){
v=q.front();q.pop();
vis[v]=0;
for(i=1;i<=n;i++){
if(v==1 && map[v][i]){
d[i]=map[v][i];
if(!vis[i]){
vis[i]=1;
q.push(i);
}
continue;
}
if(d[i]<min(d[v],map[v][i])){
d[i]=min(d[v],map[v][i]);
if(!vis[i]){
vis[i]=1;
q.push(i);
}
}
}
}
return d[n];
}
int main(){
int t,i,j,sum,a,b,c;
scanf("%d",&sum);
for(t=1;t<=sum;t++){
scanf("%d%d",&n,&m);
for(i=0;i<=n;i++)
for(j=0;j<=n;j++)
map[i][j]=0;
for(i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
map[a][b]=map[b][a]=c;
}
printf("Scenario #%d:\n",t);
printf("%d\n\n",spfa());
}
return 0;
}
D:POJ-3268 Silver Cow Party:这道题目首先是计算从任意一点到指定点的距离,然后利用矩阵的转置,将矩阵变换好之后,再次Dijkstra,求出距离,然后加和求最大的距离即可,AC代码:
#include <iostream>
using namespace std;
#define MAXV 1010
#define inf 1<<29
int map[MAXV][MAXV],d[MAXV],dback[MAXV];
bool vis[MAXV];
int n,m,x;
int dijkstra(){
int i,j,v,mi;
for(i=1;i<=n;i++){
vis[i]=0;
d[i]=map[x][i];
dback[i]=map[i][x];
}
for(i=1;i<=n;i++){
mi=inf;
for(j=1;j<=n;j++)
if(!vis[j] && d[j]<mi){
v=j;
mi=d[j];
}
vis[v]=1;
for(j=1;j<=n;j++){
if(!vis[j] && map[v][j]+d[v]<d[j])
d[j]=map[v][j]+d[v];
}
}
for(i=1;i<=n;i++) vis[i]=0;
for(i=1;i<=n;i++){
mi=inf;
for(j=1;j<=n;j++)
if(!vis[j] && dback[j]<mi){
v=j;
mi=dback[j];
}
vis[v]=1;
for(j=1;j<=n;j++){
if(!vis[j] && map[j][v]+dback[v]<dback[j])
dback[j]=map[j][v]+dback[v];
}
}
mi=-1;
for(i=1;i<=n;i++){
if(d[i]+dback[i]>mi)
mi=d[i]+dback[i];
}
return mi;
}
int main(){
int i,a,b,c,j;
while(~scanf("%d%d%d",&n,&m,&x)){
for(i=1;i<=n;i++){
for(j=1;j<=n;j++)
if(i!=j) map[i][j]=inf;
else map[i][j]=0;
}
for(i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
map[a][b]=c;
}
printf("%d\n",dijkstra());
}
return 0;
}
E:POJ-1860 Currency Exchange:判断是否存在正环回路,可以参考下我写的题解Currency Exchange题解
F:POJ-3259 Wormholes:判断是否存在负环问题在 SPFA算法中,每次松弛的时候,会吧初始点的访问下表变为0,如果图里面存在环的话,SPFA算法是无法结束的,利用这个思维,在一个有n个点的图中,如果不存在自身环下某一个点顶多被所有其他的点相连,这样的话,这个点顶多进队列n-1次,如果存在环,这个点进队的次数一定会大于n-1,利用这个原理,进行一遍SPFA就可以得到答案了.AC代码:
#include <iostream>
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf=0x3f3f3f;
const int N=1200;
int vis[N];
int dir[N];
int num[N];
int n,m,w;
struct st
{
int id;
int wi;
} a;
vector<st>v[N];
int SPFA()
{
memset(vis,0,sizeof(vis));
memset(dir,inf,sizeof(dir));
memset(num,0,sizeof(num));
vis[2]=0;
dir[2]=0;
queue<int>q;
q.push(2);
num[2]++;
while(!q.empty())
{
int b=q.front();
if(num[b]>=n)
{
return 1;
}
q.pop();
vis[b]=0;
for(int i=0; i<v[b].size(); i++)
{
int id=v[b][i].id;
int wi=v[b][i].wi;
if(dir[id]>dir[b]+wi)
{
dir[id]=dir[b]+wi;
if(!vis[id])
{
vis[id]=1;
q.push(id);
num[id]++;
}
}
}
}
return 0;
}
int main ()
{
int t;
int x,y,z;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&w);
for(int i=0; i<=n; i++)
v[i].clear();
for(int i=0; i<m; i++)
{
scanf("%d%d%d",&x,&y,&z);
a.id=y;
a.wi=z;
v[x].push_back(a);
a.id=x;
a.wi=z;
v[y].push_back(a);
}
for(int i=0; i<w; i++)
{
scanf("%d%d%d",&x,&y,&z);
a.id=y;
a.wi=-z;
v[x].push_back(a);
}
int flag=SPFA();
if(flag)printf("YES\n");
else printf("NO\n");
}
return 0;
}
G.POJ-1502 MPI Maelstrom:求1开始到其他点的最短路径中,最长的那个是多少。题解:
#include <iostream>
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define MAXV 102
#define INF 100000
int map[MAXV][MAXV],n;
void dijstra(){
int i,j,ans=-1,min,v;
int d[MAXV],vis[MAXV];
//d数组表示从原点到i点的最短距离
//vis用于表达这个点是否已经被选中
for(i=1;i<=n;i++){
d[i]=INF;
vis[i]=0;
}
d[1]=0; //因为start到start的距离为0,这里源点为1
for(i=1;i<=n;i++){
min=INF;
for(j=1;j<=n;j++){ //每次找点的过程,首先这个点没有被发现,然后找一个最小点
if(!vis[j] && d[j]<min){
min=d[j];
v=j;
}
}
//这里为什么找的最小的边就一定是最短路呢
//因为一个图要连通起来,就必须有一条边和已知点集连起来,所以找的最小的未知点必是最短路
vis[v]=1;
for(j=1;j<=n;j++) //加进最小点后,再修改从源点没有被发现的点的最短路径
if(!vis[j] && d[v]+map[v][j]<d[j])
d[j]=d[v]+map[v][j];
}
for(i=2;i<=n;i++)
if(d[i]>ans) ans=d[i];
printf("%d\n",ans);
}
int main(){
char s[10];
int i,j;
while(~scanf("%d\n",&n)){
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i!=j)
map[i][j]=INF;
else
map[i][j]=0;
for(i=2;i<=n;i++)
for(j=1;j<i;j++){
scanf("%s",s);
if(s[0]!='x')
map[i][j]=map[j][i]=atoi(s); //将字符串转换为数字
}
dijstra();
}
return 0;
}
H.POJ-3660 Cow Contest :最短路的变形题,题解:
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m;
int map[101][101];
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(map[i][k]&&map[k][j])
map[i][j]=1;
int cnt=0;
for(int i = 1;i<=n;i++){
int ans=n-1;
for(int j = 1;j<=n;j++){
if(map[i][j]||map[j][i])
ans--;
}
if(ans==0) cnt++;
}
printf("%d\n",cnt);
}
int main(int argc, char** argv) {
while(scanf("%d %d",&n,&m)!=EOF)
{
memset(map,0,sizeof(map));
for(int i = 0;i<m;i++){
int a,b;
scanf("%d %d",&a,&b);
map[a][b]=1;
}
floyd();
}
return 0;
}