问题描述
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。(来源:力扣(LeetCode))。多个陆地连接在一起,算作一个岛屿,单个陆地在一起也算一个岛屿。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
广度优先搜索(BFS) 解决问题
广度优先搜索(也称宽度优先搜索,缩写BFS,以下采用广度来描述)是连通图的一种遍历算法这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。基本过程,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。一般用队列数据结构来辅助实现BFS算法。
伪代码
void BFS(){
队列初始化;
初始结点入队;
while (队列非空){
队头元素出队,赋给current;
while(current 还可以扩展){
由结点current扩展出新结点new;
if (new 重复于已有的结点状态) continue;
new结点入队;
if (new结点是目标状态){
置flag= true; break;
}
}
}
}
解题
线性扫描整个二维网格,如果一个结点包含 1,则以其为根结点启动广度优先搜索。将其放入队列中,并将值设为 0 以标记访问过该结点。迭代地搜索队列中的每个结点,直到队列为空。
public static int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int count = 0; //岛屿的数量
int Y = grid.length; //水域的高度
int X = grid[0].length; //水域的宽度
//遍历每个区域 x y 代表其坐标。
for (int y = 0; y < Y; y++) {
for (int x = 0; x < X; x++) {
//找到陆地后 “炸了”, 岛屿数量+1,广度搜索旁边陆地,也“炸了”
if (grid[y][x] == '1') {
grid[y][x] = '0';
count++;
Queue<Integer> queue = new LinkedList<>();
// y * X + x 表示每个坐标的唯一标号
queue.add(y * X + x);
while (!queue.isEmpty()) {
//将头部的岛屿移除,并返回他的编号
int number = queue.remove();
//根据编号计算坐标
int x1 = number % X;
int y1 = number / X;
//把该坐标周围的陆地都找出来 并炸了
if (y1 - 1 >= 0 && grid[y1 - 1][x1] == '1') {
queue.add((y1 - 1) * X + x1);
grid[y1 - 1][x1] = '0';
}
if (y1 + 1 < Y && grid[y1 + 1][x1] == '1') {
queue.add((y1 + 1) * X + x1);
grid[y1 + 1][x1] = '0';
}
if (x1 - 1 >= 0 && grid[y1][x1 - 1] == '1') {
queue.add(y1 * X + (x1 - 1));
grid[y1][x1 - 1] = '0';
}
if (x1 + 1 < X && grid[y1][x1 + 1] == '1') {
queue.add(y1 * X + x1 + 1);
grid[y1][x1 + 1] = '0';
}
}
}
}
}
return count;
}
复杂度分析
- 时间复杂度 : O(M \times N)O(M×N),其中 MM 和 NN 分别为行数和列数。
- 空间复杂度 : O(min(M, N))O(min(M,N)),在最坏的情况下(全部为陆地),队列的大小可以达到 min(M,NM,N)。
深度优先搜索(DFS)解决问题
深度优先搜索属于图算法的一种,是一个针对图和树的遍历算法,英文缩写为DFS即Depth First Search。深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
伪代码
void DFS(结点类型 current){ // 从结点current出发递归地深度优先搜索
置visited[current]=true; // 表示结点current已被处理
if (current结点是目标状态{
置搜索成功标志flag= false;
return ;
}
while(current 还可以扩展){
由current结点扩展出新结点new;
if (! visited[new]) DFS(new); // 对未处理的结点new递归调用DFS
}
置visited[current]=flase; // 表示结点current以后可能被处理
}
解题
线性扫描整个二维网格,如果一个结点包含 1,则以其为根结点启动深度优先搜索。在深度优先搜索过程中,每个访问过的结点被标记为 0。计数启动深度优先搜索的根结点的数量,即为岛屿的数量。
public static void dfs(char[][] grid, int x, int y) {
if(x < 0 || y < 0 || y >= grid.length || x >= grid[0].length || grid[y][x] == '0' ){
return;
}
grid[y][x] = '0';
dfs(grid, x-1, y);
dfs(grid, x+1, y);
dfs(grid, x, y-1);
dfs(grid, x, y+1);
}
public static int numIslands2(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int count = 0; //岛屿的数量
int Y = grid.length; //水域的高度
int X = grid[0].length; //水域的宽度
//遍历每个区域 x y 代表其坐标。
for (int y = 0; y < Y; y++) {
for (int x = 0; x < X; x++) {
if (grid[y][x] == '1') {
count++;
dfs(grid, x, y);
}
}
}
return count;
}
复杂度分析
- 时间复杂度 : O(M\times N)O(M×N),其中 MM 和 NN 分别为行数和列数。
- 空间复杂度 : 最坏情况下为 O(M \times N)O(M×N),此时整个网格均为陆地,深度优先搜索的深度达到 M \times NM×N。