1613.最短路径问题

原题链接

外网进不去

题目大意

在一个地图上有 n ( 2 ≤ n ≤ 100 ) n(2\le n\le 100) n(2n100) 个点,每一个点对应一个坐标:
x   ( − 10000 ≤ x ≤ 10000 ) , y   ( − 10000 ≤ y ≤ 10000 ) x\ (-10000\le x\le 10000),y\ (-10000\le y\le 10000) x (10000x10000),y (10000y10000)
再给出 m   ( 1 ≤ m ≤ n 2 − n 2 ) m\ (1\le m\le \frac{n^2-n}2) m (1m2n2n) 条边(每条边对应两个数,分别表示联通一边中的第一个点和第二个点),和一个初始位置、一个结束位置(第 1 ≤ s ≤ n 1\le s\le n 1sn 个点和第 1 ≤ t ≤ n 1\le t\le n 1tn个点)。求从初始位置到结束位置的最短路径。

解题思路

1613:1

Floyed-Warshall算法

这个算法是一个思路为动态规划的算法。主要流程是:

1.找到一个初始点:

在这里插入图片描述

2.找到一个终点:

1613:2

3.找到一个中转点,并且试图中转,比较经过这一个点连接的距离和原来最短的距离,选择更短的一条路径:

1613:3

代码实现
#include<iostream>
#include<fstream>
#include<set>
#include<map>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
struct node{
	double x,y;
} xy[1010];
double a[1010][1010];
int f,t,n,m;
double dis(double x_1,double y_1,double x_2,double y_2)
{
	return sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2));//使用勾股定理计算直线距离
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>xy[i].x>>xy[i].y;
	cin>>m;
	memset(a,0x5f,sizeof(a));
	for(int i=1;i<=m;i++){
		cin>>f>>t;
		double d=dis(xy[f].x,xy[f].y,xy[t].x,xy[t].y);//连接两点直线的距离
		a[f][t]=d;//建立边
		a[t][f]=d;//建立边
	}
	cin>>f>>t;//输入起始和终点
	for(int k=1;k<=n;k++){//枚举中转点
		for(int i=1;i<=n;i++){//枚举起点
			for(int j=1;j<=n;j++){//枚举终点
				a[i][j]=min(a[i][j],a[i][k]+a[k][j]);//第三步,松弛操作
			}
		}
	}
	printf("%.2f",a[f][t]);//从f到t的最短路
} 

Dijkstra算法

这个算法的思路类似于贪心,每一次从已有的边中,找到一条最短的,向外扩散,对于每一个于其相连的边进行更新,具体流程为:

1.找到最短的一条边

1613:4

2.更新最短路

1613:5

#include<iostream>
#include<fstream>
#include<set>
#include<map>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
struct node{
	double x,y;
} xy[110];
struct node2{
	double _dis;
	int x;
};
double n,m,Min[110],a[110][110];
int f,t;
bool v[110];
double dis(double x_1,double y_1,double x_2,double y_2)
{
	return sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2));//使用勾股定理计算直线距离
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>xy[i].x>>xy[i].y;
	cin>>m;
	memset(a,0x7f,sizeof(a));
	for(int i=1;i<=m;i++){
		cin>>f>>t;
		double d=dis(xy[f].x,xy[f].y,xy[t].x,xy[t].y);//连接两点直线的距离
		a[f][t]=d;//建立边
		a[t][f]=d;//建立边
	}
	memset(Min,0x7f,sizeof(Min));
	memset(v,true,sizeof(v));
	cin>>f>>t;
	Min[f]=0;
	int next;
	for(int i=1;i<=n;i++){
		next=0;
		for(int j=1;j<=n;j++){//枚举最短路
			if(Min[j]<Min[next]&&v[j])
				next=j;//记录
		}
		if(next==0)//无法更新
			break;
		v[next]=false;//标记为最终答案
		for(int j=1;j<=n;j++){
			if(Min[j]>a[next][j]+Min[next]&&v[j])//更新长度,松弛操作
				Min[j]=a[next][j]+Min[next];//更新长度,松弛操作
		}
	}
	printf("%.2f",Min[t]);
	return 0;
} 

