Flood-Fill 算法(基础篇)

899 阅读6分钟

参考 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);
    }
}