📅  最后修改于: 2023-12-03 15:28:49.170000             🧑  作者: Mango
在一个 $n$ 行 $m$ 列的网格图中,有部分格子障碍着,定义两个格子上下左右相邻,且它们之一没有障碍时为相邻的格子。现在,从图上任选两个相邻的无障碍格子作为起点和终点,你需要求出从起点到终点的最短路,并把这些最短路上的边都取出来。
输入格式:
第一行两个正整数 $n,m$,表示网格图有 $n$ 行 $m$ 列。
接下来 $n$ 行,每行 $m$ 个字符,字符为 .
或 #
,其中 .
表示格子上没有障碍,#
表示格子上有障碍。保证第 $1$ 行和第 $n$ 行的字符都是 #
,两侧也都是 #
,且除了两侧,最多只有一列相邻的 .
格子。
输出格式: 第一行为从起点到终点的最短路长度。 接下来若干行,表示这些最短路上的边,每行两个整数,表示这个边所连接的两个点,用坐标表示,要求输出的边的顺序必须为它们在路径上出现的顺序。
本题是一个最短路问题,可以用 Dijkstra 或 BFS 解决。
对于输出的路径,可以在搜索过程中记录每个点的前一个点,找到终点后,倒推回去输出路径。
领接表存储图、堆优化的 Dijkstra 或者 BFS 实现、路径输出,都是经典算法的基础操作,这里不再详细说明。
以下为 C++ 堆优化的 Dijkstra 实现:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 510, M = N * N;
const int INF = 0x3f3f3f3f;
char g[N][N]; // 存储地图
int n, m;
int idx = 0; // 存边数组边序号
int h[N], e[M], ne[M], w[M], idx;
bool st[N];
int dist[N], pre[N];
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(int start)
{
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
// 堆优化的dijkstra
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, start});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
// 相当于松弛操作
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
pre[j] = ver;
heap.push({dist[j], j});
}
}
}
}
int main()
{
cin >> n >> m;
int start, end;
memset(h, -1, sizeof h);
// Step1:读入地图,建图
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
{
cin >> g[i][j];
if (g[i][j] == 'S') start = i * m + j;
else if (g[i][j] == 'E') end = i * m + j;
if (g[i][j] == 'S' || g[i][j] == 'E' || g[i][j] == '#') continue;
int a = i * m + j; // 非障碍点的编号
if (i > 0 && g[i - 1][j] == '.') // 上
{
int b = a - m;
add(a, b, 1);
add(b, a, 1);
}
if (j > 0 && g[i][j - 1] == '.') // 左
{
int b = a - 1;
add(a, b, 1);
add(b, a, 1);
}
}
// Step2:跑dijkstra
dijkstra(start);
// Step3:输出最短路
cout << dist[end] << endl;
if (dist[end] != INF) // 查找到终点
{
int path[N], count = 0;
for(int i = end; i != start; i = pre[i])
path[count ++] = i;
path[count] = start;
for(int i = count; i >= 1; i --) // 倒序输出路径
cout << path[i] / m << " " << path[i] % m << endl;
cout << path[0] / m << " " << path[0] % m << endl;
}
else // 没有遍历到终点
{
cout << -1 << endl;
}
return 0;
}
时间复杂度: 打表得最坏情况下网格图中的总障碍数为 $Θ(m \times n)$,因此存储图需要 $Θ(m\times n)$ 的空间,同时添加边数量也为 $Θ(m\times n)$ 级别。 另外,我们需要 $O(m\times n \log m\times n)$ 的时间复杂度求解最短路,由于 $m,n$ 处于一个数量级,可以简单近似为 $Θ(n^2\log n)$。 因此总时间复杂度为 $Θ(m\times n \log m\times n)$,使用堆优化Dijkstra可以保证该复杂度下的计算效率。