【日常做题】代码随想录 图论之dfs和bfs

👨‍💻 关于作者:会编程的土豆

"不是因为看见希望才坚持,而是坚持了才看见希望。"

你好,我是会编程的土豆,一名热爱后端技术的Java学习者。

📚 正在更新中的专栏:

💕作者简介:后端学习者

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. 遍历每个格子

  2. 如果是 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 本质就是:从一个点出发,用队列一层一层把整个连通块全部"扩散清空"。

相关推荐
计算机安禾2 小时前
【数据结构与算法】第48篇:算法思想(三):贪心算法
c语言·开发语言·数据结构·算法·贪心算法·代理模式·图论
故事和你9113 小时前
洛谷-数据结构1-1-线性表1
开发语言·数据结构·c++·算法·leetcode·动态规划·图论
木井巳17 小时前
【递归算法】组合总和
java·算法·leetcode·决策树·深度优先·剪枝
计算机安禾19 小时前
【数据结构与算法】第42篇:并查集(Disjoint Set Union)
c语言·数据结构·c++·算法·链表·排序算法·深度优先
liuyao_xianhui1 天前
map和set_C++
java·开发语言·数据结构·c++·算法·宽度优先
会编程的土豆1 天前
常用算法里的细节
数据结构·c++·算法·图论
历程里程碑2 天前
二叉树---二叉树的最大深度
大数据·数据结构·算法·elasticsearch·搜索引擎·全文检索·深度优先
kyle~2 天前
BFS(广度优先搜索)与 DFS (深度优先搜索)
c++·算法·深度优先·宽度优先
dsyyyyy11013 天前
计数孤岛(DFS和BFS解决)
算法·深度优先·宽度优先