BFS 之Flood Fill 算法

一个很重要的点:只有边权为1才能应用BFS算法

习题篇:(12条消息) BFS 之Flood Fill 算法(二)_Dream.Luffy的博客-CSDN博客

算法介绍: 

一如往常,我们先看看Flood Fill算法的定义:

Flood fill算法是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典算法。

基本作用:寻找连通块

适用的题目类型: 需要找出连通块的题目

整体思路:

1.设置循环遍历地图

2.设置BFS函数

3.将当前所遍历点坐标作为参数传入bfs

4.将参数压入队列中

5.一直循环到队列为空

6. 判断当前坐标是否合法(这里是每个题目的不同点)

7.若状态满足要求,则压入队列中,重置状态

只有文字,没有图可不是我们的风格

我们先看看最初始的情况,就是每一个点都是合法的,都能倒水,那么我们可以得到下图

67bca740e4b24a5f9c4193f53d0ee4f2.png

 

 

 

重要的是,我们怎么将它转换为代码表示

这里就需要用到 数据结构 队列来操作了,如下图

d5e63e942a8442e18b481121a4851d6e.png

 

 

 我们用队列存储灌了水的格子,再遍历与它接壤的格子,不断更新直到队列为空

那么,我们先来看一个典题

 

池塘计数(来自于信息学奥赛一本通)

农夫约翰有一片 N∗M 的矩形土地。

最近,由于降雨的原因,部分土地被水淹没了。

现在用一个字符矩阵来表示他的土地。

每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。

现在,约翰想知道他的土地中形成了多少片池塘。

每组相连的积水单元格集合可以看作是一片池塘。

每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。

请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。

输入格式

第一行包含两个整数 N 和 M。

接下来 N 行,每行包含 M 个字符,字符为”W”或”.”,用以表示矩形土地的积水状况,字符之间没有空格。

输出格式

输出一个整数,表示池塘数目。

数据范围

1≤N,M≤10001≤N,M≤1000

输入样例:

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出样例:

3

 算法分析:

我们先来分析样例,w表示积水,而且是八连通的, 我们可以看到有三处积水 是互不连通

这里,我们就可以采用Flood Fill算法。

首先遍历整个地图,若该点有积水且没被遍历过(一定要是没被遍历过的,否则时间复杂度会倍增),那么我们就将该点传入BFS中,

以该积水块为起点, 遍历接壤的每一个点,执行上面的4, 5, 6, 7阶段,直到队列为空,也就是已经遍历了所有连通的积水块我们返回true, 表示这是一个积水连通块。

代码如下:
 

#include<iostream>
#include<cstring>
#include<algorithm>

#define x first
#define y second

using namespace std;
typedef pair<int, int> PII; //用pair数组来存点的坐标

const int N = 1010, M = N * N;

int n, m;
char g[N][N];
PII q[M]; //模拟队列的数组
bool st[N][N]; //st数组用于存储该点是否被遍历过

void bfs(int sx, int sy)
{
    int hh = 0, tt = 0; //模拟队列
    q[0] = {sx, sy};
    st[sx][sy] = true;
    
    while(hh <= tt)
    {
        PII t = q[hh ++];
        
        for(int i = t.x - 1; i <= t.x + 1; i ++)  //遍历八连通点
            for(int j = t.y - 1; j <= t.y + 1;j ++)
            {
                if(i == t.x && j == t.y) continue; //挖去自身中间那个点
                if(i < 0 || i >= n || j < 0 || j >= m) continue;
                if(g[i][j] == '.' || st[i][j]) continue; //如果是陆地或已被遍历过
                
                q[ ++ tt ] = {i, j}; //将该接壤点入队
                st[i][j] = true;
            }
    }
}
int main()
{
    scanf("%d%d", &n, & m);
    for(int i = 0;i < n;i ++) scanf("%s", g[i]);
    
    int cnt = 0;
    for(int i = 0;i < n;i ++)
        for(int j = 0;j < m;j ++)
            if(g[i][j] == 'W' && !st[i][j]) //如果是水地且没有被遍历过
            {
                bfs(i ,j);
                cnt ++; //连通的积水块区域 +1
            }
    printf("%d\n", cnt);
}

看了代码,你可能有两个点不明白:
一. 为什么要用数组模拟队列,如何用数组模拟队列

二.    for(int i = t.x - 1; i <= t.x + 1; i ++)  //遍历八连通点
            for(int j = t.y - 1; j <= t.y + 1;j ++)

        这两个循环是什么意思?

问题一:

