【算法】bfs与dfs算法解决FloodFill(洪流)问题(C++)

文章目录

  • 1. 什么是FloodFill问题
  • 2. 用什么方法解决FloodFill问题
  • 3. 具体例题
    • 773.图像渲染
    • 200.岛屿数量
    • 695.岛屿的最大面积
    • 130.被围绕的区域

1. 什么是FloodFill问题

一般floodfill问题可以描述为:给定一个二维矩阵,其中每个元素代表一个像素点,并给定一个起始点、目标颜色和填充颜色。问题要求将以起始点为中心,与其相邻且具有相同颜色的所有区域都填充为目标颜色。

在这里插入图片描述


2. 用什么方法解决FloodFill问题

我们通常使用下面两种方法解决FloodFill算法问题:

DFS(深搜) 算法通常使用递归实现,在处理当前像素点的相邻像素点时,递归调用 DFS 函数,不断深入直到无法找到相邻像素为止。

BFS (宽搜)算法通常使用队列实现,将起始像素点加入队列中,并不断扩展队列中的像素点,直到队列为空为止。

下面直接结合例题来理解两种解题方法。


3. 具体例题

第一道题 “图像渲染” 是下面例题中作为基座的一道题,讲解会尽量详细,后面的题

773.图像渲染

在这里插入图片描述

题意分析

即对于一个矩阵,我们随机选取一个元素,将该元素与对于其有相同值的上下左右 的元素(对上下左右的元素继续找上下左右) 均上色为color。本质上就是找区域内的相邻元素。

解法

DFS(深度优先搜索)

“搜索一个位置的上下左右坐标,并对其上下左右坐标继续搜索上下左右坐标” 的过程可以理解为将一个主问题分解为多个子问题的过程,适用递归解决。

我们知道:深度优先搜索即一条路走到死,而该递归过程也是深搜的思想。

在这里插入图片描述

思路

  1. 先记录题目给我们的当前位置的颜色prevColor,如果当前位置=color,则直接返回
  2. 以当前位置开始,直接进行递归操作
    • 对于递归函数:先将当前位置上色,随后遍历上下左右四个方向的元素,每遍历到一个元素
    • 对该元素进行进行dfs操作,即可完成对 相连像素点的上色

代码

class Solution {
private:int dx[4] = {0, 0, -1, 1}; // 用于计算上下左右位置的下标int dy[4] = {-1, 1, 0, 0};int m, n, _color, prevColor; // 定义成全局变量方便调用(也可以传参)// dfs: 查看 坐标(x, y) 上下左右四个元素,并更改颜色void dfs(vector<vector<int>>& image, int x, int y) {image[x][y] = _color; // 当前位置上色for(int k = 0; k < 4; ++k) {int nx = x + dx[k]; // 计算一个方向的坐标int ny = y + dy[k];// 确保不越界 以及 值相同if(nx >= 0 && nx < m && ny >= 0 && ny < n && image[nx][ny] == prevColor)dfs(image, nx, ny); // 递归找连结的需要上色的像素}}public:vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {prevColor = image[sr][sc]; // 记录最开始 / 当前元素颜色if(prevColor == color) return image; // 如果当前像素已经是所需颜色,则直接返回矩阵m = image.size(), n = image[0].size();_color = color;dfs(image, sr, sc);return image;}
};

BFS(宽度优先搜索)

宽度优先搜索利用队列和循环,每次将坐标入队,随后将其上下左右四个坐标入队,通过每次取队列元素,持续该过程,直到队列为空在这里插入图片描述

思路

  1. 先记录当前位置的颜色prevColor,如果当前位置=color,则直接返回
  2. 将当前位置坐标入队,每次提取当前元素的坐标,并将其上色
  3. 将上下左右四个方向的元素(符合条件的)入队,重复此过程

代码

class Solution {
public:typedef pair<int, int> pii;int dx[4] = {0, 0, -1, 1}; // 查询当前像素的上下左右时,xy坐标的更改int dy[4] = {-1, 1, 0, 0};vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {// 边界情况int tmp = image[sr][sc]; // 记录要更改的像素值if(tmp == color) return image;queue<pii> q; // 存储像素坐标q.push({sr, sc});while(!q.empty()){auto [a, b] = q.front(); // 提取当前像素坐标q.pop();image[a][b] = color; // 上色(更改像素值)for(int i = 0; i < 4; ++i){int x = a + dx[i];int y = b + dy[i];// 如果未越界则入队if(x >= 0 && x < image.size() && y >= 0 && y < image[0].size() && image[x][y] == tmp)q.push({x, y});}}return image;}
};

