宽度优先遍历(bfs)(2)——fllodfill算法

欢迎来到博主的专栏:算法解析

博主ID:代码小豪

文章目录

floodfiil算法

floodfill算法,在博主这里看来则是一个区域填充算法,具体来说则是在一个m*m的网格当中。每个网格中都要一个权值,这些权值可能相同,也可能不同,根据网格中的权值,做出填充的操作。

leetcode733------图像渲染

题目解析

以示例1为例:

从(1,1)的方格开始。将(1,1)临近的与(1,1)相同权值的网格,包括临近的网格的临近网格,全覆盖成新的权值。

比如(1,1)附近的网格有(1,0),(0,1)与(1,1)具有相同的权值,对于网格(1,0),有临近的(0,0)(2,0)网格具有相同权值,以此类推。直到所有符合条件的网格都被覆盖为止。

算法原理

我们回顾一下整个过程,对于(1,1)网格,我们要对其上下左右(即(0,1),(2,1),(1,0),(1,2))进行颜色覆盖,然后对符合要求的网格重复进行这个操作。注意重复这个字。这意味着我们找到了一个重复的最小子问题,因此我们可以用dfs来完成这个操作,但是这篇博客并不是用dfs章节,因此不讲。

既然是重复的子问题,我们尝试可以画出一个决策树,我们规定f(i,j)。是对(i,j)网格染色,然后进行一次临近网格的染色。根据题意,如果临近网格的权值与(i,j)不同,那么就不会对该网格进行染色。这意味着我们需要对决策树进行剪枝。

示例1的决策树如下:

那么既然有一个树状的结构,那么我们就可以层序遍历整个决策树。这个决策树其实是为了让大家对宽度优先遍历的原理有一个有另一个认识。在后续bfs专题的博客中,博主将不用再用决策树来做bfs的题。

所谓的bfs(宽度优先遍历)和dfs(深度优先遍历)其实都是对决策树进行遍历,不同的要求决定了我们到底是采取bfs还是dfs。比如算法如果是要求快速到达决策树的叶节点,我们就采用dfs。比如排列组合问题,而如果要求我们尽可能先遍历非叶结点,则采取bfs。比如后续章节的中最短路径问题,拓扑排序等。

像在floodfill算法问题上,使用bfs或者dfs并没有太大区别。

最后就是如何使用bfs算法,其实这里已经很明显了,我们对决策树进行一次层序遍历就是bfs算法。因此算法原理可以参考上一篇bfs博客。这里只讲方法,不讲层序遍历的原理。

(1):我们将决策树的根节点(即题目要求中的起点)放入队列。

(2):进入循环

(3):将队头节点出队列,将该节点对应的网格进行颜色覆盖。

(4):接着将队头节点的所有子节点入队列。(可能的子节点是该子节点中的上下左右节点)

(5)当队列为空时,结束循环

这里有个小细节,就是假设(x,y)网格是入队列的节点,那么它可能的子节点有(x+1,y),(x-1,y)(x,y+1),(x,y-1)。那么对于上面的节点来说,(x,y)也是它们的子节点,比如(x+1,y)是(x,y)的右边,那么对应的(x,y)在(x+1,y)的左边。那么我们就有可能进入死循环。因为假设(x,y)出队列,(x+1,y)入队列。那么当(x+1,y)出队列时,(x,y)可能入队列。以此类推,我们就进入一个死循环了。因此,我们要避免已经入队列的节点重复进入队列当中。

那么我们可以采用下面两种操作:

(1)修改出队列节点的权值。

(2)用哈希表或者vector<vector<bool>>标记出队列节点。

由于本题要求我们修改出队列节点的权值,因此我们采取方法1,这两个方法博主都会在本篇当中提到。

还有一个细节就是入队列的节点必须是合法的,比如当前网格如果是5*5.那么对于节点(5,6)这是不存在于网格当中的方块。因此不进入队列。

题解代码

cpp 复制代码
class Solution {
private:
    int dx[4]={1,-1,0,0};//对应x-1,x+1,x,x
    int dy[4]={0,0,1,-1};//对应y,y,y-1,y+1
    //比如(x+dx[0],y+dy[0])=(x+1,y)
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
        queue<pair<int,int>> q;
        if(image[sr][sc]==color) return image;//如果覆盖的网格的原颜色,与覆盖颜色一致,就不需要操作了
        q.push({sr,sc});//将决策树的根节点入队列
        int target=image[sr][sc];
        int m=image.size(),n=image[0].size();//记录网格的长度和宽度
        while(q.size()){
            auto [x1,y1]=q.front();
            image[x1][y1]=color;//对当前格子进行染色
            q.pop();
            for(int k=0;k<4;k++){//临近的格子(上下左右)
                int x=x1+dx[k];
                int y=y1+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==target){//判断合法性,即剪枝
                    q.push({x,y});
                }
            }
        }
        return image;
    }
};

leetcode130------被围绕的区域

题目解析

算法原理

如果我们继续用树来抽象理解bfs就不是很舒服了,因为在做题之前还得画画决策树,实际上对于网格问题,我们可以用网格的模型来替代决策树。

我们假设当前有一个16*16的网格,我们对中心位置进行bfs,假设题目规定一次只能向上,下,左,右进行访问。那么在中心位置的bfs会呈现如下的状态:

如果我们的题目要求是可以向左上,上,右上,左,右,左下,下,右下进行遍历,那就是这样的情况。

