Number of Islands

Number of Islands

Problem description

Given a 2d m × n m\times n m×n grid map of '1’s (land) and '0’s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.(1≤m≤1000, 1≤n≤1000)

Input

Line 1: m and n
Line 2: ‘1’ or ‘0’ in grid and split by spaces

Output

Line 1: number of islands.

Sample Input
4 5
1 1 0 0 1
0 1 0 1 1
1 1 1 0 0
0 0 0 0 1
Sample Output
3

简单来说,海岛问题,计算出所有岛屿的数目,注意题目里的邻接大陆不包括斜方向。如果说用暴力方法,每一个 ‘0’ 或 ‘1’ 都要去计算的话,这样每个点都要考虑上下左右四个方向,抛开重复的计算量,怎么确保哪些 ‘1’ 连在一起也是个问题。所以自然想到的方法就是 DFSBFS,下面就对这两种方法进行介绍。

一、DFS

什么是 DFS?DFS 是深度优先搜索(Depth-first search)。详细可参照《算法导论》第22章基本的图算法内容。DFS 是只要有可能就要在图中尽量深入,总是对最近才发现的结点 v v v 的出发边进行探索,直到该结点的所有出发边都被发现为止。如果 v v v 所有的边都被发现,那么搜索回溯到 v v v 的前驱结点(类似于父亲结点),然后再去搜索该前驱结点的出发边,一直到源结点可以达到的所有结点都被发现为止。以下图来进行说明:

假设源节点是 A A A,我们要找到结点 F F F,采用 DFS 时,它会从沿着以下路径遍历:

A->B->D->B->E->H->E->I->E->B->A->C->F

那么结合 DFS 的这个特征以及岛屿数量问题,我们有如下代码:

时间复杂度 O ( n 2 ) O(n^2) O(n2),两次 for 循环

/*********************************************************************************
  *Copyright(C),xxx-xxx
  *FileName:   CountIslands.cpp
  *Author:  xxx
  *Version:  1.0
  *Date:  xxxx
  *Description:  the question of water and land, and count the number of the island
                (only consider horizontally or vertically)
  *Code List: 
     1. dfs
     2. bfs with visit
     3. bfs
  *History:  
     1.Date: xxxx
       Author: xxx
       Modification: create
     2.…………

**********************************************************************************/
// dfs
#include <cstdio>
// 因为都是 0 1,采用 bool 类型节省空间
// 为了防止数组下标越界,将其长度设置为比题目要求稍微大点
bool map[1010][1010];   

void SelectDirection(bool (*map)[1010], int row, int col, int m, int n)
{
    if(row < 0 || row > m-1 || col < 0 || col > n - 1)  // 结束dfs的边界条件
        return;
    if(map[row][col] == 1)  // 遇到 '1' 时,将它变成 '0',防止再次遍历
        map[row][col] = 0;
    else
        return;
    
    // 朝上下左右四个方向进行递归
    SelectDirection(map, row-1, col, m, n);
    SelectDirection(map, row+1, col, m, n);
    SelectDirection(map, row, col-1, m, n);
    SelectDirection(map, row, col+1, m, n);

}

int FindIslandAndCount(bool (*map)[1010], int row, int col)
{
    int cnt = 0;
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            if(map[i][j] == 1)  // 如果遇到 '1',执行 dfs 将 '1' 置 '0',说明遇到island,计数加一
            {
                SelectDirection(map, i, j, row, col);
                ++cnt;
            }    
    return cnt;
}
int main()
{
    int row, col;
    scanf("%d %d", &row, &col);
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            scanf("%d", &map[i][j]);

    int cnt = FindIslandAndCount(map, row, col);
    printf("%d", cnt);
}

上面代码可以用下图来解释,其中"黑点"代表’1’,也就是 land,"白点"代表’0’也就是 water。

但是这样做,在 OJ 时后面几个样例会 MLE(Memory Limit Exceeded,内存超限),是因为空间复杂度较大,爆栈,也就是说上面的递归太“深”了。在课程的 OJ 上只能 score95,最后一个样例不通过。那么解决的办法要么把递归改成循环,要么就用下面介绍的 BFS(实际中我采用了 BFS 算法)。

二、BFS

BFS 是广度优先搜索(Breadth-first search),同样在《算法导论》的章节也有详细介绍,较好的解释是“白、灰、黑涂色”以及“走迷宫最短路径”问题。在给定源节点 s s s 之后,该算法可以将已发现结点和未发现结点之间的边界,沿着广度反向向外扩展。也就是说,算法在发现所有距离源节点 s s s k k k 的所有结点之后,才会发现距离源节点 s s s k + 1 k+1 k+1 的其他结点。

假设源节点是 A A A ,我们要找到结点 F F F,对于上图采用 BFS 时,它会从沿着以下路径遍历,每次遍历会对结点数据进行存储:

