参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!

# 99. 岛屿数量 [卡码网题目链接(ACM模式)](https://kamacoder.com/problempage.php?pid=1171) 题目描述: 给定一个由 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 提示信息 ![](https://file1.kamacoder.com/i/algo/20240516111613.png) 根据测试案例中所展示,岛屿数量共有 3 个,所以输出 3。 数据范围: * 1 <= N, M <= 50 ## 思路 **[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[图论:广搜有陷阱啊!! | 广度优先搜索 | 卡码网:99.岛屿数量](https://www.bilibili.com/video/BV18PRGYcEiB/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 注意题目中每座岛屿只能由**水平方向和/或竖直方向上**相邻的陆地连接形成。 也就是说斜角度链接是不算了, 例如示例二,是三个岛屿,如图: ![图一](https://file1.kamacoder.com/i/algo/20220726094200.png) 这道题题目是 DFS,BFS,并查集,基础题目。 本题思路:遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。 再遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。 那么如果把节点陆地所能遍历到的陆地都标记上呢,就可以使用 DFS,BFS或者并查集。 ### 广度优先搜索 如果不熟悉广搜,建议先看 [广搜理论基础](./图论广搜理论基础.md)。 不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节: 根本原因是**只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过**。 很多同学可能感觉这有区别吗? 如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。 ![](https://file1.kamacoder.com/i/algo/20250124094043.png) 超时写法 (从队列中取出节点再标记,注意代码注释的地方) ```CPP int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向 void bfs(vector>& grid, vector>& visited, int x, int y) { queue> que; que.push({x, y}); while(!que.empty()) { pair 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}); } } } } ``` 加入队列 就代表走过,立刻标记,正确写法: (注意代码注释的地方) ```CPP int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向 void bfs(vector>& grid, vector>& visited, int x, int y) { queue> que; que.push({x, y}); visited[x][y] = true; // 只要加入队列,立刻标记 while(!que.empty()) { pair 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;` 放在的地方,这取决于我们对 代码中队列的定义,队列中的节点就表示已经走过的节点。 **所以只要加入队列,立即标记该节点走过**。 本题完整广搜代码: ```CPP #include #include #include using namespace std; int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向 void bfs(const vector>& grid, vector>& visited, int x, int y) { queue> que; que.push({x, y}); visited[x][y] = true; // 只要加入队列,立刻标记 while(!que.empty()) { pair 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> grid(n, vector(m, 0)); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> grid[i][j]; } } vector> visited(n, vector(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; } ``` ## 其他语言版本 ### Java ```java import java.util.*; public class Main { public static int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};//下右上左逆时针遍历 public static void bfs(int[][] grid, boolean[][] visited, int x, int y) { Queue queue = new LinkedList();//定义坐标队列,没有现成的pair类,在下面自定义了 queue.add(new pair(x, y)); visited[x][y] = true;//遇到入队直接标记为优先, // 否则出队时才标记的话会导致重复访问,比如下方节点会在右下顺序的时候被第二次访问入队 while (!queue.isEmpty()) { int curX = queue.peek().first; int curY = queue.poll().second;//当前横纵坐标 for (int i = 0; i < 4; i++) { //顺时针遍历新节点next,下面记录坐标 int nextX = curX + dir[i][0]; int nextY = curY + dir[i][1]; if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) { continue; }//去除越界部分 if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) { queue.add(new pair(nextX, nextY)); visited[nextX][nextY] = true;//逻辑同上 } } } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int m = sc.nextInt(); int n = sc.nextInt(); int[][] grid = new int[m][n]; boolean[][] visited = new boolean[m][n]; int ans = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { grid[i][j] = sc.nextInt(); } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (!visited[i][j] && grid[i][j] == 1) { ans++; bfs(grid, visited, i, j); } } } System.out.println(ans); } } ``` ### Python ```python from collections import deque directions = [[0, 1], [1, 0], [0, -1], [-1, 0]] def bfs(grid, visited, x, y): que = deque([]) que.append([x,y]) visited[x][y] = True while que: cur_x, cur_y = que.popleft() for i, j in directions: next_x = cur_x + i next_y = cur_y + j if next_y < 0 or next_x < 0 or next_x >= len(grid) or next_y >= len(grid[0]): continue if not visited[next_x][next_y] and grid[next_x][next_y] == 1: visited[next_x][next_y] = True que.append([next_x, next_y]) def main(): n, m = map(int, input().split()) grid = [] for i in range(n): grid.append(list(map(int, input().split()))) visited = [[False] * m for _ in range(n)] res = 0 for i in range(n): for j in range(m): if grid[i][j] == 1 and not visited[i][j]: res += 1 bfs(grid, visited, i, j) print(res) if __name__ == "__main__": main() ``` ### Go ```go package main import ( "bufio" "fmt" "os" "strconv" "strings" ) var dir = [4][2]int{{0, 1}, {1, 0}, {-1, 0}, {0, -1}} // 四个方向 func dfs(grid [][]int, visited [][]bool, x, y int) { for i := 0; i < 4; i++ { nextx := x + dir[i][0] nexty := y + dir[i][1] if nextx < 0 || nextx >= len(grid) || nexty < 0 || nexty >= len(grid[0]) { continue // 越界了,直接跳过 } if !visited[nextx][nexty] && grid[nextx][nexty] == 1 { // 没有访问过的 同时 是陆地的 visited[nextx][nexty] = true dfs(grid, visited, nextx, nexty) } } } func main() { reader := bufio.NewReader(os.Stdin) var n, m int fmt.Scanf("%d %d", &n, &m) grid := make([][]int, n) for i := 0; i < n; i++ { grid[i] = make([]int, m) line, _ := reader.ReadString('\n') line = strings.TrimSpace(line) elements := strings.Split(line, " ") for j := 0; j < m; j++ { grid[i][j], _ = strconv.Atoi(elements[j]) } } visited := make([][]bool, n) for i := 0; i < n; i++ { visited[i] = make([]bool, m) } result := 0 for i := 0; i < n; i++ { for 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 } } } fmt.Println(result) } ``` ### Rust ### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); // 创建readline接口 let iter = r1[Symbol.asyncIterator](); // 创建异步迭代器 const readline = async () => (await iter.next()).value; let graph let N, M let visited let result = 0 const dir = [[0, 1], [1, 0], [0, -1], [-1, 0]] // 读取输入,初始化地图 const initGraph = async () => { let line = await readline(); [N, M] = line.split(' ').map(Number); graph = new Array(N).fill(0).map(() => new Array(M).fill(0)) visited = new Array(N).fill(false).map(() => new Array(M).fill(false)) for (let i = 0; i < N; i++) { line = await readline() line = line.split(' ').map(Number) for (let j = 0; j < M; j++) { graph[i][j] = line[j] } } } /** * @description: 从(x, y)开始广度优先遍历 * @param {*} graph 地图 * @param {*} visited 访问过的节点 * @param {*} x 开始搜索节点的下标 * @param {*} y 开始搜索节点的下标 * @return {*} */ const bfs = (graph, visited, x, y) => { let queue = [] queue.push([x, y]) visited[x][y] = true //只要加入队列就立刻标记为访问过 while (queue.length) { let [x, y] = queue.shift() for (let i = 0; i < 4; i++) { let nextx = x + dir[i][0] let nexty = y + dir[i][1] if(nextx < 0 || nextx >= N || nexty < 0 || nexty >= M) continue if(!visited[nextx][nexty] && graph[nextx][nexty] === 1){ queue.push([nextx, nexty]) visited[nextx][nexty] = true } } } } (async function () { // 读取输入,初始化地图 await initGraph() // 统计岛屿数 for (let i = 0; i < N; i++) { for (let j = 0; j < M; j++) { if (!visited[i][j] && graph[i][j] === 1) { // 遇到没访问过的陆地,+1 result++ // 广度优先遍历,将相邻陆地标记为已访问 bfs(graph, visited, i, j) } } } console.log(result); })() ``` ### TypeScript ### PhP ```PHP = count($grid) || $nexty < 0 || $nexty >= count($grid[0])) { continue; // 越界了,直接跳过 } if (!$visited[$nextx][$nexty] && $grid[$nextx][$nexty] == 1) { // 没有访问过的 同时 是陆地的 $visited[$nextx][$nexty] = true; dfs($grid, $visited, $nextx, $nexty); } } } function main() { fscanf(STDIN, "%d %d", $n, $m); $grid = []; for ($i = 0; $i < $n; $i++) { $grid[$i] = array_map('intval', explode(' ', trim(fgets(STDIN)))); } $visited = array_fill(0, $n, array_fill(0, $m, false)); $result = 0; for ($i = 0; $i < $n; $i++) { for ($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 } } } echo $result . PHP_EOL; } main(); ?> ``` ### Swift ### Scala ```scala import scala.collection.mutable.Queue import util.control.Breaks._ // Dev on LeetCode: https://leetcode.cn/problems/number-of-islands/description/ object Solution { def numIslands(grid: Array[Array[Char]]): Int = { val row = grid.length val col = grid(0).length val dir = List((-1,0), (0,-1), (1,0), (0,1)) // 四个方向 var visited = Array.fill(row)(Array.fill(col)(false)) var counter = 0 var que = Queue.empty[Tuple2[Int, Int]] (0 until row).map{ r => (0 until col).map{ c => breakable { if (!visited(r)(c) && grid(r)(c) == '1') { que.enqueue((r, c)) visited(r)(c) // 只要加入队列,立刻标记 } else break // 不是岛屿不进入queue,也不记录 while (!que.isEmpty) { val cur = que.head que.dequeue() val x = cur(0) val y = cur(1) dir.map{ d => val nextX = x + d(0) val nextY = y + d(1) breakable { // 越界就跳过 if (nextX < 0 || nextX >= row || nextY < 0 || nextY >= col) break if (!visited(nextX)(nextY) && grid(nextX)(nextY) == '1') { visited(nextX)(nextY) = true // 只要加入队列,立刻标记 que.enqueue((nextX, nextY)) } } } } counter = counter + 1 // 找完一个岛屿后记录一下 } } } counter } } ``` ### C# ### Dart ### C