代码随想录 day51 图论 1-6学习

99. 岛屿数量

卡码网题目链接(ACM模式)(opens new window)
题目描述:
给定一个由 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

提示信息
根据测试案例中所展示,岛屿数量共有 3 个,所以输出 3。

数据范围:
1 <= N, M <= 50

思考

注意题目中每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

也就是说斜角度链接是不算了。

这道题题目是 DFS,BFS,并查集,基础题目。

本题思路,是用遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。

在遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。

那么如何把节点陆地所能遍历到的陆地都标记上呢,就可以使用 DFS,BFS或者并查集。

# 深度优先搜索
以下代码使用dfs实现,如果对dfs不太了解的话,建议按照代码随想录的讲解顺序学习。

c++ code 版本一

// 版本一
#include 
#include 
using namespace std;

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向   上、右、左、下
void dfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];     // 当前节点的四个方向
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
        if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) { // 没有访问过的 同时 是陆地的

            visited[nextx][nexty] = true;
            dfs(grid, visited, nextx, nexty);
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));    // 获取 grid
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    vector<vector<bool>> visited(n, vector<bool>(m, false));  // 所有的网格都没有访问过

    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {   // 如果没有访问过,且有岛屿 就进行操作。
                visited[i][j] = true;
                result++; // 遇到没访问过的陆地,+1        // 岛屿的数量增加一个
                dfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
            }
        }
    }

    cout << result << endl;
}

看了上面的代码, 我依然有点蒙圈

回忆一下 (0,0)的四个方向

         (0, 1)
(-1, 0)  (0, 0)  (1, 0)
         (0,-1)
这样定义的四个方向都是水平于(0, 0) 或者 垂直于(0, 0)。也就是开始说的: 斜角度链接是不算了。
1 1
1 0
也算是一个岛屿, 满足 水平 or 垂直。

code python

def dfs(graph, visited, x, y):
    dir = [(0,1), (1, 0), (-1,0), (0, -1)]   # 四个方向   上、右、左、下
    for i in range(4):
        nextx = x + dir[i][0]
        nexty = y + dir[i][1]   # 当前节点的四个方向
        if nextx < 0 or nextx >= len(graph) or nexty < 0 or nexty >= len(graph[0]): continue   #  越界了,直接跳过
        if (not visited[nextx][nexty] and graph[nextx][nexty] == 1):
            visited[nextx][nexty] = True
            dfs(graph, visited, nextx, nexty)

def main():
    n, m = [int(v) for v in input().split(' ')]   # 行数 列数
    graph = []
    visited = [[False for _ in range(m)] for _ in range(n)]     # 所有的网格都没有访问过
    result = 0
    n1 = n
    while n1:   # 获取 grid
        n1 -= 1
        v = [int(i) for i in input().split(' ')]
        graph.append(v)

    for i in range(n):
        for j in range(m):
            if (visited[i][j] == False and graph[i][j]==1):   # 如果没有访问过,且有岛屿 就进行操作。
                visited[i][j] = True
                result += 1                   # 遇到没访问过的陆地,+1        // 岛屿的数量增加一个
                dfs(graph, visited, i, j)     # 将与其链接的陆地都标记上 true
    return result

result = main()
很多录友可能有疑惑,为什么 以上代码中的dfs函数,没有终止条件呢? 感觉递归没有终止很危险。

其实终止条件 就写在了 调用dfs的地方,如果遇到不合法的方向,直接不会去调用dfs。

当然也可以这么写:

c++ code 版本二

#include 
#include 
using namespace std;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
    visited[x][y] = true; // 标记访问过
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
        dfs(grid, visited, nextx, nexty);
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    vector<vector<bool>> visited(n, vector<bool>(m, false));

    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {
                result++; // 遇到没访问过的陆地,+1
                dfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
            }
        }
    }
    cout << result << endl;
}
这里大家应该能看出区别了,无疑就是版本一中 调用dfs 的条件判断 放在了 版本二 的 终止条件位置上。

版本一的写法是 :下一个节点是否能合法已经判断完了,传进dfs函数的就是合法节点。

版本二的写法是:不管节点是否合法,上来就dfs,然后在终止条件的地方进行判断,不合法再return。

理论上来讲,版本一的效率更高一些,因为避免了 没有意义的递归调用,在调用dfs之前,就做合法性判断。 但从写法来说,可能版本二 更利于理解一些。(不过其实都差不太多)

很多同学看了同一道题目,都是dfs,写法却不一样,有时候有终止条件,有时候连终止条件都没有,其实这就是根本原因,两种写法而已。

