参考 acwing Flood-Fill算法题目
算法思想介绍
从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典算法
例题一:二维数组区块计数
题目描述:
输入一个只包含0和1的二维数组,上下左右和对角相邻的1组成一个区块,0不形成区块,求数组中的区块个数。
输入格式 第一行输入两个正整数N和M,N表示数组行数,M表示数组列数。
接下来N行,每行表示数组对应的一行,每行包含M个整数,整数之间用空格隔开。
输出格式 输出一个整数,表示数组中区块的个数。
数据范围
0≤N,M,N∗M≤106 输入样例:
3 3
0 1 0
1 0 0
1 0 1
输出样例:
2
样例解释 数组右下角的1单独构成一个区块,其他的3个1对角或上下相邻,构成另一个区块。
代码如下
import java.io.*;
public class Main{
static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
static int[][] arr;
static int n, m;
public static void main(String[] args) throws IOException{
String[] s = cin.readLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
arr = new int[n][m];
for (int i = 0; i < n; i++) {
s = cin.readLine().split(" ");
for (int j = 0; j < m ; j++) {
arr[i][j] = Integer.parseInt(s[j]);
}
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (arr[i][j] == 1) {
dfs(i, j);
res++;
}
}
}
System.out.println(res);
}
private static void dfs(int x, int y) {
arr[x][y] = 0;
for (int i = -1; i <= 1; i ++){
for (int j = -1; j <= 1; j++) {
int a = x + i, b = y + j;
if (a >= 0 && a < n && b >= 0 && b < m && arr[a][b] == 1) dfs(a, b);
}
}
}
}
例题二:池塘计数
题目描述:
农夫约翰有一片 N∗M 的矩形土地。最近,由于降雨的原因,部分土地被水淹没了。现在用一个字符矩阵来表示他的土地。
每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。现在,约翰想知道他的土地中形成了多少片池塘。每组相连的积水单元格集合可以看作是一片池塘。
每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。 请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。
输入格式 第一行包含两个整数 N 和 M。
接下来 N 行,每行包含 M 个字符,字符为”W”或”.”,用以表示矩形土地的积水状况,字符之间没有空格。
输出格式 输出一个整数,表示池塘数目。
数据范围
1≤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
采用dfs会实现
import java.io.*;
public class Main{
static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
static char[][] arr;
static int n, m;
public static void main(String[] args) throws IOException{
String[] s = cin.readLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
arr = new char[n][m];
for (int i = 0; i < n; i++) {
String str = cin.readLine();
for (int j = 0; j < m; j++) {
arr[i][j] = str.charAt(j);
}
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++){
if (arr[i][j] == 'W') {
dfs(i, j);
res++;
}
}
}
System.out.println(res);
}
private static void dfs(int x, int y) {
arr[x][y] = '.';
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int a = x + i, b = y + j;
if (a >= 0 && a < n && b >= 0 && b < m && arr[a][b] == 'W') dfs(a, b);
}
}
}
}
采用宽搜bfs使用队列实现,这个算法的消耗的时间相比dfs少
import java.io.*;
import java.util.*;
public class Main{
static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
static char[][] arr;
static int n, m;
static class Place {
int x, y;
Place(int i, int j) {
x = i;
y = j;
}
}
private static void bfs(int x, int y) {
Queue<Place> q = new LinkedList<>();
q.offer(new Place(x, y));
arr[x][y] = '.';
while (!q.isEmpty()) {
Place pl = q.poll();
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int a = pl.x + i, b = pl.y + j;
if (a >= 0 && a < n && b >= 0 && b < m && arr[a][b] == 'W') {
q.offer(new Place(a, b));
arr[a][b] = '.';
}
}
}
}
}
public static void main(String[] args) throws IOException{
String[] s = cin.readLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
arr = new char[n][m];
for (int i = 0; i < n; i++) {
String str = cin.readLine();
for (int j = 0; j < m; j++) {
arr[i][j] = str.charAt(j);
}
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++){
if (arr[i][j] == 'W') {
bfs(i, j);
res++;
}
}
}
System.out.println(res);
}
}
例题三:山峰和山谷
题目描述:
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。
输出格式 共一行,包含两个整数,表示山峰和山谷的数量。
数据范围 1≤n≤1000, 0≤w≤109 输入样例1:
5
8 8 8 7 7
7 7 8 8 7
7 7 7 7 7
7 8 8 7 8
7 8 8 8 8
输出样例1:
2 1
输入样例2:
5
5 7 8 3 1
5 5 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 0 1 7
输出样例2:
3 3
解题思想:
关键是通过使用标记 bool 、 has_higher 、 has_higher、 分别表示是否访问过,在当前点周围是否还包含更高或更低的点
代码如下:
import java.io.*;
import java.util.*;
public class Main{
static BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
static int n;
static boolean[][] bool; // 标记是否访问过
static boolean has_higher;
static boolean has_lower;
static int[][] arr;
static class Place {
int x, y;
Place(int i, int j) {
x = i;
y = j;
}
}
public static void bfs(int x, int y) {
Queue<Place> q = new LinkedList<>();
q.offer(new Place(x, y));
bool[x][y] = true;
while (!q.isEmpty()) {
Place p = q.poll();
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int a = p.x + i, b = p.y + j;
if (a == x && b == y) continue;
if (a >= 0 && a < n && b >= 0 && b < n) {
if (arr[a][b] != arr[x][y]) { // 说明有比当前位置更高或低的点
if (arr[a][b] > arr[x][y]) has_higher = true;
else has_lower = true;
} else if (!bool[a][b]) { //当前节点未被访问过
q.offer(new Place(a, b));
bool[a][b] = true;
}
}
}
}
}
}
public static void main(String[] args) throws IOException {
String[] s = cin.readLine().split(" ");
n = Integer.parseInt(s[0]);
arr = new int[n][n];
bool = new boolean[n][n];
for (int i = 0; i < n; i++) {
s = cin.readLine().split(" ");
for (int j = 0; j < n; j++) {
arr[i][j] = Integer.parseInt(s[j]);
}
}
int maxNum = 0, minNum = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (!bool[i][j]) {
has_higher = false;
has_lower = false;
bfs(i, j);
if (!has_higher) maxNum++;
if (!has_lower) minNum++;
}
}
}
System.out.println(maxNum + " " + minNum);
}
}