POJ 2387-Til the Cows Come Home(最短路问题)

POJ 2387-Til the Cows Come Home

Bessie is out in the field and wants to get back to the barn to get as much sleep as possible before Farmer John wakes her for the morning milking. Bessie needs her beauty sleep, so she wants to get back as quickly as possible.

Farmer John’s field has N (2 <= N <= 1000) landmarks in it, uniquely numbered 1…N. Landmark 1 is the barn; the apple tree grove in which Bessie stands all day is landmark N. Cows travel in the field using T (1 <= T <= 2000) bidirectional cow-trails of various lengths between the landmarks. Bessie is not confident of her navigation ability, so she always stays on a trail from its start to its end once she starts it.

Given the trails between the landmarks, determine the minimum distance Bessie must walk to get back to the barn. It is guaranteed that some such route exists.
Input

  • Line 1: Two integers: T and N

  • Lines 2…T+1: Each line describes a trail as three space-separated integers. The first two integers are the landmarks between which the trail travels. The third integer is the length of the trail, range 1…100.
    Output

  • Line 1: A single integer, the minimum distance that Bessie must travel to get from landmark N to landmark 1.
    Sample Input
    5 5
    1 2 20
    2 3 30
    3 4 20
    4 5 20
    1 5 100
    Sample Output
    90
    Hint
    INPUT DETAILS:

There are five landmarks.

OUTPUT DETAILS:

Bessie can get home by following trails 4, 3, 2, and 1.

典型的最短路问题

  • Floyd-Warshall算法
    Floyd-Warshall算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题
    同时也被用于计算有向图的传递闭包。
    Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。
    原理很简单,用一个二维数组来存图,然后每次判断两个点之间是否能通过第三个点来使路径变短 a–b 看是否有一个点 k 可以 a–k--b使路径变短,变短了就更新这个值 假如这个k==1
for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(mp[i][j]>mp[i][1]+mp[1][j])
			mp[i][j]=mp[i][1]+mp[1][j]

同理如果k==2

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		if(mp[i][j]>mp[i][2]+mp[2][j])
			mp[i][j]=mp[i][2]+mp[2][j];

所以我们想看a-b 的最短路只要比较通过1-n所有点中转后的最小的一个就是a-b的最短路,依次更新任意两点之间的最短路,代码如下:(当然这样的复杂度时n^3,肯定会超时,我们在这只是介绍下这种最简单的最短路)

#include <iostream>
#include <cstdio>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;
int mp[1010][1020];
void init()
{
	memset(mp,inf,sizeof(mp));
}
int main()
{
	int t,n;
	scanf("%d%d",&t,&n);
	init();
	int a,b,w;
	for(int i=0;i<t;i++)
	{
		//cin>>a>>b>>w;
		scanf("%d%d%d",&a,&b,&w);
		mp[a][b]=w;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				if(mp[i][j]>mp[i][k]+mp[k][j])
					mp[i][j]=mp[i][k]+mp[k][j];
			}
		}
	}
	cout<<mp[1][n]<<endl;
	
	return 0;
}
  • Dijkstra算法
    Dijkstra算法是典型的用来解决单源最短路径的算法,用来求从起始点到其他所有点最短路径,时间复杂度是o(n^3)。
    该算法采用了贪心的思想,每次都查找与该点距离最近的点 ,也因为这样,它不能用来解决存在负权边的图。
    解决的问题大多是这样的:有一个无向图G(V,E),边E[i]的权值为W[i],找出V[0]到V[i]的最短路径。
    原理就是每次找到离出发点最近的点,然后用这个出发点到这个点所在的边来松弛其他的边,当然已经确定是最短的边在松弛的时候要跳过去。
步骤:1.先将所有的顶点分成两个集合,P和Q,P是已知最短路的集合,Q 是未知最短路的集合,用一个vis[]数组来区分P和Q ,vis[i]==1就是P 反之就在Q里. 2.用dis数组记录从初始点s到i的最短路,每次从P中找一个最小的点,用这个点来松弛其他的所有点,更新dis数组。

代码如下:

