【图的深度优先、广度优先】200. 岛屿数量

题目描述

(中等)给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

解题思路

经典的矩阵类题目,矩阵又是图(Graph)的典型类型之一。之前的科研工作就是处理这种数据类型,简单的奖基于图匹配算法的无人机视觉导航定位。

根据题意,此处的二位矩阵可以视为在水平竖直方向上相邻的 ‘1’ 存在边的无向图。求岛屿数量的本质上就是求无向图内连通分量的个数。直觉上,更容易联想到并查集。但考虑到本文作为图算法的入门级教程,这里主要介绍图的深度优先广度优先算法。

算法很简单,与树的遍历十分相似,可以说是一个套路,可以简单的将树理解为具有单一有向边的图(即若存在结点A->结点B,则必不存在结点B->结点A)。而无向图的特点即存在边连接的结点可以相互到达,这种特质导致图中的结点存在被重复访问的可能。故相较于树的遍历算法,图需要记录访问的手段。

继续讨论本题思路,无论广度还是深度,整体思路是一样的:遍历二维矩阵,搜索值为 ‘1’ 的结点,以此结点开始一轮新的深度/广度,每次深度/广度时记录访问避免重复访问,最后深度/广度的总次数即为岛屿数量。

代码实现

深度优先

创建visited数组记录访问,

class Solution {
	//三点钟方向顺时针遍历
    const int dx[4] = {0, 1, 0, -1};
    const int dy[4] = {1, 0, -1, 0}; 
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int ans = 0;
        //创建数组记录访问
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        //遍历整个矩阵,记录总深度次数
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == '1' && !visited[i][j]){
                    ans++;
                    dfs(grid, visited, i, j);
                }
            }
        }
        return ans;
    }
	
	//图的深度优先算法实现
    void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int i, int j){
    	//校验下标合法和确定递归逻辑
        if(i >= 0 && j >= 0 && i < grid.size() && j < grid[0].size() && grid[i][j] == '1' && !visited[i][j]){
            visited[i][j] = true;
            for(int k = 0; k < 4; k++){
                int nx = i + dx[k];
                int ny = j + dy[k];
                dfs(grid, visited, nx, ny);
            }
        }
    }
};

在整个算法流程内,矩阵数据 ‘1’ 只需读取一次,无需维护,故可以在原矩阵上维护访问记录,无需创建visited数据,节省内存开销。

class Solution {
    const int dx[4] = {0, 1, 0, -1};
    const int dy[4] = {1, 0, -1, 0}; 
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int ans = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == '1'){
                    ans++;
                    dfs(grid, i, j);
                }
            }
        }
        return ans;
    }

    void dfs(vector<vector<char>>& grid, int i, int j){
        if(i >= 0 && j >= 0 && i < grid.size() && j < grid[0].size() && grid[i][j] == '1'){
            //将访问过的陆地置为0,配合深度递归逻辑,即可避免重复访问
            grid[i][j] = 0;
            for(int k = 0; k < 4; k++){
                int nx = i + dx[k];
                int ny = j + dy[k];
                dfs(grid, nx, ny);
            }
        }
    }
};

运行结果
运行结果

广度优先

图的广度优先与树的层序遍历极度相似,几乎是一个模板,都依赖一个队列管理结点的出队入队。

class Solution {
    const int dx[4] = {0, 1, 0, -1};
    const int dy[4] = {1, 0, -1, 0}; 
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int ans = 0;
        //创建队列
        queue<pair<int, int>> que;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
            	//满足条件,开始广度遍历,记录广度次数
                if(grid[i][j] == '1'){
                    ans++;
                    //入队
                    que.push({i, j});
                    //注意置为'0'
                    grid[i][j] = '0';
                    while(!que.empty()){
                        auto note = que.front();
                        //出队
                        que.pop();
                        for(int k = 0; k < 4; k++){
                            int nx = note.first + dx[k];
                            int ny = note.second + dy[k];
                            if(nx >= 0 && ny >= 0 && nx < grid.size() && ny < grid[0].size() && grid[nx][ny] == '1'){
                                que.push({nx, ny});
                                //注意置为'0'
                                grid[nx][ny] = '0';
                            }
                        }
                    }
                }
            }
        }
        return ans;
    }

};

运行结果
运行结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值