题目描述
给一个 n ( 1 ≤ n ≤ 2500 ) n(1 \leq n \leq 2500) n(1≤n≤2500)个点 m ( 1 ≤ m ≤ 6200 ) m(1 \leq m\leq 6200 ) m(1≤m≤6200) 条边的无向图,求 s s s到 t t t的最短路。
输入格式
第一行四个由空格隔开的整数
n
n
n、
m
m
m、
s
s
s、
t
t
t。
之后的
m
m
m 行,每行三个正整数
s
i
s_i
si、
t
i
t_i
ti、
w
i
(
1
≤
w
i
≤
1
0
9
)
w_i(1 \leq w_i \leq 10 ^ 9)
wi(1≤wi≤109),表示一条从
s
i
s_i
si 到
t
i
t_i
ti 长度为
w
i
w_i
wi 的边。
输出格式
一个整数表示从 s s s 到 t t t 的最短路长度。数据保证至少存在一条道路。
输入样例
7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1
输出样例
7
算法思路
迪杰斯特拉算法会计算出从起始节点到所有其他节点的最短距离
算法思维: 贪心策略
- 邻接矩阵: g r a p h graph graph, 题解中用 a a a数组表示
- 记录起点到所有节点的最短距离: d i s t dist dist
- 记录节点的访问状态: v v v 0 0 0表示未访问, 1 1 1表示已访问
- 进行 n n n次遍历, 每次在 d i s t dist dist中找到一个未被访问的最小值,
- 记录最小值 m i n _ d i s t min\_dist min_dist, 并记录其节点 m i d mid mid
- 以 m i d mid mid节点为中转节点, 遍历 d i s t dist dist所有节点, 如果
v[j] == 0 && dist[j] > dist[mid] + a[mid][j]
即节点未被访问, 且起始节点到 mid 节点的距离 + mid 节点到此节点的距离 < 起始节点到此节点的距离,
也即是说找到了一条更短的起始点到达此节点的路径, 那么就更新 d i s t dist dist里此节点的信息.
代码实现
#include<bits/stdc++.h>
using namespace std;
int a[2505][2505]; // 邻接矩阵
int v[2505] = {0}; // 访问数组, 初始为 0, 未访问
int dist[2505]; // 起点到各节点的最短路径数组
int main()
{
memset(a, 0x3f, sizeof(a)); // 初始为极大值
memset(a, 0x3f, sizeof(a)); // 初始化为极大值
int n, m, s, t;
int x, y, d;
cin >> n >> m >> s >> t;
for(int i = 1; i <= m; i++) // 存储邻接矩阵
{
cin >> x >> y >> d;
a[x][y] = d; // 无向图, 相互距离一致
a[y][x] = d;
}
for(int i = 1; i <= n; i++) // 记录已有的起始点 s 到各点的距离
dist[i] = a[s][i];
v[s] = 1; // 起点标记
for(int i = 1; i <= n; i++) // 开始更新所有节点, n 次
{
int min_dist = 0x3f3f3f3f; // 初始化最短距离
int mid = 0;
for(int j = 1; j <= n; j++) // 寻找未被访问的最近邻接点
{
if(v[j] == 0 && min_dist > dist[j])
{
min_dist = dist[j]; // 记录最近距离邻接点
mid = j;
}
}
for(int j = 1; j <= n; j++) // 以最近距离邻接点为中转, 更新邻节点
{
if(v[j] == 0 && dist[j] > dist[mid] + a[mid][j])
dist[j] = dist[mid] + a[mid][j];
}
v[mid] = 1; // 当前节点标记为已访问
}
cout << dist[t]; // 输出起点到 t 的最短路径
}
输出最短的路径
- 创建一个数组 p a t h path path , 初始化为 0 0 0, 记录每个节点的前驱节点, 即在路径最短的情况下: 起点到达该节点, 是由哪个节点中转过来的。
- 在更新最短路径的过程中, 如果更新了某个节点的最短距离, 那么相应的更新该节点的前驱节点。
- 使用动态数组 p a t h _ s _ t path\_s\_t path_s_t记录起点至终点的最优路径, 由终点开始, 在 p a t h path path中回溯路径, 回溯至起点或者 0 0 0, 回溯完成。
- 逆向输出动态数组, 即路径:
代码示例:
#include<bits/stdc++.h>
using namespace std;
int a[2505][2505]; // 邻接矩阵
int v[2505] = {0}; // 访问数组, 初始为 0, 未访问
int dist[2505]; // 起点到各节点的最短距离
int path[2505]; // 记录每个节点的前驱节点
int main()
{
memset(a, 0x3f, sizeof(a));
memset(dist, 0x3f, sizeof(dist));
int n, m, s, t;
int x, y, d;
cin >> n >> m >> s >> t;
for(int i = 1; i <= m; i++) // 存储邻接矩阵
{
cin >> x >> y >> d;
a[x][y] = d; // 无向图, 相互距离一致
a[y][x] = d;
}
for(int i = 1; i <= n; i++) // 起始点 s 到各点的距离
dist[i] = a[s][i];
v[s] = 1; // 起点标记
for(int i = 1; i <= n; i++) // 开始更新所有节点, n 次
{
int min_dist = 0x3f3f3f3f; // 初始化最短距离
int mid = 0;
for(int j = 1; j <= n; j++) // 寻找未被访问的最近邻接点
{
if(v[j] == 0 && min_dist > dist[j])
{
min_dist = dist[j]; // 记录最近距离邻接点
mid = j;
}
}
for(int j = 1; j <= n; j++) // 以最近距离邻接点为中转, 更新邻节点
{
if(v[j] == 0 && dist[j] > dist[mid] + a[mid][j])
{
dist[j] = dist[mid] + a[mid][j];
path[j] = mid; // 记录此节点的前驱节点,即由哪个节点到达此节点最近
}
}
v[mid] = 1; // 标记已访问
}
cout << dist[t]; // 输出到 t 的最短路径距离
// for(int i = 1; i <= n; i++) cout << endl << path[i]; // 查看所有节点的前驱节点
vector <int> path_s_t; // 存储由终点至起点的路径
int node = t; // 由终点开始回溯路径节点
while(true)
{
path_s_t.push_back(node); // 将当前节点加入路径中
if(path[node] == 0 || path[node] == s) // 当前节点无前置节点或前置节点是起点,路径存储结束
{path_s_t.push_back(s); break;}
node = path[node]; // 找到当前节点的前驱节点
}
// 输出完整路径, 因为存储的时是由终点到起点, 输出时, 逆向输出
cout << endl;
for(int i = path_s_t.size() - 1; i >= 0 ; i--) cout << path_s_t[i] << "->";
}