Flood Fill——AcWing 1097. 池塘计数

Flood Fill

定义

Flood Fill算法,又称为洪水填充或种子填充算法,是一种在图或网格数据结构中探索连通区域的搜索算法。它从一个初始节点(种子点)开始,将具有相同属性(如颜色、值等)的相邻节点递归或迭代地标记或改变属性值,直到遍历完所有与种子点连通且满足条件的节点。该算法因其形象地模拟了洪水填满低洼地带的过程而得名。

运用情况

  1. 图形编辑软件:用于实现颜色填充工具,用户点击图像上的一点后,相同颜色的相邻区域会被填充上新的颜色。
  2. 游戏开发:在游戏地图编辑或UI设计中,快速改变特定区域的颜色或纹理。
  3. 计算机视觉与图像处理:在图像分割、物体识别中,标识或隔离特定区域。
  4. 数据分析与可视化:在数据集上标记相连的相同值区域,帮助数据分析和可视化。
  5. 路径规划与地图绘制:在网格地图中寻找并标记可达区域或障碍区域。

注意事项

  1. 递归深度限制:使用递归实现时,需警惕栈溢出风险,可通过设置递归深度限制或改用迭代方法避免。
  2. 边界检查:确保算法不会超出数据结构的边界,避免访问未定义或非法内存区域。
  3. 效率与空间优化:利用访问标志数组减少重复访问,使用广度优先搜索(BFS)相比深度优先搜索(DFS)可能在空间效率上有优势。
  4. 颜色相似度判断:在某些应用场景中,可能需要基于颜色差异阈值来决定是否填充相邻像素。
  5. 并发处理:在并行或分布式系统中实施时,需要适当的锁机制或任务划分策略以避免冲突。

解题思路

  1. 初始化:选择起始节点(种子点)和目标属性值(如颜色)。
  2. 选择搜索策略:决定使用DFS还是BFS。DFS更易于实现但可能导致较深的递归调用;BFS则更适合求解最短路径问题,且在大多数情况下空间效率较高。
  3. 创建工作队列/栈:根据所选策略准备数据结构来存储待处理节点。
  4. 标记与扩展:将种子点标记为已访问,并将其相邻且属性相同的节点加入队列/栈。
  5. 循环处理:循环执行以下步骤直至队列/栈为空:从队列/栈中取出一个节点,修改其属性(如颜色),并将符合条件的相邻未访问节点加入队列/栈。
  6. 结束条件:当所有与种子点连通且符合条件的节点都被处理过后,算法结束。

AcWing 1097. 池塘计数

题目描述

AcWing 1097. 池塘计数 - AcWing

运行代码

#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<vector<char>> grid;
void dfs(int i, int j) {
    if (i < 0 || i >= n || j < 0 || j >= m || grid[i][j]!= 'W') {
        return;
    }
    grid[i][j] = '.';
    dfs(i - 1, j);
    dfs(i + 1, j);
    dfs(i, j - 1);
    dfs(i, j + 1);
    dfs(i - 1, j - 1);
    dfs(i + 1, j + 1);
    dfs(i - 1, j + 1);
    dfs(i + 1, j - 1);
}
int countPonds() {
    int ponds = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 'W') {
                ponds++;
                dfs(i, j);
            }
        }
    }
    return ponds;
}
int main() {
    cin >> n >> m;
    grid.resize(n, vector<char>(m));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    cout << countPonds() << endl;
    return 0;
}

代码思路

  1. 输入处理:

    • 首先,读取网格的行数n和列数m
    • 然后,根据nm的值,初始化一个二维向量grid来存储地图信息。每一行是一个包含m个元素的字符向量,表示地图的一行。
    • 接着,逐行读取每个单元格的状态(字符),填充到grid中。
  2. 深度优先搜索(DFS):

    • 定义函数dfs(int i, int j),用于从地图上的位置(i, j)开始,遍历并标记相连的所有水池单元格。它通过递归地访问当前点的上、下、左、右以及对角线相邻的点,如果那些点是水池(即字符为'W'),则标记为已访问(改为'.')并继续深入搜索。
    • 注意这里考虑了八个方向的邻居,包括水平、垂直和两个对角线方向,实现了对角落和边缘连接的全面搜索。
  3. 计算水汽数量:函数countPonds()遍历整个网格,每当遇到一个未被访问的水池(字符'W'),就调用dfs()函数进行填充,并累加计数器ponds。这样,最终的ponds值就是地图中独立水池的数量。

  4. 输出结果:主函数最后输出计算得到的水汽数量。