ok,让我们回到原题,题目中要求我们找到被'X'围绕的'O',并且让我们将被围绕的'O'区域替换成'X'。那么,我们只需要对被围绕的'O'区域中的任意一个'O'点进行一次fllodfill(bfs),就能将这些区域的所有'O'替换成'X'。但是问题来了,我们如何判断哪些区域会被'X'围绕呢?

实际上如果我们按照这个思路做题,就会发现这个判断不是很好写。因此我们可以将思路反过来,如果找被'X'围绕的区域不好找,那么我们尝试一下找不被'X'围绕的区域呢?这其实就是用到了"正难则反"的数学思想。

那么满足什么条件的'O'区域不被'X'围绕呢?首先我们可以确定一点,在边界的'O'点,不会被'X'围绕。因为被'X'围绕的判定条件是'O'区域的上,下,左,右皆有'X'点。但是在边界的'O'点至少存在一个方向没有'X'。

那么对于不在边界的'O'点如何判断是否被'X'围绕呢?很明显,如果该'O'点对应的'O'区域存在一个'O'点位于边界,那么该'O'区域一定不是被'X'围绕的区域。

那么我们可以上来直接遍历边界,如果边界上存在'O'点,那么我们就对该'O'点做一次floodfill(bfs)算法、找到该'O'点所在的'O'区域。这片'O'区域都是不为'X'围绕的区域。

为了使'X'区域,被'X'围绕的'O'区域,以及不被'X'围绕的'O'区域区分开来,我们可以规定,让不被'X'的'O'区域的点,置成'A'。

如果是示例1,那么在完成对所有边界'O'点进行floodfill算法之后,网格将会如此呈现:

接着我们遍历一遍网格,将网格中的'O'置为'X',将网格中的'A',置为'O',这样就是完成了找到不被'X'围绕的区域。最终将该网格进行返回。

最后也是补充一点小细节。我们要注意被bfs过的方格不能再次被遍历,这里我们可以设置一个与网格同等规模的vector<vector<bool>> vis,如果(i,j)参与了bfs。我们就将vis[i][j]设为true。即对其进行标记。

实际上由于我们可以将'O'修改成'A',因此可以"权值修改法",但是由于我们这里用vis标记了被遍历过的方格。因此这里可以不用将'O'修改成'A'。

在完成所有操作后,将网格遍历一遍,如果网格中存在'O'点,而且该点没有被标记过,则说明该点没有进行过floodfill,即该点是被'X'围绕的区域。因此将其设为'X'。

最后的最后,就是不要将越界的节点加入bfs当中。

题解代码

cpp 复制代码
class Solution {
private:
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    void floodfill(vector<vector<char>>& board,int i,int j){
        queue<pair<int,int>> q;
        q.push({i,j});
        while(q.size()){
            auto [x1,y1]=q.front();
            q.pop();
            for(int k=0;k<4;k++){
                int x=x1+dx[k];
                int y=y1+dy[k];
                if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='O'&&vis[x][y]==false){
                    q.push({x,y});
                    vis[x][y]=true;
                }
            }
        }
    }
public: 
    void solve(vector<vector<char>>& board) {
        m=board.size(),n=board[0].size();
        vis.resize(m,vector<bool>(n,false));
        for(int i=0;i<m;i++){
            if(board[i][0]=='O'&&vis[i][0]==false){//如果(i,0)是'O'点,而且没有被遍历过
                vis[i][0]=true;
                floodfill(board,i,0);
            }
            if(board[i][n-1]=='O'&&vis[i][n-1]==false){//同上
                vis[i][n-1]=true;
                floodfill(board,i,n-1);
            }
        }
        for(int j=1;j<n-1;j++){
            if(board[0][j]=='O'&&vis[0][j]==false){
                vis[0][j]=true;
                floodfill(board,0,j);
            }
            if(board[m-1][j]=='O'&&vis[m-1][j]==false){
                vis[m-1][j]=true;
                floodfill(board,m-1,j);
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(board[i][j]=='O'&&vis[i][j]==false){
                    board[i][j]='X';
                }
            }
        }
    }
private:
    int m;
    int n;
    vector<vector<bool>> vis;//标记
};
相关推荐
GeminiJM6 分钟前
分布式选举算法<一> Bully算法
分布式·算法
爱吃芝麻汤圆11 分钟前
分布式——分布式一致性算法(共识算法)
分布式·算法·共识算法
周方.36 分钟前
191. 位1的个数
数据结构·算法·leetcode·链表·职场和发展
可可格子衫44 分钟前
129. 求根节点到叶节点数字之和 --- DFS +回溯(js)
javascript·算法·深度优先
Tony沈哲1 小时前
基于 MODNet 和 Face Parsing 实现高质量人像分割与换发色
深度学习·opencv·算法
缘友一世1 小时前
设计模式之五大设计原则(SOLID原则)浅谈
java·算法·设计模式
圣保罗的大教堂2 小时前
leetcode 1432. 改变一个整数能得到的最大差值 中等
算法·leetcode·职场和发展
Coding小公仔2 小时前
LeetCode 48. 旋转图像
算法·leetcode·职场和发展
幻奏岚音2 小时前
Java数据结构——第 2 章线性表学习笔记
java·数据结构·笔记·学习·算法·排序算法
CoovallyAIHub3 小时前
YOLOv8/v10/v11自动驾驶实测对比:揭秘v11遮挡车辆检测精度提升关键
深度学习·算法·计算机视觉