///这是用一个mp数组来记录的路径 空间复杂度是o(n^2)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;
int mp[1020][1020];
int dis[1010];//储存的是从目的地到各个点的最短路 
int vis[1010];
int n,m;///n 是点数,t是边数 
void init()
{
	memset(vis,0,sizeof(vis));
	memset(dis,inf,sizeof(dis));
	memset(mp,inf,sizeof(mp));
}
void Dijkstra(int s,int e)///s是出发点 e是目的地 
{
	dis[s]=0;
	for(int k=1;k<=n;k++)
	{
		int minw=inf;///记录用来松弛的边的长度 
		int minv;///记录用来松弛的点
		for(int i=1;i<=n;i++)
		{
			if(dis[i]<minw&&vis[i]==0)
			{
				minw=dis[i];
				minv=i;
			}
		} 
		vis[minv]=1;
		/*if(minv==e) 
		{
			cout<<dis[minv]<<endl;
			return ;
		}*/ 
		//这一部分可以不用也可以用 这是做了一个小小的优化 找到目的地立刻退出,Dijkstra是可以找到从出发点到所有点的最短路
		//如果加上这一句就不能保证其他的能找到了
		for(int i=1;i<=n;i++)
		{
			if(vis[i]==1)//已经确定了的点 
				continue;
			//if(mp[minv][i]==inf) ///表示从松弛点到i并没有边 所以就不存在松弛了
			//	continue
			dis[i]=min(dis[i],dis[minv]+mp[minv][i]); 
		}
	} 
	cout<<dis[e]<<endl;
}
int main()
{
	cin>>m>>n;
	init();
	int a,b,w;
	for(int i=0;i<m;i++)
	{
		cin>>a>>b>>w;
		if(w<mp[a][b])
		{
			mp[a][b]=w;
			mp[b][a]=w;	 
		}
	}
	Dijkstra(1,n);
	return 0;
}
  • SPFA算法(最好的最短路算法,啥也能解决复杂度还低)
    SPFA 算法通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度为 O(mn)。很多时候,给定的图存在负权边,这时类似Dijkstra算法等便没有了用武之地,就需要SPFA算法,SPFA还可以判负环。
    原理:用邻接表存图,每次把松弛后的点放入队列,每次取队首的那个点,找和这个点相连的所有边来看看是否能松弛,能松弛就的点入队,直到队列为空。
    代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>
#define maxx 1010
using namespace std;
int head[maxx];
int cn[maxx];
int dis[maxx];
int vis[maxx];
struct node {
	int to;
	int val;
	int next;
}side[maxx*maxx];
int cnt=0;
void add(int x,int y,int val)
{
	side[cnt].to=y;
	side[cnt].val=val;
	side[cnt].next=head[x];
	head[x]=cnt++;
}
void init()
{
	memset(head,-1,sizeof(head));
	memset(dis,0x3f3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	cnt=0;
}
int n,m;
int main()
{
	cin>>m>>n;
	init();
	int a,b,w;
	for(int i=1;i<=m;i++)
	{
		cin>>a>>b>>w;
		add(a,b,w);
		add(b,a,w);
	}
	dis[1]=0;
	vis[1]=1;
	queue<int> q;
	q.push(1);
	while(q.size())
	{
		int p=q.front();
		q.pop();
		vis[p]=0;
		for(int i=head[p];i!=-1;i=side[i].next)
		{
			if(dis[side[i].to]>dis[p]+side[i].val)
			{
				dis[side[i].to]=dis[p]+side[i].val;
				if(vis[side[i].to]==0)
				{
					cn[side[i].to]++;///超过n 就有负环(负权回路) 
					q.push(side[i].to);
					vis[side[i].to]=1;
				}
			}
		}
	}
	cout<<dis[n]<<endl; 
}
 

通过这个题,对最短路的几个算法做一下总结

  • Floyd 算法空间负责度 o(n^2) 时间复杂度 o(n ^3) 优点是代码短,能解含有负权的最短路问题。可解决多源最短路问题,缺点就是慢!
  • Dijkstra 如果用数组存图 时间复杂度o(N^2) 空间复杂度 o(n ^2).如果用邻接表的话 空间复杂度 0(m)时间复杂度o(m+n)log n,如果是稠密图的话更适合用数组存,如果是稀疏图的话更适合用邻接表存。优点是有良好的扩展性,时间复杂度可以接受,缺点是不能解决负权问题。
  • SPFA 时间复杂度最坏就是o(m*n) 空间复杂度o(m) 能解决负权问题,能判断负环,效率高,是最好的解决单源最短路的方法。

猜你喜欢

转载自blog.csdn.net/weixin_43179892/article/details/83448773