数据结构与算法:洪水填充

瑞星  金牌会员 | 2025-3-17 07:08:32 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 953|帖子 953|积分 2859

前言

洪水填充是一种用在图上的搜刮算法,其过程就像洪水或病毒一样徐徐蔓延整个地区,继而到达遍历和统计相同属性的连通地区的功能,中间也可以通过每走过一个节点就设置路径信息的方法来到达剪枝的效果。
一、岛屿数目——洪水填充方法

  1. class Solution {
  2. public:
  3.     int numIslands(vector<vector<char>>&grid)
  4.     {
  5.         return solve2(grid);
  6.     }
  7.     //洪水填充方法
  8.     int solve2(vector<vector<char>>&grid)
  9.     {
  10.         int n=grid.size();
  11.         int m=grid[0].size();
  12.         int ans=0;
  13.         for(int i=0;i<n;i++)
  14.         {
  15.             for(int j=0;j<m;j++)
  16.             {
  17.                 if(grid[i][j]=='1')
  18.                 {
  19.                     dfs(i,j,grid);
  20.                     ans++;
  21.                 }
  22.             }
  23.         }
  24.         return ans;
  25.     }
  26.     void dfs(int i,int j,vector<vector<char>>&grid)
  27.     {
  28.         if(i<0||i==grid.size()||j<0||j==grid[0].size()||grid[i][j]!='1')
  29.         {
  30.             return ;
  31.         }
  32.         grid[i][j]=0;
  33.         dfs(i,j+1,grid);
  34.         dfs(i,j-1,grid);
  35.         dfs(i+1,j,grid);
  36.         dfs(i-1,j,grid);
  37.     }
  38.     //并查集方法
  39.     vector<int>father;
  40.     int sets;
  41.     int solve1(vector<vector<char>>& grid) {
  42.         int n=grid.size();
  43.         int m=grid[0].size();
  44.         build(n,m,grid);
  45.         for(int i=0;i<n;i++)
  46.         {
  47.             for(int j=0;j<m;j++)
  48.             {
  49.                 if(grid[i][j]=='1')
  50.                 {
  51.                     if(j>0&&grid[i][j-1]=='1')//左
  52.                     {
  53.                         Union(index(i,j,m),index(i,j-1,m));
  54.                     }
  55.                     if(i>0&&grid[i-1][j]=='1')//上
  56.                     {
  57.                         Union(index(i,j,m),index(i-1,j,m));
  58.                     }
  59.                 }
  60.             }
  61.         }
  62.         return sets;
  63.     }
  64.     void build(int n,int m,vector<vector<char>>&grid)
  65.     {
  66.         father.resize(n*m);
  67.         sets=0;
  68.         int idx;
  69.         //只有为“1”的格子才计算
  70.         for(int i=0;i<n;i++)
  71.         {
  72.             for(int j=0;j<m;j++)
  73.             {
  74.                 if(grid[i][j]=='1')
  75.                 {
  76.                     idx=index(i,j,m);
  77.                     father[idx]=idx;
  78.                     sets++;
  79.                 }
  80.             }
  81.         }
  82.     }
  83.     int find(int i)
  84.     {
  85.         if(i!=father[i])
  86.         {
  87.             father[i]=find(father[i]);
  88.         }
  89.         return father[i];
  90.     }
  91.     void Union(int x,int y)
  92.     {
  93.         int fx=find(x);
  94.         int fy=find(y);
  95.         if(fx!=fy)
  96.         {
  97.             father[fx]=fy;
  98.             sets--;
  99.         }
  100.     }
  101.     int index(int i,int j,int m)
  102.     {
  103.         return i*m+j;
  104.     }
  105. };
复制代码
 这个题如果看过我上一篇文章数据结构与算法:并查集的话对这个题应该不陌生,除了可以用并查集,还可以用洪水填充做。