相比于容器中的队列,用数组模拟速度更快,而且也轻巧方便

怎么用数组模拟队列呢?

我们只需让队头 hh = 0, 队尾 tt = 0,  

当我们想加入一个点到队头, 就让q[0] = {sx, sy};

读取队头并把队头抛弃: PII t =  q[hh++] 

将点加入到队尾中 q[++ tt] = {i , j};

问题二:
这里借用一下我们上一章状态压缩DP的图哈哈

2a4adcee711b4f2eb74d19566b3caf1d.png

{tx, ty} 是红色点的坐标, 而我们目标是遍历所有与它接壤的点,并判断它是否合法。

,那么我们左上角的坐标 就对应{tx - 1, ty - 1},  右下角坐标 就对应{tx + 1, ty + 1};

其它点同理。

这样,就解决咯!

java代码:

import java.io.*;
public class Main{
    static int N = 1010,n,m,tt,hh;
    static PII[] q = new PII[N * N];
    static char[][] g = new char[N][N];
    static boolean[][] st = new boolean[N][N];
    public static void bfs(int x,int y){
        hh = 0;tt = -1;
        q[++ tt] = new PII(x,y);
        while(hh <= tt){
            PII t = q[hh ++ ];
            int a = t.x;
            int b = t.y;
            //模拟可以走的方向上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。
            for(int i = a - 1 ; i <= a + 1 ; i ++ ){
                for(int j = b - 1 ; j <= b + 1 ; j ++ ){
                    if(i == a && j == b) continue; // 跳过自己那个点
                    if(i < 0 || j < 0 || i >= n || j >= m) continue; //判断是否越界
                    if(g[i][j] == '.' || st[i][j]) continue; // 不是水 并且 它已经被标记过了
                    q[++ tt] = new PII(i,j);
                    st[i][j] = true;
                }
            }
        }
    }
    public static void main(String[] args)throws IOException{
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        String[] s = bf.readLine().split(" ");
        n = Integer.parseInt(s[0]);
        m = Integer.parseInt(s[1]);
        for (int i = 0 ; i < n ; i ++ ){
            String str = bf.readLine();
            char[] c = str.toCharArray();
            for (int j = 0 ; j < m ; j ++ ){
                g[i][j] = c[j];
            }
        }
        int cnt = 0;
        for (int i = 0 ; i < n ; i ++ ){
            for (int j = 0 ; j < m ; j ++ ){
                //如果是水且没有被标记过就进行bfs
                if(g[i][j] == 'W' && !st[i][j]){
                    bfs(i,j);
                    cnt ++;
                }
            }    
        }
        System.out.println(cnt);
    }
}
class PII{
    int x,y;
    public PII(int x,int y){
        this.x = x;
        this.y = y;
    }
}

python代码:

import sys
line = sys.stdin.readline().strip()
n,m = [int(t) for t in line.split(' ')]
nums = []
for i in range(n):
    line = sys.stdin.readline().strip()
    nums.append(line)

st = [[False for j in range(m)] for i in range(n)]

def bfs(sx, sy):
    '''
      sx: start x
      sy: start y
    '''
    # 数组模拟队列, hh-队头, tt-队尾
    # 一开始队列只有一个元素为起点
    hh = 0; tt = 0
    q = [(sx,sy)]
    st[sx][sy] = True

    # 当队列不空时, 执行以下程序
    while (hh <= tt):
        # 先把队头取出来
        cur = q[hh]; hh += 1
        # 遍历当前点周围的8个点
        for i in range(cur[0]-1, cur[0]+2):
            for j in range(cur[1]-1, cur[1]+2):
                if i == cur[0] and j == cur[1]: continue
                if (i<0) or (i>=n) or (j<0) or (j>=m): continue
                if (nums[i][j] != 'W') or (st[i][j]): continue

                # 将合法元素加入到队列中
                q.append((i,j)); tt += 1
                st[i][j] = True

cnt = 0
for i in range(n):
    for j in range(m):
        if (nums[i][j] == 'W') and (not st[i][j]):
            bfs(i, j)
            cnt += 1
print(cnt)

贴上状态压缩DP讲解,也很详细哦!状态压缩DP 图文详解(一)_Dream.Luffy的博客-CSDN博客

以及习题篇(12条消息) BFS 之Flood Fill 算法(二)_Dream.Luffy的博客-CSDN博客

该系列会持续更新, 我是Luffy,期待与你再次相遇
 

 

e98498d992771c946b03be1ffb02d0c5.jpeg

 

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 49
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值