当前位置:   article > 正文

算法:BFS解决 FloodFill 算法

算法:BFS解决 FloodFill 算法

目录

FloodFill 算法

题目一:图像渲染

题目二:岛屿数量

题目三:岛屿的最大面积

题目四:被围绕的区域


FloodFill 算法

在递归搜索回溯中已经说到过 FloodFill 算法了,但是那里是用 dfs 解决的,这里会使用 bfs 解决

FloodFill 算法(中文:洪水灌溉):

也就是找出性质相同的联通块

例如下图找出有几块会被洪水灌溉的区域,其中负数表示凹下去的地面,正数表示凸起的地面:

此时就会有两片区域会被洪水灌溉,也有问灌溉区域最大的面积是多少,或是灌溉区域最大的边是多少等等

按左边那个大的区域举例,之前的dfs,就是先一条路走到不能走时,再退回去,找其他路,也就是下面的紫色箭头的方式:

而bfs则是一层一层的扩展,每一层都关注该层的上下左右是否能扩展到其他地方,即下图所示:

共扩展了三层,就能够走完这个区域,这就是bfs的方式

总而言之,都是找出性质相同的联通块


题目一:图像渲染

有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。

你也被给予三个整数 sr ,  sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。

为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。

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

示例 1:

输入: image = [[1,1,1],[1,1,0],[1,0,1]],sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]]
解析: 在图像的正中间,(坐标(sr,sc)=(1,1)),在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,因为它不是在上下左右四个方向上与初始点相连的像素点。

示例 2:

输入: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, newColor = 2
输出: [[2,2,2],[2,2,2]]

这道题的题意就是:给我们一个起始位置(sr, sc),找到与起始位置相连且像素值相同的区域,都修改为newColor

也就是使用层序遍历,先找出起始位置的上下左右是否有相连的相同数字块,找到后,接下来再找刚刚找到的这些数字块的上下左右,有没有相连的,直到没有为止

并且在扩展的过程中,将像素值再修改为 newColor

在书写bfs时,非常常用的技巧就是:设置两个数组dx和dy,他们对应的位置组合来表示上下左右的移动,即dx[4] = {0, 0, 1, -1},dy[4] = {1, -1, 0, 0}

对应位置组合为:(0, 1),(0, -1),(1, 0),(-1, 0)

让原本的坐标,加上对应的这四个坐标,就能够完美表示上下左右的移动

代码如下:

  1. class Solution
  2. {
  3. // 简便表示pair,typedef重命名一下
  4. typedef pair<int, int> PII;
  5. int dx[4] = {0, 0, 1, -1};
  6. int dy[4] = {1, -1, 0, 0};
  7. public:
  8. vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color)
  9. {
  10. // 记录原本的像素值
  11. int prev = image[sr][sc];
  12. int m = image.size(), n = image[0].size();
  13. // 边界条件
  14. if(prev == color) return image;
  15. queue<PII> q;
  16. q.push({sr, sc});
  17. // 层序遍历
  18. while(!q.empty())
  19. {
  20. // 这样可以简便的取出pair里的内容,不需要使用first,second
  21. auto [a, b] = q.front();
  22. q.pop();
  23. image[a][b] = color;
  24. for(int i = 0; i < 4; i++)
  25. {
  26. // x、y是上下左右移动后的新坐标
  27. int x = a + dx[i];
  28. int y = b + dy[i];
  29. // 需要判断x、y是否越界访问image
  30. if(x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
  31. q.push({x, y});
  32. }
  33. }
  34. return image;
  35. }
  36. };

题目二:岛屿数量

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

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

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

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

依旧是使用bfs的方式,这道题需要注意的是:

全是1的联通块是一个岛屿,但是在遍历时,有可能会重复计算同一块,假设第一块的右边第二块也是1,此时遍历到第一块时,会计算第二块,而遍历到第二块时,也可能会找到第一块,这样会导致死循环,一直遍历

解决方式就是创建一个与原数组一样大的数组vis,都是bool类型的,每遍历到一块区域,就将vis数组中对应位置的数设置为true,这样就不会重复遍历了

所以下面代码实现时,每次遍历到一个位置时,需要将vis数组中的该位置改为true,之后只有当vis数组对应位置为false时才遍历

代码如下:

  1. class Solution
  2. {
  3. typedef pair<int, int> PII;
  4. // 判断数组,用于判断该位置是否被遍历过
  5. bool vis[301][301];
  6. int dx[4] = {0, 0, -1, 1};
  7. int dy[4] = {-1, 1, 0, 0};
  8. // m、n定义为全局的,便于bfs函数能取到值
  9. int m, n;
  10. public:
  11. int numIslands(vector<vector<char>>& grid)
  12. {
  13. int ret = 0;
  14. m = grid.size(), n = grid[0].size();
  15. for(int i = 0; i < m; i++)
  16. {
  17. for(int j = 0; j < n; j++)
  18. {
  19. // 当等于1时,还需要判断vis数组中对应位置是否为false
  20. if(grid[i][j] == '1' && vis[i][j] == false)
  21. {
  22. // 每进入一次层序遍历,表示找到了一个岛,ret++
  23. ret++;
  24. bfs(grid, i, j);
  25. }
  26. }
  27. }
  28. return ret;
  29. }
  30. // bfs层序遍历函数
  31. void bfs(vector<vector<char>>& grid, int i, int j)
  32. {
  33. queue<PII> q;
  34. q.push({i, j});
  35. vis[i][j] = true;
  36. while(!q.empty())
  37. {
  38. auto [a, b] = q.front();
  39. q.pop();
  40. for(int num = 0; num < 4; num++)
  41. {
  42. // 上下左右四块
  43. int x = a + dx[num];
  44. int y = b + dy[num];
  45. // 判断不越界的同时,还需要判断vis数组是否为false
  46. if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1' && vis[x][y] == false)
  47. {
  48. // 每次遍历到后,将vis数组对应位置改为true
  49. vis[x][y] = true;
  50. q.push({x, y});
  51. }
  52. }
  53. }
  54. }
  55. };

