
👨💻 关于作者:会编程的土豆
"不是因为看见希望才坚持,而是坚持了才看见希望。"
你好,我是会编程的土豆,一名热爱后端技术的Java学习者。
📚 正在更新中的专栏:
-
《数据结构与算法》😊😊😊
-
《leetcode hot 100》🥰🥰🥰🤩🤩🤩
-
《数据库mysql》
💕作者简介:后端学习者
1.

dfs版
cpp
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
vector<vector<int>>edge;
long long sum = 0;
int dx[4] = { 1,-1,0,0 };
int dy[4] = { 0,0,1,-1 };
int N, M;
void dfs(int i, int j)
{
int x = i; int y = j;
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && edge[nx][ny] == 1)
{
edge[nx][ny] = 0;
dfs(nx, ny);
}
}
}
int main()
{
cin >> N >> M;
edge.resize(N + 1, vector<int>(M + 1, 0));
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
cin >> edge[i][j];
}
}
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
if (edge[i][j] == 1)
{
sum++;
dfs(i, j);
}
}
}
cout << sum << endl;
return 0;
}
用 DFS 解决"岛屿数量问题":从代码到思路一次讲透
这类题在数据结构与算法里非常经典,本质就是:在一个二维网格中,统计连通块的数量。你这段代码写的是最标准的解法之一------DFS(深度优先搜索)。
很多人一开始只是"会写",但不太清楚为什么这样写,这篇文章就带你把这段代码彻底理解。
一、问题本质
给你一个 N×M 的网格:
-
1 表示陆地
-
0 表示水
要求:
统计有多少个"岛屿"(连通的 1)
举个例子
1 1 0 0
1 0 0 1
0 0 1 1
这里有 3 个岛屿。
二、核心思路(非常重要)
一句话总结:
遇到一个 1,就把整个连通块"淹掉"
什么意思?
-
扫描整个地图
-
遇到一个 1 → 说明发现新岛屿
-
用 DFS 把和它连在一起的所有 1 全部改成 0
-
岛屿数量 +1
三、DFS 在这里干了什么?
来看你的代码:
cpp
void dfs(int i, int j)
{
int x = i; int y = j;
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && edge[nx][ny] == 1)
{
edge[nx][ny] = 0;
dfs(nx, ny);
}
}
}
关键点拆解
1. 四个方向
int dx[4] = { 1,-1,0,0 };
int dy[4] = { 0,0,1,-1 };
表示:
下 上 右 左
2. 向四周扩展
int nx = x + dx[i];
int ny = y + dy[i];
就是在找相邻的格子。
3. 判断是否合法
nx >= 1 && nx <= N && ny >= 1 && ny <= M
防止越界。
4. 判断是否是陆地
edge[nx][ny] == 1
只有是 1 才继续扩展。
5. 标记已访问
edge[nx][ny] = 0;
这一步非常关键:
防止重复访问(否则会死循环)
四、主函数逻辑
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
if (edge[i][j] == 1)
{
sum++;
dfs(i, j);
}
}
}
逻辑解释
-
遍历每个格子
-
如果是 1:
-
说明发现一个新岛
-
sum++ -
用 DFS 把整个岛"清空"
-
五、完整执行流程(重点理解)
假设某个点是 1:
发现一个岛 → sum++
然后:
DFS 把整个连通区域全部变成 0
这样:
-
同一个岛只会被统计一次
-
后续不会重复计数
六、时间复杂度
O(N × M)
原因:
-
每个点最多访问一次
-
DFS 不会重复访问
七、空间复杂度
递归 DFS:
最坏 O(N × M)
比如整个图都是 1,会递归很深。
八、常见错误
1. 忘记标记访问
edge[nx][ny] = 0;
会导致无限递归。
2. 越界没判断
nx >= 1 && nx <= N
必须写!
3. 没有在主循环中判断 1
if(edge[i][j] == 1)
否则会重复 DFS。
九、可以优化的点
你的代码其实可以更规范一点,比如:
优化:起点也要清零
if (edge[i][j] == 1)
{
sum++;
edge[i][j] = 0;
dfs(i, j);
}
避免重复访问当前点。
十、一句话总结
DFS 在网格问题中的本质就是:从一个点出发,把整个连通块全部"吃掉"。
bfs版
cpp
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
vector<vector<int>>edge;
long long sum = 0;
int dx[4] = { 1,-1,0,0 };
int dy[4] = { 0,0,1,-1 };
int N, M;
queue <pair<int, int>> q;
void bfs(int i, int j)
{
while (!q.empty())
{
int x = q.front().first;
int y = q.front().second;
q.pop();
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= N && ny >= 1 && ny <= M&&edge[nx][ny]==1)
{
edge[nx][ny] = 0;
q.push({ nx,ny });
}
}
}
}
int main()
{
cin >> N >> M;
edge.resize(N + 1, vector<int>(M + 1, 0));
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
cin >> edge[i][j];
}
}
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
if (edge[i][j] == 1)
{
sum++;
q.push({ i,j });
bfs(i, j);
}
}
}
cout << sum << endl;
return 0;
}
用 BFS 解决"岛屿数量问题":从代码细节到思路彻底吃透
在上一种写法中我们用 DFS 做了连通块统计,这一篇我们用 BFS(广度优先搜索) 来解决同一个问题,并把你这段代码彻底讲清楚。
很多同学写 BFS 能跑通,但对"为什么这么写"理解不够,这里我们把逻辑拆开讲清楚。
一、问题本质再回顾
给你一个二维网格:
-
1 表示陆地
-
0 表示水
目标:
统计有多少个"岛屿"(上下左右连通的 1)
二、BFS 的核心思想
一句话总结:
从一个点出发,一层一层把整个连通块全部扩展完
和 DFS 的区别:
-
DFS:一条路走到黑(递归)
-
BFS:一层一层扩展(队列)
三、你的代码结构分析
1. 方向数组
int dx[4] = { 1,-1,0,0 };
int dy[4] = { 0,0,1,-1 };
表示四个方向:
下 上 右 左
2. BFS函数
cpp
void bfs(int i, int j)
{
while (!q.empty())
{
int x = q.front().first;
int y = q.front().second;
q.pop();
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && edge[nx][ny] == 1)
{
edge[nx][ny] = 0;
q.push({ nx,ny });
}
}
}
}
四、BFS 执行过程详解
第一步:发现新岛屿
cpp
if (edge[i][j] == 1)
{
sum++;
q.push({ i,j });
bfs(i, j);
}
含义:
发现一个 1 → 新岛屿 → sum++
然后:
把这个点丢进队列,开始"扩散"
第二步:队列扩展
队列:[(起点)]
进入 BFS:
-
取出一个点
-
向四个方向扩展
-
找到新的 1 → 入队
第三步:逐层扩展
举个例子:
1 1 0
1 0 0
过程:
(1,1) 入队
↓
扩展到 (1,2)、(2,1)
↓
再扩展它们的邻居
↓
直到整个岛屿全部变成 0
五、一个关键问题(你这段代码的小坑)
你现在的代码:
q.push({ i,j });
bfs(i, j);
但你没有把起点标记为 0
会发生什么?
起点仍然是 1
虽然一般不会死循环,但:
-
逻辑不严谨
-
有重复访问风险
正确写法(必须改)
if (edge[i][j] == 1)
{
sum++;
edge[i][j] = 0; // 关键:先标记
q.push({ i,j });
bfs(i, j);
}
六、为什么一定要"先标记"?
核心原因:
防止一个点被多次加入队列
如果不标记:
-
可能从不同方向重复访问
-
队列变大,效率变差
七、时间复杂度
O(N × M)
原因:
-
每个点最多进队一次
-
每条边最多访问一次
八、空间复杂度
O(N × M)
最坏情况:
-
整个图都是 1
-
队列会存很多节点
九、DFS vs BFS(快速对比)
| 项目 | DFS | BFS |
|---|---|---|
| 实现 | 递归 | 队列 |
| 访问方式 | 一条路走到底 | 一层一层扩展 |
| 空间 | 递归栈 | 队列 |
| 使用场景 | 连通块、回溯 | 最短路径、层序 |
十、代码优化版本(推荐写法)
cpp
void bfs(int i, int j)
{
queue<pair<int,int>> q;
q.push({i,j});
edge[i][j] = 0;
while (!q.empty())
{
auto [x, y] = q.front();
q.pop();
for (int k = 0; k < 4; k++)
{
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && edge[nx][ny] == 1)
{
edge[nx][ny] = 0;
q.push({nx, ny});
}
}
}
}
优点:
-
队列局部化(更安全)
-
逻辑更清晰
-
不依赖全局变量
十一、一句话总结
BFS 本质就是:从一个点出发,用队列一层一层把整个连通块全部"扩散清空"。