200.岛屿数量

在这里插入图片描述
题意分析

矩阵由’1’, ‘0’ 两个元素组成,每一块相连的1(横着竖着相连)组成的区域为一个岛屿,我们需要找到矩阵中岛屿的数量。

解法

DFS(深度优先搜索)

思路

  1. 首先我们定义visit数组,用于记录矩阵中每一位元素是否已经被遍历过,如果为true,则不去执行该位的操作。
  2. 遍历整个矩阵,对于矩阵的每个(符合要求的)元素都执行dfs算法
  3. 每次循环中dfs彻底结束,则统计出了一片岛屿,++ret(最终结果)
  4. 关于此题的dfs:
    • 用于将矩阵中坐标为(x, y)的元素及其上下左右的元素标记

代码

class Solution {
private:int ret = 0; // 定义为全局变量,方便dfs改动和调用int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};int m, n;vector<vector<bool>> visit; // 存储网格是否被检索过的信息private:// 通过递归,将当前位置的岛屿统计出来// dfs: 将grid[x][y] 位置所在的岛屿统计出来void dfs(vector<vector<char>>& grid, int x, int y) {// 将当前位置检索设置为truevisit[x][y] = true;for(int i = 0; i < 4; ++i){int nx = x + dx[i];int ny = y + dy[i];if((nx >= 0 && nx < m) && (ny >= 0 && ny < n) && !visit[nx][ny] && grid[nx][ny] == '1'){dfs(grid, nx, ny);}}}public:int numIslands(vector<vector<char>>& grid) {m = grid.size(), n = grid[0].size();visit.resize(m, vector<bool>(n, false)); // 初始化visit数组for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){if(!visit[i][j] && grid[i][j] == '1') // 该位置并未检索过且值为1{++ret;dfs(grid, i, j);}}}return ret;}
};

BFS(宽度优先搜索)

思路

  1. 这里宽搜和深搜的代码非常相似,只需要将bfs代码内部将递归改为使用队列即可,具体代码中注解,不再多余解释

代码

class Solution {
public:int ret = 0;int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};int m, n;vector<vector<bool>> visit; // 存储网格是否被检索过的信息void bfs(vector<vector<char>>& grid, int x, int y) {queue<pair<int, int>> q; // 存储位置坐标q.push({x, y});while(!q.empty()){auto [a, b] = q.front(); // 取出队头元素q.pop();for(int k = 0; k < 4; ++k) // 遍历该位置的上下左右四个位置{int nx = a + dx[k];int ny = b + dy[k];if((nx >= 0 && nx < m) && (ny >= 0 && ny < n) && !visit[nx][ny] && grid[nx][ny] == '1'){q.push({nx, ny});visit[nx][ny] = true;}}}}int numIslands(vector<vector<char>>& grid) {m = grid.size(), n = grid[0].size();visit.resize(m, vector<bool>(n, false));// 遍历矩阵for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){if(!visit[i][j] && grid[i][j] == '1'){++ret;bfs(grid, i, j);}}}return ret;}
};

695.岛屿的最大面积

在这里插入图片描述

题意分析

该题和上一题(岛屿数量)在解法和思路上可以说非常相似,只是前者要求岛屿数量,而后者要求岛屿最大面积,我们只需定义一个变量,每次统计一个岛屿后比较并更新该变量即可。

解法

DFS(深度优先搜索)

思路

重复:该题和上一题(岛屿数量)在解法和思路上可以说非常相似,只是前者要求岛屿数量,而后者要求岛屿最大面积,我们只需定义一个变量,每次统计一个岛屿后比较并更新该变量即可。

所以重点在于ret何时统计:

  1. 对于深搜,我们每次执行当前递归函数彻底结束后,执行ret = max(count, ret);即更新最大面积。
  2. 因为递归函数结束后,此时count记录的就是新岛屿的面积,则在dfs后更新最大面积。

代码

