最短路基础与习题

目录

 

前言

Floyd算法

Dijkstra算法

详解

模板

Spfa算法

详解

模板

例题

A.POJ-2387 Til the Cows Come Home

B.POJ-2253 Frogger

C.POJ-1797 Heavy Transportation

D:POJ-3268 Silver Cow Party

E:POJ-1860 Currency Exchange

F:POJ-3259 Wormholes

G.POJ-1502 MPI Maelstrom

H.POJ-3660 Cow Contest 


前言

最短路算法主要分为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])。这个做法空间是  0(N^{3})

但我们发现数组的第一维是没有用的,于是可以直接改成 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
",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
",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
",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

",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:
",t);
		printf("%d

",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
",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
");
        else printf("NO
");
    }
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
",ans);
}

int main(){
	char s[10];
	int i,j;
	while(~scanf("%d
",&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
",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;
}
原文地址:https://www.cnblogs.com/shmilky/p/14089055.html