一、小 S 的黑白块迷宫
1. 题目介绍
小S需要在一个 n×m 网格迷宫中,从起点 (1,1) 到终点 (n,m),经过尽可能少的黑色格子。网格中的每个格子可以是:
- 白色格子(值为
0,通过成本为0)。 - 黑色格子(值为
1,通过成本为1)。
2、思路
这类问题可以抽象为加权最短路径问题,权值为 0 或 1。使用 0-1 BFS(双端队列 BFS)可以高效解决。
实现步骤:
-
初始化:
- 定义一个二维数组
dist记录从起点到每个格子的最小黑色格子数量,初始化为inf。 - 使用双端队列
dq,将起点加入队列。
- 定义一个二维数组
-
BFS 遍历:
-
每次从队列中取出一个格子
(x, y)。 -
遍历四个相邻方向
(nx, ny),计算到达新位置的代价:- 如果是白色格子(
grid[nx][ny] == 0),优先处理,加入队列前端。 - 如果是黑色格子(
grid[nx][ny] == 1),加入队列后端。
- 如果是白色格子(
-
-
返回结果:
- 终点的
dist[n-1][m-1]即为最少经过的黑色格子数量。
- 终点的
3、代码实现
from collections import deque
def min_black_cells(n, m, grid):
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
dist = [[float('inf')] * m for _ in range(n)]
dist[0][0] = grid[0][0]
dq = deque([(0, 0)])
while dq:
x, y = dq.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < n and 0 <= ny < m:
new_cost = dist[x][y] + grid[nx][ny]
if new_cost < dist[nx][ny]:
dist[nx][ny] = new_cost
if grid[nx][ny] == 0:
dq.appendleft((nx, ny))
else:
dq.append((nx, ny))
return dist[n-1][m-1]
二、类似题目
一个在植物园中的探险问题,小明需要在不回头的情况下尽可能参观完植物园,同时要尽量避免踩到障碍物。题目给出了植物园的长度、前进的最小和最大距离、以及障碍物的位置,要求计算小明在抵达终点时最少需要踩到的障碍物数量。
输入格式:
- 第一行:一个正整数
L,表示植物园的长度。 - 第二行:三个正整数
S、T和M,分别表示前进的最小距离、最大距离及园内障碍物的个数。这三个数之间用一个空格隔开,M后不准有空格。 - 第三行:
M个不同的正整数,表示这些障碍物在路径上的位置。所有相邻的整数之间用一个空格隔开,最后一个正整数之后不可有空格。
输入样例1:
植物园长度 L:10
前进的最小距离 S:2
前进的最大距离 T:3
障碍物个数 M:5
障碍物位置:2 3 5 6 7
输出:2
BFS 解题思路:
- 初始化:从起点
0开始,将其加入队列,并记录当前位置的障碍物数量。 - BFS 遍历:每次从队列中取出一个位置,尝试从该位置前进
S到T之间的所有可能步数,并将新位置加入队列。 - 障碍物处理:如果新位置是障碍物,则增加障碍物数量。
- 终止条件:当到达或超过终点
L时,记录当前的障碍物数量,并继续搜索直到队列为空。
from collections import deque
def min_obstacles_bfs(L, S, T, M, obstacles):
# 将障碍物位置放入集合中,方便快速查找
obstacle_set = set(obstacles)
# 初始化队列,每个元素为 (当前位置, 当前踩到的障碍物数量)
queue = deque([(0, 0)])
# 记录已经访问过的位置,避免重复访问
visited = set([0])
# 记录最少踩到的障碍物数量
min_obstacles = float('inf')
while queue:
current_pos, current_obstacles = queue.popleft()
# 如果到达或超过终点,更新最少障碍物数量
if current_pos >= L:
min_obstacles = min(min_obstacles, current_obstacles)
continue
# 尝试从当前位置前进 S 到 T 之间的所有步数
for step in range(S, T + 1):
next_pos = current_pos + step
# 如果新位置未访问过,则加入队列
if next_pos not in visited:
visited.add(next_pos)
next_obstacles = current_obstacles + (1 if next_pos in obstacle_set else 0)
queue.append((next_pos, next_obstacles))
return min_obstacles
# 输入样例
L = 10
S = 2
T = 3
M = 5
obstacles = [1, 3, 5, 7, 9]
# 输出结果
print(min_obstacles_bfs(L, S, T, M, obstacles))
三、知识总结
1. 核心算法
-
BFS:
- 适用于路径规划问题,依次扩展当前层的所有可能状态。
- 时间复杂度为
O(V + E),其中V为节点数,E为边数。
-
0-1 BFS:
- 优化处理权值为
0或1的图,利用双端队列实现优先处理低权值的路径。 - 时间复杂度与普通 BFS 相同,但减少了冗余计算。
- 优化处理权值为
2. 状态定义与转移方程
-
网格问题:
- 状态定义:
dist[i][j]表示从起点到格子(i, j)所需经过的最少黑色格子数。 - 转移方程:
dist[nx][ny] = min(dist[nx][ny], dist[x][y] + grid[nx][ny])。
- 状态定义:
-
植物园问题:
- 状态定义:
current_obstacles表示到达当前位置所需踩到的最少障碍物数量。 - 状态转移:从
current_pos跳跃到next_pos,next_obstacles = current_obstacles + (1 if next_pos in obstacle_set else 0)。
- 状态定义:
3. 时间与空间复杂度
-
网格问题:
- 时间复杂度:
O(n * m),遍历网格的所有节点和边。 - 空间复杂度:
O(n * m),用于存储距离数组。
- 时间复杂度:
-
植物园问题:
- 时间复杂度:
O(L * (T - S)),每个位置最多扩展(T - S)个状态。 - 空间复杂度:
O(L),用于存储队列和已访问节点集合。
- 时间复杂度:
4. 应用场景
-
网格问题:
- 路径规划:机器人或游戏中的路径优化。
- 图像处理:计算从起点到目标点的最优像素路径。
-
植物园问题:
- 跳跃游戏:模拟固定步长范围的跳跃路径。
- 路线设计:从起点到终点的最优路线选择。
五、学习心得
通过学习上述 BFS(广度优先搜索)和 0-1 BFS 的范式,深刻感受到其在解决路径规划、图搜索以及动态规划问题中的强大应用能力。BFS 的核心在于逐层扩展状态,并在每个状态中检查所有可能的下一步。这种方式确保了在无权图中,第一个到达目标节点的路径一定是最短路径。以下是我对 BFS 范式的理解与收获。
BFS 的核心思想与应用
BFS 通过队列实现,借助“先进先出”的特性逐层扩展搜索空间。其最大的优势在于系统性和最优性:通过逐层扩展,它能够确保搜索到的路径是最短的。应用 BFS 时,关键在于:
- 队列管理:将初始状态(起点)加入队列,并逐步扩展其所有合法邻居。
- 访问标记:使用集合或数组记录已访问的节点,避免重复搜索。
- 终止条件:当目标状态被首次访问时,即可返回最优解。
这种范式广泛应用于无权图的最短路径问题、网络流量分析、迷宫求解等领域。在具体实现中,数据结构的选用(如 deque 提供的双端队列)显著提升了算法的效率和可读性。
0-1 BFS 的优势与特性
在带权图中,0-1 BFS 利用双端队列高效处理权值为 0 或 1 的边。其核心在于将权值为 0 的状态加入队列前端优先处理,权值为 1 的状态加入队列后端延后处理。这种优先级的设计,保证了路径权重累加的最优性,同时避免了传统 Dijkstra 算法的复杂性和冗余计算。
在具体实现中,学习到了以下优化技巧:
- 动态优先队列:通过双端队列模拟权重优先级,不需要显式排序。
- 边的处理顺序:动态插入权值较小的状态,使得 BFS 在本质上与权值排序无缝结合。
- 高效性:相比 Dijkstra,0-1 BFS 的时间复杂度为
O(V + E),非常适合处理稀疏图或路径权值单一的问题。
应用场景的多样性
上述范式的学习让我更加体会到 BFS 和 0-1 BFS 的适用性:
- 路径规划:机器人移动、迷宫问题、游戏场景路径优化等问题的最优解。
- 动态规划优化:将动态规划的状态转移映射为图的边权,实现更灵活的状态转移。
- 网络分析:从社交网络的传播模型到通信网络的最短路径计算,都有 BFS 的身影。
总结与感悟
BFS 和 0-1 BFS 的学习不仅帮助我在算法设计上有了更清晰的思路,还让我感受到优雅的算法可以通过巧妙的结构设计大幅降低复杂度。特别是 0-1 BFS 的优先级设计,启发了我在解决实际问题时更多地关注问题的结构特性,而不是机械套用模板。同时,这种范式让我更加体会到数学与计算机科学结合的美感。通过不断的练习和总结,我相信可以将 BFS 和 0-1 BFS 应用于更复杂的问题场景中,实现高效优雅的解决方案。