代码随想录算法训练营 Day44 | 图论 part02

99. 计数孤岛

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。

后续 N 行,每行包含 M 个数字,数字为 1 或者 0。

输出描述

输出一个整数,表示岛屿的数量。如果不存在岛屿,则输出 0。

输入示例

复制代码
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1

输出示例

复制代码
3
cpp 复制代码
// DFS
#include <iostream>
#include <vector>
using namespace std;

// 定义四个方向的移动向量:右、下、左、上
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
// 深度优先搜索函数
void dfs(vector<vector<int>>& graph, vector<vector<bool>>& visited, int x, int y) {
    // 递归终止条件:如果越界、已经访问过、或者是水域(0),则直接返回
    if (x < 0 || x >= graph.size() || y < 0 || y >= graph[0].size() || visited[x][y] || graph[x][y] == 0) {
        return;
    }
    // 标记当前陆地节点为已访问
    visited[x][y] = true;
    // 递归遍历当前节点的四个相邻节点
    // 只要相邻节点是陆地且未访问,就会一直"深挖"下去
    for (int i = 0; i < 4; i++) {
        int curx = x + dir[i][0];
        int cury = y + dir[i][1];
        dfs(graph, visited, curx, cury);
    }
}

int main() {
    int n, m;
    cin >> n >> m; // 读取矩阵的行数 n 和列数 m
    vector<vector<int>> graph(n, vector<int>(m));
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> graph[i][j];
        }
    }
    int res = 0; // 统计岛屿数量
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 发现未访问的陆地,说明找到了一个新岛屿
            if (!visited[i][j] && graph[i][j] == 1) {
                res++;
                dfs(graph, visited, i, j); // 将该岛屿所有相连陆地标记为已访问
            }
        }
    }
    cout << res << endl;
    return 0;
}


// BFS
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

// 定义四个方向的移动向量:右、下、左、上
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
// 广度优先搜索函数
void bfs(vector<vector<int>>& graph, vector<vector<bool>>& visited, int x, int y) {
    // 创建队列,存储待扩展的坐标
    queue<pair<int, int>> que;
    // 起点入队,并立刻标记为已访问(防止同一节点被重复加入队列)
    que.push({x, y});
    visited[x][y] = true;
    while (!que.empty()) {
        // 取出队首元素
        pair<int, int> cur = que.front(); 
        que.pop();
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int curx = cur.first + dir[i][0];
            int cury = cur.second + dir[i][1];
            // 边界检查:如果越界,跳过当前方向
            if (curx < 0 || curx >= graph.size() || cury < 0 || cury >= graph[0].size()) continue;
            // 如果是未访问过的陆地,入队并标记
            if (!visited[curx][cury] && graph[curx][cury] == 1) {
                que.push({curx, cury});
                visited[curx][cury] = true; 
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m; // 读取矩阵的行数 n 和列数 m
    vector<vector<int>> graph(n, vector<int>(m));
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> graph[i][j];
        }
    }
    int res = 0; // 统计岛屿数量
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 发现未访问的陆地,说明找到了一个新岛屿
            if (!visited[i][j] && graph[i][j] == 1) {
                res++;
                bfs(graph, visited, i, j); // 将该岛屿所有相连陆地标记为已访问
            }
        }
    }
    cout << res << endl;
    return 0;
}

总结

1. DFS vs BFS 怎么选?
  • 结论:笔试/面试写 BFS 最稳妥;日常刷水题图省事写 DFS。
对比维度 DFS (递归) BFS (队列)
代码量 极简,几行搞定 较繁琐,需引入 <queue>
致命缺陷 极易栈溢出(网格全为1时,递归深度达 N*M) 无栈溢出风险(空间在堆上分配)
最佳场景 数据规模小(如 < 100x100) 大规模数据,或需要求最短路径时
2. 易错点
  • BFS 绝对不能等"出队"才标记
    • 错误写法:出队时 visited = true,会导致同一节点被四周邻居重复入队,内存爆炸。
    • 正确写法:入队即标记(que.push() 下方紧接着写 visited = true)。
  • DFS 剪枝要写在函数开头
    • 把"越界、已访问、非陆地"三种判断统一写在函数第一行做 return,千万别写在 for 循环里面,代码最干净且不易漏判。