但是,上面的代码时间复杂度很高,为 O ( n 2 ) O(n^2) O(n2) 。其中,有一个 n n n 是为了找到最短路径而使用的,如果可以省去,效率将大大提高。这里,我们可以使用优先队列。

队列优化,代码实现
#include<iostream>
#include<fstream>
#include<set>
#include<map>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
struct node{
	double x,y;
} xy[110];
struct node2{
	double _dis;
	int x;
};
double n,m,Min[1010];
vector<node2> a[1010];
priority_queue<node2> q;//有限队列,默认大根堆
int f,t;
double dis(double x_1,double y_1,double x_2,double y_2)
{
	return sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2));//使用勾股定理计算直线距离
}
bool operator<(node2 q,node2 h)//重载比较符号,双关键字比较
{
	if(q._dis!=h._dis)//第一关键字
		return q._dis<h._dis;
	return q.x<h.x;//第二关键字
}
bool operator>(node2 q,node2 h)
{
	if(q._dis!=h._dis)//第一关键字
		return q._dis>h._dis;
	return q.x>h.x;//第二关键字
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>xy[i].x>>xy[i].y;
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>f>>t;
		double d=dis(xy[f].x,xy[f].y,xy[t].x,xy[t].y);//连接两点直线的距离
		a[f].push_back((node2){d,t});//建立边,邻接列表
		a[t].push_back((node2){d,f});//建立边,邻接列表
	}
	memset(Min,0x7f,sizeof(Min));
	cin>>f>>t;
	q.push((node2){0,f});//初始化队列
	Min[f]=0;//初始化,起始点最短路距离为0
	while(!q.empty()){
		int now_x=q.top().x;
		double now_dis=-q.top()._dis;//从队首取出元素
		q.pop();
		if(Min[now_x]<now_dis) continue;
		for(int i=0;i<a[now_x].size();i++){//遍历邻接列表
			int next=a[now_x][i].x;
			if(Min[next]>now_dis+a[now_x][i]._dis){//更新长度,松弛操作
				Min[next]=now_dis+a[now_x][i]._dis;//更新长度,松弛操作
				q.push((node2){-Min[next],next});//更新优先队列,存储时乘上-1,利用大根堆进行排序
			}
		}
	}
	printf("%.2f",Min[t]);
	return 0;
} 

spfa算法

这个算法类似于广度搜索,不过允许每一个点重复进队,但不允许有一个点在队列中同时存在两个。具体方法如下:

1.从队首取出一个元素,并且更新其领边

1613:6

2.选择更新成功的点,将其入队

1613:7

代码实现
#include<iostream>
#include<fstream>
#include<set>
#include<map>
#include<ctime>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
struct node{
	double x,y;
} xy[1010];
struct node2{
	int p;
	double l;
};
vector<node2> a[1010];
bool v[1010];
int f,t,n,m;
double ans[1010];
int q[1010];
double dis(double x_1,double y_1,double x_2,double y_2)
{
	return sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2));//使用勾股定理计算直线距离
}
void work(int n2)
{
	int h=0,t=0;
	v[n2]=true;//初始化标记进队
	q[++t]=n2;//初始化队首元素
	ans[n2]=0;//初始化最短路起点长度
	while(h!=t)
	{
		h=h%n+1;//循环队列,队首
		int now=q[h];
		for(int i=0;i<a[now].size();i++){//遍历邻接列表
			if(ans[now]+a[now][i].l<ans[a[now][i].p]){//更新长度,松弛操作
				ans[a[now][i].p]=ans[now]+a[now][i].l;//更新长度,松弛操作
				if(!v[a[now][i].p]){//判断是否在队列中
					t=t%n+1;//循环队列,队尾
					q[t]=a[now][i].p;//进队
					v[a[now][i].p]=true;//标记进队
				}
			}
		}
		v[now]=false;//彻底出队
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>xy[i].x>>xy[i].y;
		ans[i]=0x7fffffff;
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>f>>t;
		double d=dis(xy[f].x,xy[f].y,xy[t].x,xy[t].y);//连接两点直线的距离
		a[f].push_back((node2){t,d});//建立边,邻接列表
		a[t].push_back((node2){f,d});//建立边,邻接列表
	}
	cin>>f>>t;
	work(f);
	printf("%.2f",ans[t]);
}

样例

输入

5
0 0 
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5

输出

3.41
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值