原题链接
题目大意
在一个地图上有
n
(
2
≤
n
≤
100
)
n(2\le n\le 100)
n(2≤n≤100) 个点,每一个点对应一个坐标:
x
(
−
10000
≤
x
≤
10000
)
,
y
(
−
10000
≤
y
≤
10000
)
x\ (-10000\le x\le 10000),y\ (-10000\le y\le 10000)
x (−10000≤x≤10000),y (−10000≤y≤10000)
再给出
m
(
1
≤
m
≤
n
2
−
n
2
)
m\ (1\le m\le \frac{n^2-n}2)
m (1≤m≤2n2−n) 条边(每条边对应两个数,分别表示联通一边中的第一个点和第二个点),和一个初始位置、一个结束位置(第
1
≤
s
≤
n
1\le s\le n
1≤s≤n 个点和第
1
≤
t
≤
n
1\le t\le n
1≤t≤n个点)。求从初始位置到结束位置的最短路径。
解题思路
Floyed-Warshall算法
这个算法是一个思路为动态规划的算法。主要流程是:
1.找到一个初始点:
2.找到一个终点:
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.找到最短的一条边
2.更新最短路
#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.从队首取出一个元素,并且更新其领边
2.选择更新成功的点,将其入队
代码实现
#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