【优选算法】专题十五——BFS解决FloodFill算法

文章目录

FloodFill算法简介

FloodFill算法介绍(AI总结):

  • FloodFill(泛洪填充/漫水填充)算法,简单说就是从一个"种子点"出发,像洪水蔓延一样,查找到周围所有符合条件的位置,共同形成这样的一个区块。它最直观的应用就是画图软件里的"油漆桶"工具。

核心原理:扩散与连通

  • 算法基于图遍历(DFS或BFS)实现,核心逻辑如下:

  • 起点:选择一个起始像素点(种子点)。

  • 扩散:检查该点的上下左右(四邻域)或有些时候也要求包括对角线(八邻域)的邻居。

  • 条件:如果邻居的颜色与起点颜色相同,且未被访问过,就将其"感染"成新颜色,并继续以该邻居为起点重复扩散。

  • 终止:当所有符合条件的邻居都被处理完毕,算法结束。

两种实现方式

  • DFS(深度优先):代码简洁,像"一条路走到黑"。适合小区域,但大区域容易导致栈溢出。

  • BFS(广度优先):使用队列,像"涟漪扩散"。空间占用更可控,是工程实践中的首选。

应用场景

  • 除了油漆桶,它还在以下场景大显身手:

  • 图像处理:图像分割、连通区域标记。

一、图像渲染

Leetcode链接

有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。你也被给予三个整数 sr , sc 和 color 。你应该从像素 image[sr][sc] 开始对图像进行上色 填充 。

为了完成 上色工作:

从初始像素开始,将其颜色改为 color。

对初始坐标的 上下左右四个方向上 相邻且与初始像素的原始颜色同色的像素点执行相同操作。

通过检查与初始像素的原始颜色相同的相邻像素并修改其颜色来继续 重复 此过程。

当 没有 其它原始颜色的相邻像素时 停止 操作。

最后返回经过上色渲染 修改 后的图像 。

解题思路

  • 经典FloodFill算法,使用BFS解题
  • 将当前位置入队列并更改其颜色,并将它四周(上下左右)符合条件的位置的坐标入队列。对这些位置做同样的处理

代码实现及解析

java 复制代码
class Solution {
    //向量数组(偏移量数组):
    public int[] dx=new int[]{0,0,1,-1};
    public int[] dy=new int[]{1,-1,0,0};

    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        int m=image.length,n=image[0].length;
        Queue<int[]> que=new LinkedList<>();//队列储存一个(x,y)坐标
        int prveColor=image[sr][sc];
        if(prveColor==color) return image;//如果起始点就等于color,那就不用再处理了

        que.offer(new int[]{sr,sc});//将起始点入队列
        image[sr][sc]=color;//更改该位置的颜色
        while(!que.isEmpty()){
            int[] tmp=que.poll();
            int a=tmp[0],b=tmp[1];//取出x、y坐标
            //使用偏移量数组,更便捷地定位到该位置四周的坐标
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];//找到基准坐标四周(上下左右)的位置
                if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==prveColor){//判断该位置是否符合条件
                    que.offer(new int[]{x,y});//入队列
                    image[x][y]=color;//更改其颜色
                }
            }
        }
        return image;
    }
}

总结

  • 复习解题思路和代码的整体实现框架
  • 代码中向量数组(偏移量数组)的使用:将坐标(x,y)中x、y的位置偏移量枚举出分别放入到两个数组dx、dy中,当开始对一个基准位置定位四周的坐标时,就可以使用dx、dy更便捷的找到其上下左右的坐标

二、岛屿数量

Leetcode链接

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 :

输入:grid = [

'1','1','1','1','0'\], \['1','1','0','1','0'\], \['1','1','0','0','0'\], \['0','0','0','0','0'

]

输出:1

解题思路

  • 我们遍历二维网络,找到一块陆地就计数一个岛屿,但是实际上是与该陆地连接的陆地共同所组成的一个岛屿,此时我们要将这些陆地全部标记为"已访问"(使用额外的visited矩阵对原矩阵每个位置的访问状态进行记录),下次遍历这些被标记的陆地就不能再计数了(也可以将访问过的陆地直接修改为"0",只是面试时可能不允许修改原矩阵),那么这个算法就使用的是BFS。

代码实现及解析

java 复制代码
class Solution {
    //偏移量数组:
    int[] dx=new int[]{0,0,1,-1};
    int[] dy=new int[]{1,-1,0,0};