class Solution {
private:vector<vector<bool>> visit;int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};int ret = 0, count = 0; // 统计岛屿面积int m, n;public:void dfs(vector<vector<int>>& grid, int x, int y) {visit[x][y] = true; // 将当前位置检索设置为truefor(int k = 0; k < 4; ++k){int nx = x + dx[k];int ny = y + dy[k];if((nx >= 0 && nx < m) && (ny >= 0 && ny < n) && !visit[nx][ny] && grid[nx][ny] == 1) // 判断是否合法{++count;dfs(grid, nx, ny);}}   }int maxAreaOfIsland(vector<vector<int>>& grid) {m = grid.size(), n = grid[0].size();visit.resize(m, vector<bool>(n, false));for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){count = 1; // 当前位置开始(从1开始)if(!visit[i][j] && grid[i][j] == 1){dfs(grid, i, j);ret = max(count, ret); // 找到最大面积}}}return ret;}
};

BFS(宽度优先搜索)

思路

ret何时统计:

  1. 对于宽搜,我们每次执行一次bfs函数后,进行ret = max(ret, count),
  2. 因为执行dfs后,count的值就是新岛屿的面积,所以我们在其后判断更新最大面积。

代码

class Solution {
private:typedef pair<int, int> pii;vector<vector<bool>> visit;int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};int ret = 0, count = 0; // 统计岛屿面积int m, n;public:void bfs(vector<vector<int>>& grid, int x, int y) {visit[x][y] = true; // 将当前位置检索设置为truequeue<pii> q; // 队列存储坐标q.push({x, y}); // 将起始坐标添加到队列中while(!q.empty()) // 广度优先搜索{auto [x, y] = q.front();q.pop();for(int k = 0; k < 4; ++k){int nx = x + dx[k];int ny = y + dy[k];if((nx >= 0 && nx < m) && (ny >= 0 && ny < n) && !visit[nx][ny] && grid[nx][ny] == 1) // 判断是否合法{++count;visit[nx][ny] = true;q.push({nx, ny}); // 将新的岛屿坐标添加到队列中}}}   }int maxAreaOfIsland(vector<vector<int>>& grid) {m = grid.size(), n = grid[0].size();visit.resize(m, vector<bool>(n, false));for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){count = 1; // 当前位置开始(从1开始)if(!visit[i][j] && grid[i][j] == 1){bfs(grid, i, j);ret = max(ret, count);}}   }return ret;}
};

130.被围绕的区域

在这里插入图片描述

题意分析

  1. 题目要求我们找到所有被’X’围绕的’O’,并将其改为’X’
    • 被’X’围绕的’O’:
  2. 正难则反:我们直接找到被’X’围绕的’O’是有难度的,则可以逆转思维:
    • 找到所有不被’X’包围的’O’,将这些’O’改为’T’(改成任意字符)
    • 随后遍历矩阵,将所有’O’改为’X’,此时即完成了题目要求
    • 最后将’T’再改回X即可

在这里插入图片描述

解法

DFS(深度优先搜索)

思路

  1. 根据题意分析的思路讲解,首先遍历矩阵四边,对边缘元素执行dfs函数
    • dfs: 用于将当前位置联结的’O’改为’T’
  2. 当 循环+dfs 全部执行完毕,此时四边相连的’O’已经被改为’T’
  3. 遍历矩阵,将剩余的’O’改为’X’,将’T’改为’O’。

代码

class Solution {
public:int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};int m, n;// dfs将当前位置周围的'O'改为'T'void dfs(vector<vector<char>>& board, int x, int y) {board[x][y] = 'T';for(int k = 0; k < 4; ++k){int a = x + dx[k];int b = y + dy[k];if((a >= 0 && a < m) && (b >= 0 && b < n) && board[a][b] == 'O') // 保证合法性{dfs(board, a, b);}}}void solve(vector<vector<char>>& board) {m = board.size(), n = board[0].size();// 正难则反(将未被'X'围绕的'O'更改,然后遍历矩阵找'O')// 1. 矩阵四边周围的'O' 改为 'T'for(int i = 0; i < m; ++i) // 上下边{if(board[i][0] == 'O') dfs(board, i, 0);if(board[i][n - 1] == 'O') dfs(board, i, n - 1);}for(int j = 0; j < n; ++j) // 左右边{if(board[0][j] == 'O') dfs(board, 0, j);if(board[m - 1][j] == 'O') dfs(board, m - 1, j);}// 2. 还原:将'O'改为'X' / 将'T'改为'O'for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){if(board[i][j] == 'O') board[i][j] = 'X';if(board[i][j] == 'T') board[i][j] = 'O';}}}
};