3. 复杂度
  • 时间:O(N×M)。无论哪种搜索,图中每个节点最多进一次栈/队列。
  • 空间:O(N×M)(主要是 visited 数组)。若仅算额外空间:DFS 最坏 O(N×M),BFS 最坏 O(min⁡(N,M))。

100. 最大岛屿的面积

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。后续 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述

输出一个整数,表示岛屿的最大面积。如果不存在岛屿,则输出 0。

输入示例

复制代码
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1

输出示例

复制代码
4
cpp 复制代码
// DFS
#include <iostream>
#include <vector>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int cnt; // 全局变量,用于记录当前岛屿的面积
void dfs(vector<vector<int>>& graph, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y] || graph[x][y] == 0) return;    
    visited[x][y] = true;
    cnt++; // 每访问一个陆地,当前岛屿面积加1
    for (int i = 0; i < 4; i++) {
        int curx = x + dir[i][0];
        int cury = y + dir[i][1];
        if (curx < 0 || curx >= graph.size() || cury < 0 || cury >= graph[0].size()) continue;
        dfs(graph, visited, curx, cury);
    }
}

int main() {
    int n, m; cin >> n >> m;
    vector<vector<int>> graph(n, vector<int>(m));
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> graph[i][j];
        }
    }
    int res = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && graph[i][j] == 1) {
                cnt = 0; // 发现新岛屿前,将计数器清零
                dfs(graph, visited, i, j);
                res = max(res, cnt); // 更新最大岛屿面积
            }
        }
    }
    cout << res << endl;
    return 0;
}


// BFS
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int cnt; // 全局变量,记录当前岛屿面积
void bfs(vector<vector<int>>& graph, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que;
    que.push({x, y});
    visited[x][y] = true;
    cnt++; // 起点入队时,面积加1    
    while (!que.empty()) {
        pair<int, int> cur = que.front(); que.pop();
        for (int i = 0; i < 4; i++) {
            int curx = cur.first + dir[i][0];
            int cury = cur.second + dir[i][1];
            if (curx < 0 || curx >= graph.size() || cury < 0 || cury >=graph[0].size()) continue;
            
            if (!visited[curx][cury] && graph[curx][cury] == 1) {
                que.push({curx, cury});
                visited[curx][cury] = true;
                cnt++; // 新节点入队时,面积加1
            }
        }
    }
}

int main() {
    int n, m; cin >> n >> m;
    vector<vector<int>> graph(n, vector<int>(m));
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> graph[i][j];
        }
    }    
    int res = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && graph[i][j] == 1) {
                cnt = 0; // 发现新岛屿前,计数器清零
                bfs(graph, visited, i, j);
                res = max(res, cnt); // 取最大值
            }
        }
    }
    cout << res << endl;
    return 0;
}

总结

1. 唯一核心变化:加计数

搜索逻辑不变,外层用 res = max(res, cnt) 取最大值。

2. 易错点:cnt++ 的位置
  • DFS:跟在 visited = true 后面。
  • BFS:必须写在入队时(和 visited = true 绑定)。写在出队时会导致同一节点被重复计数,面积算错。
相关推荐
minji...2 小时前
Linux 网络套接字编程(三)UDP服务器与客户端实现:Windows与Linux通信,新增字典翻译功能的 UDP 通信
linux·服务器·开发语言·网络·windows·算法·udp
Robot_Nav2 小时前
Hybrid A* 算法文献解读
算法·路径规划·hybrid a
WolfGang0073212 小时前
代码随想录算法训练营 Day41 | 单调栈 part01
算法·动态规划
嘻嘻哈哈樱桃2 小时前
牛客经典101题解题集--二分查找/排序
数据结构·算法·职场和发展
lihihi2 小时前
CF1992F Valuable Cards
算法
Omics Pro2 小时前
癌症亚型分类新型多组学整合框架
大数据·人工智能·python·算法·机器学习·分类·数据挖掘
熬夜敲代码的猫2 小时前
C++:模板精讲
c++·算法·模板
MegaDataFlowers2 小时前
3.无重复字符的最长子串
算法
人道领域2 小时前
【LeetCode刷题日记】20.有效的括号
算法·leetcode·职场和发展