最短路模型
定义
最短路模型是图论中的一个经典问题,旨在寻找从图中一个顶点到另一个顶点的路径,使得这条路径上的边(或边的权重)之和最小。这一模型在许多实际问题中有着广泛的应用,比如网络路由、地图导航、物流配送等场景。
在图论中,最短路问题通常形式化为在一个加权图 G=(V,E)G=(V,E) 中寻找两个顶点 uu 和 vv 之间的最短路径,其中 VV 是顶点集,EE 是边集,每条边 e \in Ee∈E 都有一个非负权重 w(e)w(e)。目标是找到一条从 uu 到 vv 的路径,使得路径上所有边的权重之和最小。
运用情况
- 网络路由:在互联网中,数据包需要通过最少的延迟或最低的成本从源节点传输到目的节点,这可以看作是一个最短路径问题。
- 地图导航:为司机或行人提供从起点到终点的最快或距离最短的路线。
- 物流与供应链管理:确定货物从仓库到客户或从供应商到工厂的最优运输路线,以减少运输成本和时间。
- 电路设计:在集成电路设计中,选择信号传递的最短路径以减少延迟和功耗。
- 社交网络分析:分析两个人之间关系链的最短路径,用于推荐系统或影响力传播分析。
注意事项
- 负权重边:如果图中存在负权重边,则不能直接使用某些算法如Dijkstra算法,而应考虑Bellman-Ford算法或Floyd-Warshall算法,因为它们能处理负权。
- 环路检测:在有负权重边的情况下,需要检查是否存在负权重环,因为这样的环路会导致无限减小路径长度,从而使问题无解。
- 稠密图与稀疏图:根据图的密度选择合适的算法。例如,Floyd-Warshall算法适用于稠密图,而Dijkstra或A*算法更适合稀疏图。
- 多源或多目标:当需要计算多个源点到单个目标或多个目标的最短路径时,可能需要对算法进行适当调整或多次调用。
解题思路
- 理解问题:首先明确问题是否为单源最短路径还是多源最短路径,图中是否存在负权重边。
- 选择算法:根据问题的具体情况选择合适的算法。常见的算法有Dijkstra算法(适用于无负权的单源最短路径)、Bellman-Ford算法(可处理负权重但无负权重环)、Floyd-Warshall算法(解决所有顶点对之间的最短路径,适合稠密图)。
- 实现与优化:实现所选算法,并考虑如何优化,比如使用优先队列优化Dijkstra算法,或在特定情况下利用预处理技巧减少计算量。
- 验证结果:检查计算出的路径是否确实是最短的,并确保没有违反任何先决条件,如负权重环的存在。
AcWing 188. 武士风度的牛
题目描述
AcWing 188. 武士风度的牛 - AcWing
运行代码
#include <cstring>
#include <iostream>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 155, M = N * N;
int n, m;
char g[N][N];
PII q[M];
int dist[N][N];
int bfs()
{
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
int sx, sy;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j] == 'K')
sx = i, sy = j;
int hh = 0, tt = 0;
q[0] = {sx, sy};
memset(dist, -1, sizeof dist);
dist[sx][sy] = 0;
while (hh <= tt)
{
PII t = q[hh ++ ];
for (int i = 0; i < 8; i ++ )
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] == '*') continue;
if (dist[a][b] != -1) continue;
if (g[a][b] == 'H') return dist[t.x][t.y] + 1;
dist[a][b] = dist[t.x][t.y] + 1;
q[ ++ tt] = {a, b};
}
}
return -1;
}
int main()
{
cin >> m >> n;
for (int i = 0; i < n; i ++ ) cin >> g[i];
cout << bfs() << endl;
return 0;
}
代码思路
-
包含文件和宏定义:
- 包含了
<cstring>
,<iostream>
和<algorithm>
头文件,分别用于字符串处理、输入输出和算法操作。 - 定义了一个宏
N
作为最大地图大小,并定义了类型别名PII
为pair<int, int>
,用于存储坐标。 - 使用
#define x first
和#define y second
来简化pair
对象的访问。
- 包含了
-
变量声明和初始化:
- 声明了
n
和m
分别代表地图的行数和列数,二维字符数组g
存储地图信息,q
作为队列存储待访问的坐标,dist
数组记录每个位置到起点的最短距离。 - 通过
memset
函数将dist
数组初始化为-1
,表示未访问过。
- 声明了
-
核心函数
bfs()
:- 首先,通过双层循环遍历地图,找到起始位置
'K'
的坐标(sx, sy)
。 - 初始化队列
q
,并将起始位置入队。 - 进行广度优先搜索,定义了变量
hh
和tt
作为队列的头尾指针,并用数组dx
和dy
来表示马可能的八个移动方向。 - 在 BFS 循环中,每次从队列头部取出一个坐标,遍历其八个可能的下一个位置,检查这些位置是否越界、是否有障碍物(
'*'
)或是否已经访问过。若新位置有效且未访问,则更新该位置的最短距离,并将其加入队列。 - 如果在搜索过程中遇到草(
'H'
),立即返回当前的最短距离加一(因为起始点的初始距离为0)。 - 若循环结束仍未找到草,则返回
-1
表示无解(根据题目描述,这种情况实际上不会发生)。
- 首先,通过双层循环遍历地图,找到起始位置
-
主函数
main()
:读取地图的列数m
和行数n
,然后逐行读取地图信息。调用bfs()
函数计算并输出最短跳跃次数。
改进思路
-
代码注释:添加详细的注释,特别是对于关键变量、数组含义、函数功能等进行说明,提高代码的可读性和维护性。
-
命名规范:变量命名可以更加直观,例如将
sx, sy
改为startX, startY
,将hh, tt
改为head, tail
,以增加代码的自解释性。 -
宏定义优化:考虑到使用
#define x first
和#define y second
可能导致的命名冲突和阅读困难,可以考虑直接在代码中使用first
和second
,或者在BFS函数内部临时定义辅助函数来访问pair的元素。 -
避免全局变量:尽量减少全局变量的使用,可以将
n
,m
,g
,q
,dist
等变量作为bfs
函数的参数传入,以减少不同部分间的耦合度。 -
边界检查和错误处理:虽然题目保证有解,但在实际应用中,增加对输入数据的校验(如检查地图尺寸是否合理,起点和终点是否存在等)可以提高程序的健壮性。
-
内存使用优化:当地图规模非常大时,可以考虑使用压缩存储或动态分配内存来减少空间消耗,例如只对访问过的节点分配
dist
数组的空间。 -
代码结构清晰化:将BFS逻辑拆分成几个小函数,如初始化队列、拓展节点等,可以让代码逻辑更清晰,便于阅读和维护。
-
使用标准库函数或STL容器:考虑使用
std::queue
代替原始数组实现队列,这样可以利用STL容器的便利性,减少手动管理数组指针的复杂度。