A->B->C->B->D->E->C->F

那么结合 BFS 的这个特征以及岛屿数量问题,我们有如下代码:

时间复杂度 O ( n 2 ) O(n^2) O(n2),两次 for 循环

// bfs
// Vs = Vsource 起点
// Vn = Vnow 当前点
// Va = Vadjacent 当前点的相邻点
// dir = direction 四个方向


// bfs 用 visit
// 初始时 visit 为 0,当被访问时置 1
#include <cstdio>
#include <queue>
using namespace std;
struct Node{
    int x, y;
};

bool visit[1010][1010];
bool grid[1010][1010];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 四个方向

void BFS(Node Vs, bool (*visit)[1010], int row, int col)
{
    Node Vn, Va;
    queue<Node> q;
    q.push(Vs); // 源节点加入队列
    visit[Vs.x][Vs.y] = true;   // 源节点对应坐标 visit 置 1,表示被访问过
    
    while (!q.empty())
    {
        Vn = q.front(); // 将最前面的值取出
        q.pop();    // 取出值后,将最前面的值删除
        for(int i=0; i<4; ++i)  // 四个方向进行遍历
        {
            Va = Node({Vn.x + dir[i][0], Vn.y + dir[i][1]}); // 邻接结点的坐标位置

            if(Va.x < 0 || Va.x > row-1 || Va.y < 0 || Va.y > col -1) // 如果该方向到达边界,执行下一个方向
                continue;
            if(grid[Va.x][Va.y] == 0)   // 如果此方向结点值是 0,继续下一个方向
                continue;

            if(grid[Va.x][Va.y] == 1 && visit[Va.x][Va.y] == false) // 此方向结点是 1,并且没有被访问
            {
                q.push(Va); // 将此接点加入队列
                visit[Va.x][Va.y] = true; // 访问置 1
            }
        }
    }
}
int FindIslandAndCount(bool (*grid)[1010], bool (*visit)[1010], int row, int col)
{
    int cnt = 0;
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            if(grid[i][j] == 1 && visit[i][j] == false) // 如果结点值是 1 且没被访问
            {
                BFS({i, j}, visit, row, col);
                ++cnt;
            }    
    return cnt;
}
int main()
{
    int row, col;
    scanf("%d %d", &row, &col);
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            visit[i][j] = false;
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            scanf("%d", &grid[i][j]);

    int cnt = FindIslandAndCount(grid, visit, row, col);
    printf("%d", cnt);
}

还是以上面的“海岛图”进行说明。

坐标 (0, 0) 的点的值是 0,所以不执行 BFS。坐标 (0, 1) 加入队列,将队列首值取出并删除,循环查找 (0, 1) 是 4 个方向,(0, 2) 满足要求,将其加入队列,并 visit 置 1。while 为真,将 (0, 2) 取出并删除,查寻 (0, 2) 四个方向点,发现 (0, 1) == 1,但 visit == 1,继续下一个方向,然后将 (1, 2) 和 (0, 3) 加入队列。一直到队列为空,结束循环。

但是在此程序中,我们又重新开辟了一个 bool visit[1010][1010] 数组,占用了一定的空间,如果不加 visit 要怎么处理?可以参考 DFS,将遍历过的 1 置 0.

// bfs 不用 visit
#include <cstdio>
#include <queue>
using namespace std;
struct Node{
    int x, y;
};

bool grid[1010][1010];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 四个方向
void BFS(Node Vs, int row, int col)
{
    Node Vn, Va;
    queue<Node> q;
    q.push(Vs);
    
    while (!q.empty())
    {
        Vn = q.front();
        q.pop();
        for(int i=0; i<4; ++i)
        {
            Va = Node({Vn.x + dir[i][0], Vn.y + dir[i][1]});

            if(Va.x < 0 || Va.x > row-1 || Va.y < 0 || Va.y > col -1)
                continue;

            if(grid[Va.x][Va.y] == 1)   // 为 1 入队列,置 0
            {
                q.push(Va);
                grid[Va.x][Va.y] = 0;
            }
            else
                continue;
        }
    }
}
int FindIslandAndCount(bool (*grid)[1010], int row, int col)
{
    int cnt = 0;
    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            if(grid[i][j] == 1)
            {
                BFS({i, j}, row, col);
                ++cnt;
            }    
    return cnt;
}
int main()
{
    int row, col;
    scanf("%d %d", &row, &col);

    for(int i=0; i<row; ++i)
        for(int j=0; j<col; ++j)
            scanf("%d", &grid[i][j]);

    int cnt = FindIslandAndCount(grid, row, col);
    printf("%d", cnt);
}

BFS 没有用递归,不用担心爆栈问题,可以 AC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值