根据洪水填充的过程,观察标题就可以发现,当出现一个“1”时,就开始洪水填充,感染所有连通的地区,相称于遍历完了一整个岛屿,所以要把所有点都改成0,然后让ans++即可。
这里洪水填充的函数接纳了dfs(深度优先搜刮)方法,固然也可以改成广度优先。如果没有越界大概碰到“水”,就先修改当前点为0,然后去上下左右感染。
二、被围绕的地区

  1. class Solution {
  2. public:
  3.     void solve(vector<vector<char>>& board) {
  4.         int n=board.size();
  5.         int m=board[0].size();
  6.         //先感染边界‘O’,画出不能修改的
  7.         for(int j=0;j<m;j++)
  8.         {
  9.             if(board[0][j]=='O')
  10.             {
  11.                 dfs(0,j,board);
  12.             }
  13.             if(board[n-1][j]=='O')
  14.             {
  15.                 dfs(n-1,j,board);
  16.             }
  17.         }
  18.         for(int i=0;i<n;i++)
  19.         {
  20.             if(board[i][0]=='O')
  21.             {
  22.                 dfs(i,0,board);
  23.             }
  24.             if(board[i][m-1]=='O')
  25.             {
  26.                 dfs(i,m-1,board);
  27.             }
  28.         }
  29.         //修改被包围的‘O’
  30.         for(int i=0;i<n;i++)
  31.         {
  32.             for(int j=0;j<m;j++)
  33.             {
  34.                 if(board[i][j]=='O')
  35.                 {
  36.                     board[i][j]='X';
  37.                 }
  38.                 if(board[i][j]=='F')
  39.                 {
  40.                     board[i][j]='O';
  41.                 }
  42.             }
  43.         }
  44.     }
  45.     void dfs(int i,int j,vector<vector<char>>&board)
  46.     {
  47.         if(i<0||i==board.size()||j<0||j==board[0].size()||board[i][j]!='O')
  48.         {
  49.             return ;
  50.         }
  51.         board[i][j]='F';
  52.         dfs(i-1,j,board);
  53.         dfs(i+1,j,board);
  54.         dfs(i,j-1,board);
  55.         dfs(i,j+1,board);
  56.     }
  57. };
复制代码
 这个题就需要一点预处理了,由于界限处是默认无法被困绕的,所以上来先顺着界限摸一遍,感染在界限位置无法被困绕的“O”。那么之后出现的“O”就都是可以被困绕的了,就遍历整个格子,有“O”就改成“X”,然后由于之前把界限处“O”改成了“F”,所以需要改回来。
三、最大人工岛

  1. class Solution {
  2. public:
  3.     int largestIsland(vector<vector<int>>& grid) {
  4.         int n=grid.size();
  5.         //设置编号
  6.         int id=2;
  7.         for(int i=0;i<n;i++)
  8.         {
  9.             for(int j=0;j<n;j++)
  10.             {
  11.                 if(grid[i][j]==1)
  12.                 {
  13.                     dfs(i,j,grid,id++);
  14.                 }
  15.             }
  16.         }
  17.         //统计大小
  18.         map<int,int>size;
  19.         int ans=0;
  20.         for(int i=0;i<n;i++)
  21.         {
  22.             for(int j=0;j<n;j++)
  23.             {
  24.                 if(grid[i][j]>1)
  25.                 {
  26.                     ans=max(ans,++size[grid[i][j]]);
  27.                 }
  28.             }
  29.         }
  30.         //遍历‘0’
  31.         vector<bool>visited(id,false);//去重
  32.         int up,down,left,right,sum;
  33.         for(int i=0;i<n;i++)
  34.         {
  35.             for(int j=0;j<n;j++)
  36.             {
  37.                 if(grid[i][j]==0)
  38.                 {   
  39.                     up=i==0?0:grid[i-1][j];
  40.                     down=i==n-1?0:grid[i+1][j];
  41.                     left=j==0?0:grid[i][j-1];
  42.                     right=j==n-1?0:grid[i][j+1];
  43.                     visited[up]=true;
  44.                     sum=size[up]+1;
  45.                     if(!visited[down])
  46.                     {
  47.                         sum+=size[down];
  48.                         visited[down]=true;
  49.                     }
  50.                     if(!visited[left])
  51.                     {
  52.                         sum+=size[left];
  53.                         visited[left]=true;
  54.                     }
  55.                     if(!visited[right])
  56.                     {
  57.                         sum+=size[right];
  58.                         visited[right]=true;
  59.                     }
  60.                     ans=max(ans,sum);
  61.                     visited[up]=false;
  62.                     visited[down]=false;
  63.                     visited[left]=false;
  64.                     visited[right]=false;
  65.                 }
  66.             }
  67.         }
  68.         return ans;
  69.     }
  70.     void dfs(int i,int j,vector<vector<int>>&grid,int id)
  71.     {
  72.         if(i<0||i==grid.size()||j<0||j==grid[0].size()||grid[i][j]!=1)
  73.         {
  74.             return ;
  75.         }
  76.         grid[i][j]=id;
  77.         dfs(i-1,j,grid,id);
  78.         dfs(i+1,j,grid,id);
  79.         dfs(i,j-1,grid,id);
  80.         dfs(i,j+1,grid,id);
  81.     }
  82. };
复制代码
 这个题就需要点思考了,首先分析标题可以想出整体思路就是先统计已经是岛屿的地区,接着再遍历每一个“0”,去看周围是否有岛屿。
