一. 题目描述
一天小明捧着一本世界地图在看,突然小明拿起笔,将他最爱的那些城市标记出来,并且随机的将这些城市中的某些用线段两两连接起来。
小明量出了每条线段的长度,现在小明想知道在这些线段组成的图中任意两个城市之间的最短距离是多少。
输入描述
输入包含多组测试数据。
每组输入第一行为两个正整数n(n<=10)和m(m<=n*(n-1)/2),n表示城市个数,m表示线段个数。
接下来m行,每行输入三个整数a,b和l,表示a市与b市之间存在一条线段,线段长度为l。(a与b不同)
每组最后一行输入两个整数x和y,表示问题:x市与y市之间的最短距离是多少。(x与y不同)
城市标号为1~n,l<=20。
输出描述
对于每组输入,输出x市与y市之间的最短距离,如果x市与y市之间非连通,则输出“No path”。
输入示例
4 4
1 2 4
1 3 1
1 4 1
2 3 1
2 4
输出示例
3
二. 解题思路
本题就是求两个节点之间的最短路径,在数据结构中有dijsktra和floyd两种求最短路径的方法,下面分别对这两种方法进行求解本题:
1. dijsktra解法:
int dijsktra(int x, int y, vector<vector<int>>& graph){
int num = graph.size();
vector<int> dis(num, 99);
vector<int> final(num, 0);
for(int i = 0; i < num; i++){
dis[i] = graph[x][i];
}
for(int i = 0; i < num; i++){
int u = 0, d = 100;
for(int j = 0; j < num; j++){
if(final[j] == 0 && dis[j] < d){
d = dis[j];
u = j;
}
}
final[u] = 1;
for(int j = 0; j < num; j++){
if(final[j] == 0){
dis[j] = min(dis[j], dis[u] + graph[j][u]);
}
}
}
return dis[y];
}
- 初始化:
num
是图中节点的数量。dis
是一个数组,用于存储从源点x
到每个节点的当前最短距离,初始时除了到x
自身的距离(理论上应为0,但这里从graph[x][i]
开始,这可能不正确,除非graph[x][x]
确实为0且图是有向的),其他都设为一个大数(99)。final
是一个标记数组,用于记录节点是否已被处理过(即最短路径是否已确定)。
- 寻找最小距离节点:
- 在每次迭代中,算法寻找一个未处理的节点
u
,它当前具有最小的dis[u]
值。 - 这里有一个问题:如果
graph[x][i]
初始化为graph
中x
行对应的值,那么如果graph
是一个邻接矩阵且包含未直接相连节点间的大值(如 INF),则这个初始化可能不正确,因为即使x
和i
不直接相连,dis[i]
也应该被设置为一个足够大的数(这里已经做了,但初始化方式可能不是最优的)。
- 在每次迭代中,算法寻找一个未处理的节点
- 更新距离:
- 一旦选择了节点
u
,算法会尝试通过u
来更新其他未处理节点的最短距离。这是通过检查dis[u] + graph[j][u]
是否小于当前的dis[j]
来实现的。 - 注意这里
graph[j][u]
的使用,它假设图是有向的,并且u
指向j
的边权重存储在graph[j][u]
中。这在某些图表示中是正确的(特别是当图以邻接矩阵的转置形式存储时),但在标准邻接矩阵表示中,你可能会期望看到graph[u][j]
。
- 一旦选择了节点
2. floyd解法:
int floyd(int x, int y, vector<vector<int>>& graph){
int num = graph.size();
vector<vector<int>> dis;
dis = graph;
for(int i = 0; i < num; i++){
for(int s = 0; s < num; s++){
for(int t = 0; t < num; t++){
dis[s][t] = min(dis[s][t], dis[s][i] + dis[i][t]);
}
}
}
return dis[x][y];
}
- 初始化:
int num = graph.size();
获取图中节点的数量。vector<vector<int>> dis;
声明一个二维向量dis
来存储最短路径的长度。dis = graph;
将输入的图graph
复制到dis
中,作为最短路径长度的初始值。这里假设graph
已经包含了节点之间的直接距离(如果两个节点不直接相连,则相应位置应该是一个足够大的数,表示无穷大或不可达)。
- 三重循环:
- 外部两层循环(
i
,s
)遍历图中的每个节点作为中间节点。 - 内部循环(
t
)遍历图中的每个节点作为目标节点。 - 在三重循环内部,通过比较
dis[s][t]
(当前从s
到t
的最短路径长度)和dis[s][i] + dis[i][t]
(通过中间节点i
从s
到t
的路径长度),来更新dis[s][t]
。如果后者更小,则更新dis[s][t]
,表示找到了一个更短的路径。
- 外部两层循环(
- 返回结果:
- 函数返回
dis[x][y]
,即从节点x
到节点y
的最短路径长度。
- 函数返回
三. 代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int dijsktra(int x, int y, vector<vector<int>>& graph){
int num = graph.size();
vector<int> dis(num, 99);
vector<int> final(num, 0);
for(int i = 0; i < num; i++){
dis[i] = graph[x][i];
}
for(int i = 0; i < num; i++){
int u = 0, d = 100;
for(int j = 0; j < num; j++){
if(final[j] == 0 && dis[j] < d){
d = dis[j];
u = j;
}
}
final[u] = 1;
for(int j = 0; j < num; j++){
if(final[j] == 0){
dis[j] = min(dis[j], dis[u] + graph[j][u]);
}
}
}
return dis[y];
}
int floyd(int x, int y, vector<vector<int>>& graph){
int num = graph.size();
vector<vector<int>> dis;
dis = graph;
for(int i = 0; i < num; i++){
for(int s = 0; s < num; s++){
for(int t = 0; t < num; t++){
dis[s][t] = min(dis[s][t], dis[s][i] + dis[i][t]);
}
}
}
return dis[x][y];
}
int main(){
int n, m;
while(cin >> n >> m){
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 99));
for(int i = 0 ; i< m; i++){
int a, b, l;
cin >> a >> b >> l;
graph[a][b] = l;
graph[b][a] = l;
}
for(int i = 1; i <= n; i++){
graph[i][i] = 0;
}
int x, y;
cin >> x >> y;
// int result = dijsktra(x, y, graph);
int result = floyd(x, y, graph);
if(result == 99){
cout << "No path" << endl;
}
else{
cout << result << endl;
}
}
}
四. 总结
这类题目是数据结构的基础概念,个人感觉是必须要掌握的,虽然这类题的考法就几种,但是掌握了之后对自己以后算法之类的学习还是有很大帮助的,大家一起加油!!!