    //visited矩阵记录原矩阵每个位置的访问状态
    boolean[][] visited=new boolean[300][300];//一次给够空间,也可以在下面方法中new一个合适的空间
    int m,n;
    public int numIslands(char[][] grid) {
        int ret=0;//记录岛屿数量
        m=grid.length;n=grid[0].length;
        //遍历二维网络的每个位置来查找岛屿
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]=='1'&&!visited[i][j]){//找到了陆地(岛屿+1),但该陆地不能是被访问过的(visited中不能被标记为true)
                    ret++;
                    bfs(grid,i,j);//与该陆地连接的陆地共同组成一个岛屿,将这些陆地全部标记为"已访问",下次遇到就不再计数了
                }
            }
        }
        return ret;
    }
    public void bfs(char[][] grid,int i,int j){//以grid[i][j]为起点的所有相连陆地全部标记"已访问"(使用BFS算法)
        Queue<int[]> que=new LinkedList<>();
        que.offer(new int[]{i,j});
        visited[i][j]=true;
        while(!que.isEmpty()){
            int[] tmp=que.poll();
            int a=tmp[0],b=tmp[1];
            for(int k=0;k<4;k++){//用k,别与i冲突了
                int x=a+dx[k],y=b+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]=='1'&&!visited[x][y]){
                    que.offer(new int[]{x,y});
                    visited[x][y]=true;//该位置标记为"已访问",防止下次重复访问
                }
            }
        }
    }
}

总结

  • 复习解题思路

三、被围绕的区域

Leetcode链接

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' 组成,捕获 所有 被围绕的区域:

连接:一个单元格与水平或垂直方向上相邻的单元格连接。

区域:连接所有 'O' 的单元格来形成一个区域。

围绕:如果一个区域中的所有 'O' 单元格都不在棋盘的边缘,则该区域被包围。这样的区域 完全 被 'X' 单元格包围。

通过 原地 将输入矩阵中的所有 'O' 替换为 'X' 来 捕获被围绕的区域。你不需要返回任何值。

解题思路

  • 在正常遍历矩阵去处理'O'区块时,无法判断出该'O'区块是否接触了边缘,所以我们可以先遍历矩阵的边缘去处理这些接触了矩阵的边缘的'O'区块(使用BFS)。先将这些边缘'O'区块更改为其他字符(如:' * '),再整体正常地使用for循环遍历一遍矩阵,这时矩阵剩下的字符'O'就一定不会接触边缘,可以将其更改为'X',而同时我们再把矩阵中的 ' * ' 字符改回'O'就行了。

代码实现及解析

java 复制代码
class Solution {
    //偏移量数组
    int[] dx=new int[]{0,0,1,-1};
    int[] dy=new int[]{1,-1,0,0};
    int m,n;

    public void solve(char[][] board) {
        m=board.length;n=board[0].length;
        //1.遍历边缘位置,处理这些接触边缘的'O'区块,将其区域更改为其他字符,避免在后续正常处理时被改为'X'
        for(int j=0;j<n;j++){//处理上下两行这两个边缘位置
            if(board[0][j]=='O'){
                bfs(board,0,j);//将该区域更改为字符'*'
            }
            if(board[m-1][j]=='O'){
                bfs(board,m-1,j);
            }
        }
        for(int i=0;i<m;i++){//处理左右两列这两个边缘位置
            if(board[i][0]=='O'){
                bfs(board,i,0);
            }
            if(board[i][n-1]=='O'){
                bfs(board,i,n-1);
            }
        }
        //2.再来遍历矩阵,替换矩阵中目标字符
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(board[i][j]=='O'){
                    board[i][j]='X';
                }else if(board[i][j]=='*'){//这样将处于边缘区块的'O'再变回来
                    board[i][j]='O';
                }
            }
        }
        
    }
    public void bfs(char[][] board,int i,int j){
        Queue<int[]> que=new LinkedList<>();
        que.offer(new int[]{i,j});
        board[i][j]='*';
        while(!que.isEmpty()){
            int[] tmp=que.poll();
            int a=tmp[0],b=tmp[1];
            for(int k=0;k<4;k++){
                int x=a+dx[k],y=b+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='O'){
                    board[x][y]='*';
                    que.offer(new int[]{x,y});
                }
            }
        }

    }
}

总结

  • 复习解题思路
  • "正难则反"思想:正着来不行,无法判断当前'O'区域是否接触边缘,那就反着来,先遍历边缘,先处理这些边缘'O'区域
相关推荐
2401_849644852 小时前
C++代码重构实战
开发语言·c++·算法
fengfuyao9852 小时前
一个改进的MATLAB CVA(Change Vector Analysis)变化检测程序
前端·算法·matlab
2301_815482932 小时前
C++与WebAssembly集成
开发语言·c++·算法
像污秽一样2 小时前
算法设计与分析-习题4.3
数据结构·算法·排序算法
ComputerInBook2 小时前
几何学基本概念——超平面(hyperplane)
算法·机器学习·平面·几何学
沈阳信息学奥赛培训2 小时前
C++ 指针* 和 指针的引用 *& (不是指针和引用,是指针的引用)
数据结构·c++·算法
老鱼说AI2 小时前
《深入理解计算机系统》(CSAPP)2.2:整数数据类型与底层机器级表示
开发语言·汇编·算法·c#
会编程的土豆2 小时前
【数据结构与算法】 树
数据结构·算法
LSL666_2 小时前
Redis值数据类型——hash
redis·算法·哈希算法·数据类型