题目三:岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。

岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

岛屿的面积是岛上值为 1 的单元格的数目。

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

示例 1:

输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1

示例 2:

输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0

这道题与上一题一样,也是需要vis数组,标记某个位置是否遍历过,方法也与上一题几乎一模一样,只是在统计每一岛屿时,加了一个变量count,每次入队列时count++,最后返回给主函数即可

代码如下:

  1. class Solution
  2. {
  3. public:
  4. typedef pair<int, int> PII;
  5. // 判断数组,用于判断该位置是否被遍历过
  6. bool vis[60][60];
  7. int dx[4] = {0, 0, -1, 1};
  8. int dy[4] = {-1, 1, 0, 0};
  9. // m、n定义为全局的,便于bfs函数能取到值
  10. int m, n;
  11. int maxAreaOfIsland(vector<vector<int>>& grid)
  12. {
  13. m = grid.size(), n = grid[0].size();
  14. int ret = 0;
  15. for(int i = 0; i < m; i++)
  16. {
  17. for(int j = 0; j < n; j++)
  18. {
  19. if(grid[i][j] == 1 && vis[i][j] == false)
  20. {
  21. int num = bfs(grid, i, j);
  22. ret = max(ret, num);
  23. }
  24. }
  25. }
  26. return ret;
  27. }
  28. int bfs(vector<vector<int>>& grid, int i, int j)
  29. {
  30. int count = 0;
  31. queue<PII> q;
  32. q.push({i, j});
  33. count++;
  34. vis[i][j] = true;
  35. while(!q.empty())
  36. {
  37. auto [a, b] = q.front();
  38. q.pop();
  39. for(int k = 0; k < 4; k++)
  40. {
  41. int x = a + dx[k];
  42. int y = b + dy[k];
  43. if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1 && vis[x][y] == false)
  44. {
  45. q.push({x, y});
  46. count++;
  47. vis[x][y] = true;
  48. }
  49. }
  50. }
  51. return count;
  52. }
  53. };

题目四:被围绕的区域

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

  • 连接:一个单元格与水平或垂直方向上相邻的单元格连接。
  • 区域:连接所有 'O' 的单元格来形成一个区域。
  • 围绕:如果您可以用 'X' 单元格 连接这个区域,并且区域中没有任何单元格位于 board 边缘,则该区域被 'X' 单元格围绕。

通过将输入矩阵 board 中的所有 'O' 替换为 'X' 来 捕获被围绕的区域

示例 1:

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]

输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]

解释:

在上图中,底部的区域没有被捕获,因为它在 board 的边缘并且不能被围绕。

示例 2:

输入:board = [["X"]]

输出:[["X"]]


题目要求就是联通块中不能存在边缘的方块,否则就不改变,边缘指 m * n 的四周那一圈

解法一:直接做

这道题最容易想到的思路就是直接做:

遇到联通块时,先遍历一下这个联通快,判断这个区域内是否有边缘块,如果没有就改变,如果有就不改变,这种做法是需要每次遍历两遍联通块,比较复杂,下面看解法二

解法二:正难则反

既然正着做这道题比较困难,那就反着做,题目要求不能联通块不能出现在边缘,所以我们可以先从边缘开始判断,对边缘区域的联通块先层序遍历,将这些边缘区域的○修改为+

此时矩阵中剩余的○就是符合题意的,这时遍历整个矩阵,如果有○全部修改为×

最后再将刚刚边缘改为+的位置,再改回○即可

代码如下:

  1. class Solution {
  2. public:
  3. typedef pair<int, int> PII;
  4. // 判断数组,用于判断该位置是否被遍历过
  5. int dx[4] = {0, 0, -1, 1};
  6. int dy[4] = {-1, 1, 0, 0};
  7. // m、n定义为全局的,便于bfs函数能取到值
  8. int m, n;
  9. void solve(vector<vector<char>>& board)
  10. {
  11. m = board.size(), n = board[0].size();
  12. // 将边缘区域为O的先全部变为+
  13. for(int i = 0; i < m; i++)
  14. {
  15. if(board[i][0] == 'O') bfs(board, i, 0);
  16. if(board[i][n - 1] == 'O') bfs(board, i, n - 1);
  17. }
  18. for(int j = 0; j < n; j++)
  19. {
  20. if(board[0][j] == 'O') bfs(board, 0, j);
  21. if(board[m - 1][j] == 'O') bfs(board, m - 1, j);
  22. }
  23. // 再将剩余的O变为X,+变为O
  24. for(int i = 0; i < m; i++)
  25. for(int j = 0; j < n; j++)
  26. if(board[i][j] == 'O') board[i][j] = 'X';
  27. else if(board[i][j] == '+') board[i][j] = 'O';
  28. }
  29. // bfs实现边缘区域的O变+
  30. void bfs(vector<vector<char>>& board, int i, int j)
  31. {
  32. queue<PII> q;
  33. q.push({i, j});
  34. board[i][j] = '+';
  35. while(!q.empty())
  36. {
  37. auto [a, b] = q.front();
  38. q.pop();
  39. for(int k = 0; k < 4; k++)
  40. {
  41. int x = a + dx[k];
  42. int y = b + dy[k];
  43. if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
  44. {
  45. q.push({x, y});
  46. board[x][y] = '+';
  47. }
  48. }
  49. }
  50. }
  51. };

BFS解决 FloodFill 算法的题目到此结束

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号