来源:算法讲解058【必备】洪水填充 · 2026-10-23
依据:视频字幕 2000 字
洪水填充(Flood Fill)就是:从某个格子出发,用 DFS/BFS 把与它「上下左右连通」的同类格子全部标记掉,外层按顺序扫一遍矩阵,每触发一次填充就计一片连通块。
二维网格上统计连通块数量 / 给连通块染色 / 判断某点属于哪片区域。典型题面:'1' 表示陆地、'0' 表示水,上下左右相连的陆地算一片岛,问岛的数量。也用于:扫雷展开、图像区域染色、迷宫可达性、网格中「同色区域」的合并统计。
(i, j) 表示第 i 行第 j 列,下标从 0 开始(竞赛里也常见从 1 开始,看题目输入)。想象往网格里倒水:水从某个陆地格子渗进去,凡是能连到的陆地都会被淹到,淹过的格子就变成「已访问」。因为连通关系是等价关系(自反、对称、传递),所以:
所以「触发 DFS 的次数」= 「连通块的数量」,这就是正确性的核心。
ans = 0。i 从 0 到 n-1,j 从 0 到 m-1: - 若 grid[i][j] 是陆地('1'):
- ans++
- 调用 dfs(i, j) 把整片连通块标记掉(改成 '0' 或 '2' 或访问数组置 true)。
dfs(i, j):- 越界 或 当前格子不是陆地 → 直接返回。
- 把 grid[i][j] 标记为已访问(改成非陆地字符)。
- 递归四个方向:(i-1,j) (i+1,j) (i,j-1) (i,j+1)。
ans。网格(1=陆地,0=水):
1 1 0 0
1 0 0 1
0 0 1 1
0 0 0 0
按行优先扫描:
| 步骤 | 扫描到 | 动作 | 网格变化(2=已淹) | ans |
|---|---|---|---|---|
| 1 | (0,0)=1 | 触发 DFS,淹 (0,0)(0,1)(1,0) | 2 2 0 0 / 2 0 0 1 / 0 0 1 1 / 0 0 0 0 | 1 |
| 2 | (0,1)=2 | 跳过 | 不变 | 1 |
| 3 | (1,3)=1 | 触发 DFS,淹 (1,3) | ... / 2 0 0 2 / 0 0 1 1 / ... | 2 |
| 4 | (2,2)=1 | 触发 DFS,淹 (2,2)(2,3) | ... / 0 0 2 2 / ... | 3 |
| 5 | 扫完 | 返回 | — | 3 |
结果 3 片岛。
import sys
sys.setrecursionlimit(1 << 25)
def solve():
data = sys.stdin.read().split()
if not data:
return
n, m = int(data[0]), int(data[1])
grid = [list(data[2 + i * m]) for i in range(n)] # 每行一个字符串
def dfs(i, j):
if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] != '1':
return
grid[i][j] = '2' # 标记已访问,不撤销
dfs(i - 1, j)
dfs(i + 1, j)
dfs(i, j - 1)
dfs(i, j + 1)
ans = 0
for i in range(n):
for j in range(m):
if grid[i][j] == '1':
ans += 1
dfs(i, j)
print(ans)
solve()
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<string> g; // g[i][j] 是 '0'/'1',用 string 省内存、cache 友好
void dfs(int i, int j) {
if (i < 0 || i >= n || j < 0 || j >= m || g[i][j] != '1') return;
g[i][j] = '2'; // 标记已访问(不撤销)
dfs(i - 1, j);
dfs(i + 1, j);
dfs(i, j - 1);
dfs(i, j + 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
g.resize(n);
for (int i = 0; i < n; ++i) cin >> g[i];
int ans = 0;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
if (g[i][j] == '1') {
++ans;
dfs(i, j);
}
cout << ans << '\n';
return 0;
}
'1' 陆地 '0' 水,问岛屿数量 / 最大岛屿面积 / 被围绕的区域」。// 网格洪水填充模板:四连通,统计连通块数量
// 输入:n 行 m 列,g[i] 为长度 m 的字符串,'1' 为陆地
int n, m;
vector<string> g;
void dfs(int i, int j) {
if (i < 0 || i >= n || j < 0 || j >= m || g[i][j] != '1') return;
g[i][j] = '2';
dfs(i - 1, j); dfs(i + 1, j); dfs(i, j - 1); dfs(i, j + 1);
}
int countIslands() {
int ans = 0;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
if (g[i][j] == '1') { ++ans; dfs(i, j); }
return ans;
}
若 n·m 可能 ≥ 1e6,把 dfs 换成显式栈的迭代版(见「竞赛补充」),避免爆栈。
i/j 越界,再访问 g[i][j],否则 UB/RE。C++ 里 && 短路能救你,但别写反。g、ans 要重置;vector<string> 重新 resize 或 clear。'1' 不是数字 1,比较别写错;若题目给的是 0/1 整数矩阵,判断条件相应改。g 省空间,但如果后面还要用原网格(比如多问),就得用 vector<vector<char>> vis。ans = max(ans, dfs(i,j))。(di,dj)。'2' 改成连通块编号 1,2,3...,可回答「某点属于哪片」。list/map 存网格或邻接——常数爆炸,直接 vector<string> 或 vector<vector<char>>。ans,第二组答案翻倍。g[i][j] != '1' 写成 != 1(字符和整数比较),永远为真,全图不淹。以下字幕未提及,按竞赛需要补充。
1. 迭代 DFS(防爆栈)
void dfsIter(int si, int sj) {
static int sti[1 << 22], stj[1 << 22]; // 按最大 n*m 开,或用 vector
int top = 0;
sti[top] = si; stj[top] = sj; ++top;
g[si][sj] = '2';
while (top) {
--top;
int i = sti[top], j = stj[top];
static const int di[4] = {-1, 1, 0, 0};
static const int dj[4] = {0, 0, -1, 1};
for (int d = 0; d < 4; ++d) {
int ni = i + di[d], nj = j + dj[d];
if (ni < 0 || ni >= n || nj < 0 || nj >= m || g[ni][nj] != '1') continue;
g[ni][nj] = '2';
sti[top] = ni; stj[top] = nj; ++top;
}
}
}
注意:入栈时就标记,避免同一格多次入栈导致栈溢出。
2. BFS 版(求最短步数/分层时用)
void bfs(int si, int sj) {
queue<pair<int,int>> q;
q.push({si, sj}); g[si][sj] = '2';
while (!q.empty()) {
auto [i, j] = q.front(); q.pop();
// 四方向扩展,入队即标记
}
}
3. 方向数组写法(比四次递归调用更整洁,也便于改八连通)
static const int di[4] = {-1, 1, 0, 0};
static const int dj[4] = {0, 0, -1, 1};
4. 多测清空模板
while (T--) {
cin >> n >> m;
g.assign(n, string(m, '0'));
for (int i = 0; i < n; ++i) cin >> g[i];
// ... 计算
}
5. 大规模输入优化:n·m 到 2e6 时,cin >> string 已足够快(sync_with_stdio(false) 后);若仍慢,用 fread 手写读入。
题 1:给定 3×3 网格
1 0 1
0 1 0
1 0 1
四连通下有几片岛?
5 片。每个 1 上下左右都是 0,互不连通,各自成岛。
</details>
题 2:把题 1 改成八连通,答案是多少?
1 片。四个角的 1 都通过中心 (1,1) 的 1 斜向连通,全部连成一片。
</details>
题 3:n=m=1000 的网格,整张图全是 1,用递归 DFS 会怎样?怎么改?
递归深度可达 1e6,C++ 默认栈约 8MB,每层几十字节,必爆栈(RE)。改成显式栈的迭代 DFS 或 BFS;或把网格按行优先顺序用并查集合并(但常数更大)。竞赛里 1e6 网格优先迭代 DFS。
</details>