# 总结
其实本题是 dfs,bfs 模板题,但正是因为是模板题,所以大家或者一些题解把重要的细节都很忽略了,我这里把大家没注意的但以后会踩的坑 都给列出来了。

本篇我只给出的dfs的写法,大家发现我写的还是比较细的,那么后面我再单独给出本题的bfs写法,虽然是模板题,但依然有很多注意的点,敬请期待!

99. 岛屿数量

卡码网题目链接(ACM模式)
题目描述:
给定一个由 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
提示信息
根据测试案例中所展示,岛屿数量共有 3 个,所以输出 3。

数据范围:
1 <= N, M <= 50

思路

注意题目中每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
也就是说斜角度链接是不算了,

这道题题目是 DFS,BFS,并查集,基础题目。
本题思路:遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。
再遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。
那么如果把节点陆地所能遍历到的地都标记上呢,就可以使用 DFS,BFS或者并查集。

# 广度优先搜索

如果不熟悉广搜,建议先看 广搜理论基础。  (队列  栈  数组)

不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节:

根本原因是只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过。

很多同学可能感觉这有区别吗?

如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。

超时写法 (从队列中取出节点再标记,注意代码注释的地方)

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向   无论深搜还是广搜,都需要对给节点的上下左右进行扫荡搜索。没有漏网之鱼
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que;    // 队列的定义
    que.push({x, y});         // 第一个节点加入队列
    while(!que.empty()) {
        pair<int ,int> cur = que.front(); que.pop();  // 先取出队列的值, 再删除队列开头元素
        int curx = cur.first;
        int cury = cur.second;
        visited[curx][cury] = true; // 从队列中取出在标记走过, 对于处理过的元素进行标记, 并对该节点的上下左右进行搜索
        for (int i = 0; i < 4; i++) {
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
            if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
                que.push({nextx, nexty});   // 对于遍历过的元素没有进行标记, 则下次到达(该节点的上下左右,还是需要 push() ,然后在重新冗余进行处理,消耗大量时间  visited[nextx][nexty] = true
            }
        }
    }

}


加入队列 就代表走过,立刻标记,正确写法: (注意代码注释的地方)

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void bfs(vector<vector<char>>& grid, 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();
        int curx = cur.first;
        int cury = cur.second;
        for (int i = 0; i < 4; i++) {
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
            if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
                que.push({nextx, nexty});
                visited[nextx][nexty] = true; // 只要加入队列立刻标记
            }
        }
    }

}

以上两个版本其实,其实只有细微区别,就是 visited[x][y] = true; 放在的地方,这取决于我们对 代码中队列的定义,队列中的节点就表示已经走过的节点。 所以只要加入队列,立即标记该节点走过。

本题完整: c++ code 广搜代码

#include 
#include 
#include 
using namespace std;

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void bfs(const vector<vector<int>>& grid, 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();
        int curx = cur.first;
        int cury = cur.second;
        for (int i = 0; i < 4; i++) {
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1];
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
            if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) {
                que.push({nextx, nexty});
                visited[nextx][nexty] = true; // 只要加入队列立刻标记
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    vector<vector<bool>> visited(n, vector<bool>(m, false));

    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {
                result++; // 遇到没访问过的陆地,+1
                bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
            }
        }
    }


    cout << result << endl;
}

本题完整: code python 广搜代码

from collections import deque
def bfs(graph, visited, x, y):
    que = deque()
    que.append([x, y])
    dir = [(1, 0), (0, 1), (-1, 0), (0, -1)]
    while que:
        cur = que.popleft()
        for i in range(4):
            nextx = cur[0] + dir[i][0]
            nexty = cur[1] + dir[i][1]
            if nextx < 0 or nexty < 0 or nextx >= len(graph) or nexty >= len(graph[0]): continue
            if not visited[nextx][nexty] and graph[nextx][nexty] == 1:
                visited[nextx][nexty] = True
                que.append([nextx, nexty])


# 主函数
# 原始数据的输入, 结果输出
def main():
    n, m = [int(v) for v in input().split(' ')]
    graph = []
    visited = [[False for _ in range(m)] for _ in range(n)]
    result = 0
    n1 = n
    while n1:
        n1 -= 1
        v = [int(v) for v in input().split(' ')]
        graph.append(v)

    for i in range(n):
        for j in range(m):
            if not visited[i][j] and graph[i][j]:
                visited[i][j] = True
                result += 1
                bfs(graph, visited, i, j)
    print(result)

main()

你可能感兴趣的:(算法,之,代码随想录学习与复习,图论,学习,深度优先)