BFS(宽度优先搜索)

思路

和前面的题一致,我们用队列进行宽搜的实现:

  1. 首先创建visit数组,用于记录矩阵中的每位是否被遍历过
  2. 除了bfs函数思路不同,其余与dfs完全一致,这里主要在于bfs的写法:
    • 先将当前位置入队,并在visit中改为true
    • 只要队列不为空,持续获取上下左右方向的元素,符合要求的入队更改为true,持续此过程。

代码

class Solution {
public:int dx[4] = {0, 0, -1, 1};int dy[4] = {-1, 1, 0, 0};vector<vector<bool>> visit;int m, n;// bfs将当前位置周围的'O'改为'T'void bfs(vector<vector<char>>& board, int x, int y) {queue<pair<int, int>> q;visit[x][y] = true;board[x][y] = 'T';q.push({x, y}); // 将起始位置入队while(!q.empty()){auto [x, y] = q.front();q.pop();for(int k = 0; k < 4; ++k){int nx = x + dx[k];int ny = y + dy[k];if((nx >= 0 && nx < m) && (ny >= 0 && ny < n) && !visit[nx][ny] && board[nx][ny] == 'O'){board[nx][ny] = 'T'; // 将'O'改为'T'visit[nx][ny] = true; // 修改为单等号q.push({nx, ny});}}}}void solve(vector<vector<char>>& board) {m = board.size(), n = board[0].size();visit.resize(m, vector<bool>(n, false)); // bfs算法中记录该位置是否被检查过// 正难则反(将未被'X'围绕的'O'更改,然后遍历矩阵找'O')// 1. 矩阵四边周围的'O' 改为 'T'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);}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);}// 2. 还原:将'O'改为'X' / 将'T'改为'O'for(int i = 0; i < m; ++i){for(int j = 0; j < n; ++j){if(board[i][j] == 'O') board[i][j] = 'X';if(board[i][j] == 'T') board[i][j] = 'O';}}}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/229914.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

setXxx getXxx 封装

1.封装介绍 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。 2.封装的理解和好处 (1)隐藏实现细节 方法(连接数据库)<-----调用(传入参数...) 只负责调…

寻找最大整数 C语言xdoj51

问题描述 从键盘输入四个整数&#xff0c;找出其中的最大值并将其输出。 输入说明 输入4个整数&#xff0c;用空格分隔 输出说明 输出值最大的一个整数 输入样例 25 99 -46 0 输出样例 99 #include <stdio.h>//寻找最大整数 int main() {int i, a[4]…

【Gradle】运行时一直要下载 gradle-8.5-bin.zip

如何解决 Downloading https://services.gradle.org/distributions/gradle-8.5-bin.zip 的问题 文章目录 1. 问题描述2. 解决方法1&#xff09;找到 gradle-wrapper.properties2&#xff09;修改 distributionUrl 对应的值 3. 验证 1. 问题描述 在执行 gradlew 命令的时候&…

【数据结构】(堆)Top-k|堆排序

目录 概念&#xff1a; 堆的实现 构建 初始化 销毁 插入元素 往上调整 删除堆顶元素 往下调整 返回堆顶元素 返回有效个数 是否为空 堆排序 Top-k问题 ​编辑 创建数据 堆top-k 概念&#xff1a; 堆是将数据按照完全二叉树存储方式存储到一维数组中&#xff…

[计网00] 计算机网络开篇导论

目录 前言 计算机网络的概念 计算机网络的分层 计算机网络的分类 网络的标准化工作和相关组织 计算机网络的性能指标 前言 计算机网络在我们的日常生活中无处不在 在网络会有各种各样的协议和封装 保证我们的信息完整,无误的在各个客户端之前传输 计算机网络的概念 四…

从文字下乡到人人学英语

从建国到改革开放&#xff0c;从恢复高考到新式教育改革&#xff0c;中国飞速发展&#xff0c;文字需求也在不断增大&#xff0c;在“地球村”的时代下&#xff0c;我们要“习文字之变&#xff0c;顺时代发展。” 古言道&#xff1a;“仓颉作书&#xff0c;后稷作稼”&#xff…

UE4 去除重复纹理

如果直接连的话&#xff0c;效果如下&#xff1a; 就存在很多重复的纹理&#xff0c;如何解决这个问题呢&#xff1f; 将同一个纹理&#xff0c;用不同的Tilling&#xff0c;将Noise进行Lerp两者之间&#xff0c;为什么要这么做呢&#xff1f;因为用一个做清晰纹理&#xff0c;…

设计模式——命令模式

引言 命令模式是一种行为设计模式&#xff0c; 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中&#xff0c; 且能实现可撤销操作。 问题 假如你正在开发一款新的文字编辑器&#xff0c; …

C语言—小小圣诞树

这个代码会询问用户输入圣诞树的高度&#xff0c;然后根据输入的高度在控制台上显示相应高度的圣诞树。 #include <stdio.h>int main() {int height, spaces, stars;printf("请输入圣诞树的高度: ");scanf("%d", &height);spaces height - 1;st…

Linux---远程登录、远程拷贝命令

1. 远程登录、远程拷贝命令的介绍 命令说明ssh远程登录scp远程拷贝 2. ssh命令的使用 ssh是专门为远程登录提供的一个安全性协议&#xff0c;常用于远程登录&#xff0c;想要使用ssh服务&#xff0c;需要安装相应的服务端和客户端软件&#xff0c;当软件安装成功以后就可以使…

论文阅读《DPS-Net: Deep Polarimetric Stereo Depth Estimation》

论文地址&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/html/Tian_DPS-Net_Deep_Polarimetric_Stereo_Depth_Estimation_ICCV_2023_paper.html 概述 立体匹配模型难以处理无纹理场景的匹配&#xff0c;现有的方法通常假设物体表面是光滑的&#xff0c;或者光照是…

express中实现将mysql中的数据导出为excel

express中实现将mysql中的数据导出为excel 安装node-excel cnpm install node-xlsx -S封装公用的导出方法 /*** 查询* param tableName: 表名* param sqlJson&#xff1a;需要拼接的SQL* returns {Promise<unknown>}*/ const find (tableName, sqlJson) > {return…

Linux——权限

个人主页&#xff1a;日刷百题 系列专栏&#xff1a;〖C语言小游戏〗〖Linux〗〖数据结构〗 〖C语言〗 &#x1f30e;欢迎各位→点赞&#x1f44d;收藏⭐️留言&#x1f4dd; ​ ​ 一、 Linux下用户的分类 Linux下有两种用户&#xff1a; 1. root&#xff08;超级管理员用户…

基于FPGA的HDMI编码模块设计(包含工程源文件)

前文已经通过FPGA实现了TMDS视频编码的算法&#xff0c;也对单沿数据采样转双沿数据采样的ODDR原语做了详细讲解和仿真验证&#xff0c;本文将这些模块结合&#xff0c;设计出HDMI编码模块&#xff0c;在HDMI接口的显示器上显示一张图片。 1、整体思路 如图1所示&#xff0c;是…

Github 2023-12-18 开源项目周报 Top14

根据Github Trendings的统计&#xff0c;本周(2023-12-18统计)共有14个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量TypeScript项目4Python项目4Jupyter Notebook项目3非开发语言项目1JavaScript项目1Rust项目1Go项目1 基于项目…

【5G PHY】5G小区类型、小区组和小区节点的概念介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

前后端传参中遇见的问题

前后端传参经常容易出错&#xff0c;本文记录开发springBootMybatis-plusvuecli项目中出现的传参问题及解决办法 1.前后端没有跨域配置&#xff0c;报错 解决方法&#xff1a;后端进行跨域配置&#xff0c;拷贝CorsConfig类 package com.example.xxxx.config;import org.spr…

web服务器之——基于虚拟目录和用户控制的web网站

目录 一、虚拟目录 虚拟目录的作用&#xff1a; 二、搭建基于虚拟目录的web网站 1、www服务器配置 2、搭建静态网站 设置防火墙状态 关闭文件访问权限——SeLinux 3、编辑网页资源文件 4、设置虚拟目录 5、向虚拟目录中写入资源 6、重启httpd 三、搭建基…

Flink系列之:监控反压

Flink系列之&#xff1a;监控反压 一、反压二、Task 性能指标三、示例四、反压状态 Flink Web 界面提供了一个选项卡来监控正在运行 jobs 的反压行为。 一、反压 如果你看到一个 task 发生 反压警告&#xff08;例如&#xff1a; High&#xff09;&#xff0c;意味着它生产数…

什么是缓存击穿、缓存穿透、缓存雪崩?

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…