接着就是分步解决题目,首先是统计已经是岛屿的地区,这个就是碰到是“1”的点洪水填充即可。但由于之后再遍历“0”的时间要获取周围岛屿的巨细,所以要知道每一个岛屿本身的巨细,所以考虑在洪水填充时给每个岛屿打上编号,接着设置一个map存岛屿编号对应巨细。需要注意的是,由于最后让返回最大岛屿的巨细,当情况是原装岛就是最大的时,要返回原装岛的巨细,所以在这一步就要统计ans最大值。
之后是遍历“0”的过程,由于当一个“0”周围存在同一片岛屿时,这个岛屿的巨细加一次就够了,所以还要考虑去重。具体方法就是设置一个visited数组,来到就设为true。之后遍历,每次统计周围岛屿的巨细之和即可。
四、打砖块

  1. class Solution {
  2. public:
  3.     vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
  4.         //思想————“时光倒流”->炮弹倒着看->之前炮弹对当前有影响
  5.         int n=grid.size();
  6.         int m=grid[0].size();
  7.         int h=hits.size();
  8.         vector<int>ans(h,0);
  9.         
  10.         //特例
  11.         if(n==1)
  12.         {
  13.             return ans;
  14.         }
  15.         //先让命中位置-1
  16.         for(int i=0;i<h;i++)
  17.         {
  18.             grid[hits[i][0]][hits[i][1]]--;
  19.         }
  20.         //天花板
  21.         for(int j=0;j<m;j++)
  22.         {
  23.             if(grid[0][j]==1)
  24.             {
  25.                 dfs(0,j,grid);
  26.             }
  27.         }
  28.         //“时光倒流”
  29.         for(int i=h-1;i>=0;i--)
  30.         {
  31.             grid[hits[i][0]][hits[i][1]]++;//命中位置补回来
  32.             if(worth(hits[i][0],hits[i][1],n,m,grid))
  33.             {
  34.                 ans[i]=dfs(hits[i][0],hits[i][1],grid)-1;
  35.             }
  36.         }
  37.         return ans;
  38.     }
  39.     //返回新增的安全格子数
  40.     int dfs(int i,int j,vector<vector<int>>&grid)
  41.     {
  42.         if(i<0||i==grid.size()||j<0||j==grid[0].size()||grid[i][j]!=1)
  43.         {
  44.             return 0;
  45.         }
  46.         grid[i][j]=2;
  47.         return 1+dfs(i-1,j,grid)+dfs(i+1,j,grid)+dfs(i,j-1,grid)+dfs(i,j+1,grid);
  48.     }
  49.     //是否值得填充
  50.     bool worth(int i,int j,int n,int m,vector<vector<int>>&grid)
  51.     {
  52.         return grid[i][j]==1&&  //有效命中
  53.         (i==0||   //在天花板上
  54.         (i>0&&grid[i-1][j]==2)||(i<n-1&&grid[i+1][j]==2)||(j>0&&grid[i][j-1]==2)||
  55.         (j<m-1&&grid[i][j+1]==2));  //周围有“2”
  56.     }
  57. };
复制代码
 这个题的思路就非常巧妙了,属于是初见肯定想不到的那种。
分析标题,由于之前的炮弹对当前的炮弹有影响,换言之,能让下面全部掉落的炮弹一定是最后一发,前面与之干系的炮弹可以明白为都是辅助炮,那么这里可以用到一个头脑——“时光倒流”。“时光倒流”的方法就是,先只遍历一遍炮弹的命中位置,接着从后往前看炮弹,统计掉落的砖块
所以首先只遍历炮弹命中位置,让该位置-1即可。
之后,考虑天花板处连接的砖块,直接连接天花板的砖块洪水填充即可。
经历上述过程后,肯定有剩余的“1”,这些就是会被炮弹打下来的砖块,所以此时进行“时光倒流”。首先把命中位置补回来,接下来的worth函数表示是否值得去洪水填充,即满足打中的是砖块且要么在天花板,可以连接下面的砖块,要么周围有“2”,即和天花板连接的砖块,那么就可以去洪水填充。由于要统计数目,所以这里的dfs函数需要返回连通砖块的个数,所以只需要返回附近之和再加本身的1即可。注意标题形貌被命中的砖块不算,所以ans要再减去1。
总结

其实感觉洪水填充和并查集有异曲同工之处,就像这几个题感觉都能改并查集,只是洪水填充会更快。而且洪水填充只在情况比较简朴的标题里速度比并查集快,劈面对上一篇里最后感染源头那种题时的发挥空间就比较有限了,所以这里就更体现出并查集给集合打标签的这个用法的优势了。
还剩不到一个月,要加快速度了啊!期待接下来的图论和动态规划章节!!
END


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表