1. 题目解析与问题建模
P1162 "填涂颜色"是一道经典的深度优先搜索(DFS)算法练习题,主要考察对二维矩阵的遍历和标记能力。题目通常给出一个由0和1组成的N×N矩阵,要求将数字1围成的封闭区域内的所有0变为2。
1.1 问题核心理解
这道题的关键在于识别"被1包围的区域"。从矩阵边缘出发的0都不属于被包围区域,只有完全被1围住的0才需要染色。这类似于图像处理中的"泛洪填充"算法,我们需要:
- 识别所有与边缘相连的0(这些0不被1包围)
- 剩下的未被访问的0就是需要染色的目标
1.2 输入输出分析
典型输入格式:
code复制3
1 1 1
1 0 1
1 1 1
期望输出:
code复制1 1 1
1 2 1
1 1 1
2. DFS算法设计与实现
2.1 基础DFS实现
标准DFS解法需要以下步骤:
python复制n = int(input())
grid = [list(map(int, input().split())) for _ in range(n)]
# 四个方向的移动向量
directions = [(-1,0),(1,0),(0,-1),(0,1)]
def dfs(i, j):
if 0 <= i < n and 0 <= j < n and grid[i][j] == 0:
grid[i][j] = -1 # 临时标记边缘连接的0
for di, dj in directions:
dfs(i+di, j+dj)
# 从边缘开始DFS
for i in range(n):
dfs(i, 0) # 左边缘
dfs(i, n-1) # 右边缘
for j in range(n):
dfs(0, j) # 上边缘
dfs(n-1, j) # 下边缘
# 染色处理
for i in range(n):
for j in range(n):
if grid[i][j] == 0:
grid[i][j] = 2
elif grid[i][j] == -1:
grid[i][j] = 0
# 输出结果
for row in grid:
print(' '.join(map(str, row)))
2.2 算法优化技巧
- 方向数组优化:使用静态方向数组比动态生成更高效
- 边界检查合并:将边界检查与值判断合并减少条件分支
- 原地修改:直接修改原矩阵避免额外空间开销
优化后的DFS函数:
python复制def dfs(i, j):
if i < 0 or i >= n or j < 0 or j >= n or grid[i][j] != 0:
return
grid[i][j] = -1
for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
dfs(i+di, j+dj)
3. BFS替代方案与比较
3.1 BFS实现
对于大规模矩阵,BFS通常有更好的性能表现:
python复制from collections import deque
def bfs(i, j):
q = deque([(i, j)])
grid[i][j] = -1
while q:
x, y = q.popleft()
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x+dx, y+dy
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 0:
grid[nx][ny] = -1
q.append((nx, ny))
3.2 DFS与BFS对比
| 特性 | DFS | BFS |
|---|---|---|
| 实现方式 | 递归/栈 | 队列 |
| 空间复杂度 | O(max(m,n)) | O(min(m,n)) |
| 适用场景 | 深路径/拓扑排序 | 最短路径/层次遍历 |
| 本题表现 | 代码简洁但可能栈溢出 | 更稳定,适合大矩阵 |
4. 常见错误与调试技巧
4.1 典型错误案例
-
边界处理不全:只处理了四个角而漏掉了整个边缘
python复制# 错误示例 - 漏掉了中间边缘点 for i in [0, n-1]: for j in [0, n-1]: dfs(i, j) -
标记冲突:使用1或2作为临时标记导致混淆
python复制# 错误示例 - 使用了与最终结果冲突的标记 grid[i][j] = 2 # 临时标记,但2是最终需要的值 -
方向遗漏:少考虑了某些移动方向
python复制# 错误示例 - 漏掉了垂直方向 directions = [(-1,0), (1,0)] # 缺少左右移动
4.2 调试方法
- 小规模测试:先用3×3矩阵验证基本逻辑
- 打印中间状态:在DFS后输出临时标记结果
python复制print("After DFS:") for row in grid: print(row) - 可视化工具:使用ASCII艺术展示矩阵变化
5. 性能优化与进阶思路
5.1 多源点BFS
同时从所有边缘点开始BFS,减少重复操作:
python复制q = deque()
for i in range(n):
for j in [0, n-1]:
if grid[i][j] == 0:
q.append((i,j))
grid[i][j] = -1
for j in range(n):
for i in [0, n-1]:
if grid[i][j] == 0:
q.append((i,j))
grid[i][j] = -1
while q:
x, y = q.popleft()
# ...剩余BFS逻辑...
5.2 并查集解法
虽然不如DFS/BFS直观,但可以练习并查集的应用:
python复制parent = [i for i in range(n*n + 1)] # 多一个虚拟边界节点
def find(u):
while parent[u] != u:
parent[u] = parent[parent[u]]
u = parent[u]
return u
def union(u, v):
root_u = find(u)
root_v = find(v)
if root_u != root_v:
parent[root_v] = root_u
# 将边缘0连接到虚拟边界节点
boundary = n*n
for i in range(n):
for j in [0, n-1]:
if grid[i][j] == 0:
union(i*n + j, boundary)
for j in range(n):
for i in [0, n-1]:
if grid[i][j] == 0:
union(i*n + j, boundary)
# 内部连通处理
for i in range(1, n-1):
for j in range(1, n-1):
if grid[i][j] == 0:
# 检查四个方向
for di, dj in [(-1,0),(0,-1)]: # 只需检查左上避免重复
ni, nj = i+di, j+dj
if grid[ni][nj] == 0:
union(i*n + j, ni*n + nj)
# 染色处理
bound_root = find(boundary)
for i in range(n):
for j in range(n):
if grid[i][j] == 0 and find(i*n + j) != bound_root:
grid[i][j] = 2
5.3 迭代DFS避免栈溢出
对于特别大的矩阵(如1000×1000),递归DFS可能导致栈溢出,可改用显式栈:
python复制def dfs_iterative(i, j):
stack = [(i, j)]
while stack:
x, y = stack.pop()
if 0 <= x < n and 0 <= y < n and grid[x][y] == 0:
grid[x][y] = -1
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
stack.append((x+dx, y+dy))
6. 实际应用与扩展
6.1 图像处理中的应用
这种算法在图像处理中称为"泛洪填充"(Flood Fill),常用于:
- 照片编辑软件的魔术棒工具
- 图形绘制软件的油漆桶工具
- 图像分割中的连通区域分析
6.2 算法变种
- 多颜色填充:支持多种颜色同时填充不同区域
- 非四连通区域:考虑八连通(包括对角线方向)
- 动态填充:实时响应用户交互的填充操作
6.3 竞赛中的常见变形
- 三维填充:将二维矩阵扩展为三维立方体
- 动态障碍物:矩阵中的障碍物(1)会随时间变化
- 最小填充代价:不同位置的填充操作有不同的代价
提示:在竞赛编程中,这类题目通常会设置较大的数据规模(如n≤1000),因此务必注意算法的时间复杂度。DFS/BFS的时间复杂度都是O(n²),对于1000×1000的矩阵是完全可行的。
