一个很重要的点:只有边权为1时才能应用BFS算法
习题篇:(12条消息) BFS 之Flood Fill 算法(二)_Dream.Luffy的博客-CSDN博客
算法介绍:
一如往常,我们先看看Flood Fill算法的定义:
Flood fill算法是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典算法。
基本作用:寻找连通块
适用的题目类型: 需要找出连通块的题目
整体思路:
1.设置循环遍历地图
2.设置BFS函数
3.将当前所遍历点坐标作为参数传入bfs
4.将参数压入队列中
5.一直循环到队列为空
6. 判断当前坐标是否合法(这里是每个题目的不同点)
7.若状态满足要求,则压入队列中,重置状态
只有文字,没有图可不是我们的风格
我们先看看最初始的情况,就是每一个点都是合法的,都能倒水,那么我们可以得到下图
重要的是,我们怎么将它转换为代码表示?
这里就需要用到 数据结构 队列来操作了,如下图
我们用队列存储灌了水的格子,再遍历与它接壤的格子,不断更新直到队列为空
那么,我们先来看一个典题
池塘计数(来自于信息学奥赛一本通)
农夫约翰有一片 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的图哈哈
{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,期待与你再次相遇