改进思路

  1. 使用迭代而非递归:深度优先搜索虽然逻辑直观,但是递归可能会导致较大的栈空间消耗,特别是对于大型网格。可以通过使用栈数据结构来实现迭代版本的DFS,从而避免栈溢出的风险。

  2. 增加记忆化或标记已访问:尽管代码中通过将'W'改为'.'来标记已访问,但这种做法改变了原始地图。可以引入一个额外的二维布尔数组visited[n][m]来记录每个位置是否已被访问过,这样既不影响原地图,也能有效避免重复访问,提高效率。

  3. 广度优先搜索(BFS)替代DFS:在某些情况下,使用BFS代替DFS可能更有利,特别是当需要按距离排序(比如找出最近的水池)或者关心最短路径时。BFS使用队列进行层次遍历,可以更容易地控制搜索的范围和顺序。

  4. 并行处理:如果网格非常大,可以考虑将网格分割成多个小块,并在不同的线程或进程中并行执行DFS或BFS,最后汇总结果。但需要注意同步访问共享资源,防止数据竞争。

  5. 优化搜索方向:在某些特定场景下,不是所有八个方向都需要检查。例如,如果知道水体只能水平或垂直流动(非对角线),则可以减少搜索方向,提高效率。

  6. 使用并查集数据结构:对于频繁的连通性查询,使用并查集可以在合并连通分量时提供高效的O(α(n))操作(其中α(n)是一个非常接近于1的函数),有助于提升处理大量连通组件的效率。

  7. 动态调整搜索范围:根据具体问题,可能不需要全面八方向搜索。例如,如果水域只能在平面上水平或垂直流动,可以只检查上下左右四个方向。

其它代码

#include <iostream>
#include <cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n, m;
int ans;
char g[N][N];
PII p[N * N];
bool st[N][N];
void bfs(int sx, int sy)
{
    int hh = 0, tt = -1;
    p[++ tt] = {sx, sy};
    st[sx][sy] = true;
    while(hh <= tt)
    {
        PII t = p[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 < 0 || i >= n || j < 0 || j >= m) continue;
                if(st[i][j] || g[i][j] == '.') continue;
                st[i][j] = true;
                p[++ tt] = {i, j};
            }
    }
}
int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++ ) cin >> g[i];
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < m; j ++ )
            if(!st[i][j] && g[i][j] == 'W')
            {
                bfs(i, j);
                ans ++ ;
            }
    cout << ans << endl;
    return 0;
}

代码思路

  1. 数据结构与变量定义:

    • 使用#define宏定义了坐标对的引用方式,便于后续操作。
    • 定义了常量N作为最大行数/列数的限制,以及二维数组g来存储地图信息,布尔型二维数组st来标记某个位置是否已经被访问过。
    • PII是一个类型别名,表示一对整数(坐标),用于存储队列中的位置信息。
    • ans变量用来累计水池的数量。
  2. 主函数(main):

    • 首先读取地图的行数n和列数m,然后逐行读取地图信息到二维数组g中。
    • 双重循环遍历整个地图,对于每一个未访问且为水池的格子,调用bfs函数进行广度优先搜索,同时增加水池计数器ans的值。
  3. 广度优先搜索(bfs)函数:

    • 初始化队列,将当前水池的起始坐标(sx, sy)入队,并标记为已访问。
    • 当队列不为空时,循环处理队首元素,遍历其周围的8个相邻格子(注意代码中实际访问了9个位置,包括自身,但注释掉的那行代码表明本意是排除自身)。
    • 对于每个有效的相邻格子(未越界且未访问且为水池),将其标记为已访问,并将其坐标入队。
    • 这一过程会持续到当前水池区域的所有可达位置都被访问过,从而完成了单个水池的计数。
  4. 输出结果:最后,输出统计到的水池总数ans

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值