常见算法之Flood Fill算法
算法介绍
- 基本作用:寻找连通块
- 基本方法:BFS搜索
- 适用题目:需要找出分类块的题目/一些聚类问题
- 顾名思义,Flood Fill算法就是像
洪水泛滥
一样去寻找周围符合条件的区域,采用BFS可以完成先从自身最近的点寻找随后逐步扩展。
代码思路
- 设置函数bfs
- 传入参数坐标,创建队列,初始位置压入
- 将形参坐标改变,并标记已经访问(防止重复)
- 循环直到队列空
- 将队头元素取出
- 判断改变后的坐标是不是满足要求(范围,标记状态,属于连通块等)
- 若满足状态,压入队列,将状态重置
例题
- 池塘计数(简单套用)
农夫约翰有一片 N∗M 的矩形土地。最近,由于降雨的原因,部分土地被水淹没了。现在用一个字符矩阵来表示他的土地。每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。现在,约翰想知道他的土地中形成了多少片池塘。
每组相连的积水单元格集合可以看作是一片池塘。
每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。
请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。
输入样例
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
ac代码:
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n, m;
char g[N][N];
bool st[N][N];
int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1}, dy[] = {0, 1, 1, 1, 0, -1, -1, -1};//八个方向
void bfs(int x, int y)
{
st[x][y] = true;
queue<PII> q;
q.push({x, y});
while(q.size())
{
PII tmp = q.front();
q.pop();
for(int i = 0; i < 8; i++)
{
int nx = tmp.x + dx[i], ny = tmp.y + dy[i];
if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if(st[nx][ny] || g[nx][ny] == '.') continue;
st[nx][ny] = true;
q.push({nx, ny});
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++) cin >> g[i];
int ans = 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);
ans ++;
}
}
}
cout << ans << endl;
return 0;
}
- 城堡
请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。城堡被分割成 m∗n个方格区域,每个方格区域可以有0~4面墙。
城堡描述如下:
1 2 3 4 5 6 7
#############################
1 # | # | # | | #
#####---#####---#---#####---#
2 # # | # # # # #
#---#####---#####---#####---#
3 # | | # # # # #
#---#########---#####---#---#
4 # # | | | | # #
#############################
(图 1)
# = Wall
| = No wall
- = No wall
方向:上北下南左西右东。
输入格式
第一行包含两个整数 m 和 n,分别表示城堡南北方向的长度和东西方向的长度。
接下来 m 行,每行包含 n 个整数,每个整数都表示平面图对应位置的方块的墙的特征。
每个方块中墙的特征由数字 P 来描述,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,P 为该方块包含墙的数字之和。
例如,如果一个方块的 P 为3,则 3 = 1 + 2,该方块包含西墙和北墙。
城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。
输入的数据保证城堡至少有两个房间。
输出格式:
共两行,第一行输出房间总数,第二行输出最大房间的面积(方块数)。
数据范围
1≤m,n≤50,0≤P≤15
输入样例:
4 7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13
输出样例:
5
9
核心:每个方块中墙的特征由数字 P 来描述,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,P 为该方块包含墙的数字之和。,这是一个二进制套壳的洪水算法
分析:1表示西墙,2表示北墙,4表示东墙,8表示南墙,比如11 = 1 + 2 + 8表示西北南有墙,在这个位置上就只能向东走,换而言之,就是给定一个四位的二进制数,对应位为0才能够向该方向出发,求连通块的数目以及最大连通块的面积。
- 要注意的易错点就是虽然上北下南左西右东,但是地图上的上面对应的唯一是(-1,0),表示横坐标的减少,而不是我们经常用到的(0,1)表示上面,因为这里是行列(0,-1),(-1,0),(0,1),(1,0)分别表示西北东南四个方向,比如某个位置上的数是1010,就可以向(0,-1)和(0,1)移动。
- 用到到二进制转换的技巧:
num >> x & 1
可以判断,num二进制的第x-1位是否为1。
由于还要找出最大的房间,所以BFS函数要返回一个int值。我们可以在队列循环开始时就将空间大小加一即可。
ac代码:
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 55;
int n, m;
int g[N][N];
bool st[N][N];
int dx[] = {0, -1, 0, 1}, dy[] = {-1, 0, 1, 0};
int bfs(int x, int y)
{
int res = 0;//记录房间大小
st[x][y] = true;
queue<PII> q;
q.push({x, y});
while(q.size())
{
PII tmp = q.front();
q.pop();
res ++;
for(int i = 0; i < 4; i++)
{
if(g[tmp.x][tmp.y] >> i & 1) continue;
int nx = tmp.x + dx[i], ny = tmp.y + dy[i];
if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if(st[nx][ny]) continue;
st[nx][ny] = true;
q.push({nx, ny});
}
}
return res;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cin >> g[i][j];
int ans = 0, num = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
{
if(!st[i][j])
{
num ++;//房间数目
int t = bfs(i, j);
ans = max(ans, t);
}
}
cout << num << endl;
cout << ans << endl;
return 0;
}
- 山峰和山谷
FGD小朋友特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。为了能够对旅程有一个安排,他想知道山峰和山谷的数量。给定一个地图,为FGD想要旅行的区域,地图被分为 n×n 的网格,每个格子 (i,j) 的高度 w(i,j) 是给定的。若两个格子有公共顶点,那么它们就是相邻的格子,如与 (i,j) 相邻的格子有(i−1,j−1),(i−1,j),(i−1,j+1),(i,j−1),(i,j+1),(i+1,j−1),(i+1,j),(i+1,j+1)。我们定义一个格子的集合 S 为山峰(山谷)当且仅当:
- S 的所有格子都有相同的高度。
- S 的所有格子都连通。
- 对于 s 属于 S,与 s 相邻的 s′ 不属于 S,都有 ws>ws′(山峰),或者 ws< ws′(山谷)。
如果周围不存在相邻区域,则同时将其视为山峰和山谷。
你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。
输入格式
第一行包含一个正整数 n,表示地图的大小。接下来一个 n×n 的矩阵,表示地图上每个格子的高度 w。
输出格式
共一行,包含两个整数,表示山峰和山谷的数量。
思路分析:
在搜索连通块的时候需要判断周围连通块的类型,可以使用两个变量记录当前连通块周围是否有比它高或者比它矮的连通块,我们可以使用flood fill算法搜索连通块,根据两个记录的变量计算当前山峰和山谷的数量。
ac代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
int n;
int peek,valley = 0;
int g[N][N];
bool st[N][N];
int dx[] = {-1,-1,-1,0,1,1,1,0},dy[] = {-1,0,1,1,1,0,-1,-1}
int bfs(int x,int y)
{
bool high = false;//表示有比该点处高的
bool low = false;
queue<PII> q;
st[x][y] = true;
q.push({x,y});
while(!q.empty())
{
PII tmp = q.front();
q.pop();
for(int i = 0; i < 8; i++)
{
int nx = tmp.x + dx[i],ny = tmp.y + dy[i];
if(nx < 0||nx > n||ny < 0||ny>n)continue;
if(g[nx][ny]!=g[tmp.x][tmp.y])
{
if(g[nx][ny] > g[tmp.x][tmp.y]) high = true;
if(g[nx][ny] < g[tmp.x][tmp.y]) low = true;
}
else if(!st[nx][ny])
{
st[nx][ny] = true;
q.push({nx,ny});
}
}
}
if(high && low) return -1;
else if(!high &&!low) return 0;
else if(!high) return 1;
else return 2;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
cin >>g[i][j];
}
}
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(!st[i][j])
{
int t = bfs(i,j);
if(t == 0)peek ++,valley ++;
if(t == 1)peek ++;
if(t == 2)valley ++;
}
}
}
cout << peek << " "<< valley <<endl;
return 0;
}