代码随想录第51 52天 | 图论-岛屿问题汇总
图论基础介绍传送门:代码随想录第50天 | 图论 基础介绍(新篇章-CSDN博客
岛屿数量 深搜
岛屿数量 深搜
深度搜索是dfs,思路是:遍历矩阵的所有,找到地面,此时岛屿数+1,接着进行dfs,把与该地面相连的所有地面(上下左右四个方向),全部设为0,也就是地面记为已访问。再接着去找新的地面
1. 下面这是一种写法,当然也可以用visited数组来去记录地面已经访问.(重点在于这个dfs无结束条件——这是因为在for循环去遍历上下左右过程中就已经把不符合条件的给筛掉了。)
#include<iostream>
#include <vector>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void dfs(vector<vector<int>>& c,int x,int y){//找与其连接的上下左右四个方向的!for(int i=0;i<4;i++){int nx=x+dx[i];int ny=y+dy[i];if (nx<0||nx>=c.size()|| ny<0 || ny>=c[0].size()||c[nx][ny]==0) {continue;}c[nx][ny]=0;//把遍历的位置进行初始化dfs(c,nx,ny);}
}int main(){//dfs的思路是,遍历整个矩阵,找到是陆地的,则岛屿数+1,且把这个矩阵周围的挨着的都记录为已被访问(可以赋值为0),继续遍历找陆地int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}int res=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res++;c[i][j]=0;//因为dfs不是先进行操作当前位置,而是操作下一个位置,则需要提前设为0dfs(c,i,j);}}}cout<<res;return 0;
}版本二:也可以把终止条件写在外面:
#include<iostream>
#include <vector>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void dfs(vector<vector<int>>& c,int x,int y){if(c[nx][ny]==0) return;//当这个地面被访问过或者是本身就不是地面,则不符合条件c[x][y]=0;//dfs先处理当前位置
、for(int i=0;i<4;i++){int nx=x+dx[i];int ny=y+dy[i];if (nx<0||nx>=c.size()|| ny<0 || ny>=c[0].size()) {continue;}dfs(c,nx,ny);}
}int main(){//dfs的思路是,遍历整个矩阵,找到是陆地的,则岛屿数+1,且把这个矩阵周围的挨着的都记录为已被访问(可以赋值为0),继续遍历找陆地int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}int res=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res++;//既然先处理当前位置,则这里不用再让c[i][j]设为0dfs(c,i,j);}}}cout<<res;return 0;
}方法1是 :dfs是处理下一个节点,判断是否能合法,则再传进dfs函数的就是合法节点。
方法2是:dfs先处理当前节点,且处理节点前要判断是否符合要求:被访问或者是否为地面?
岛屿数量 广搜
岛屿数量 广搜
其实这个广搜的思路和深搜的思路一样:都是先遍历矩阵,找到地面,然后岛屿数量++,接着寻找与这块地面相连的所有地面!设为已访问(可以采用visited数组,或者直接设矩阵对应位置为0)
区别是:BFS 使用队列,将当前陆地的邻居加入队列,并标记为已访问
这个过程的重点在于:只要加入队列,立刻标记,而不是:从队列中取出节点再标记
#include<iostream>
#include <vector>
#include<queue>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void bfs(vector<vector<int>>& c,int x,int y){queue<pair<int, int>> q;q.push({x,y});//放入队列则立即进行已访问的标记c[x][y]=0;while(!q.empty()){pair<int ,int> cur = q.front(); //队列出来方向的第一个元素q.pop();//记录了则弹出队列元素int curx = cur.first;int cury = cur.second;for(int i=0;i<4;i++){int nx=curx+dx[i];int ny=cury+dy[i];if (nx<0||nx>=c.size()|| ny<0 || ny>=c[0].size()||c[nx][ny]==0) {continue;}c[nx][ny] = 0;      // 标记为已访问q.push({nx, ny});}}}int main(){int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}int res=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res++;bfs(c,i,j);}}}cout<<res;return 0;
}岛屿的最大面积
岛屿的最大面积
DFS:
#include<iostream>
#include<vector>
using namespace std;int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};int res;
void dfs(vector<vector<int>>& c,int x,int y){if(c[x][y]==0) return;c[x][y]=0;res++;for(int i=0;i<4;i++){int nx=dx[i]+x;int ny=dy[i]+y;if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()){continue;}dfs(c,nx,ny);}
}int main(){//使用dfsint n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}int result=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res=0;//每次要初始化一下,dfs先处理当前节点dfs(c,i,j);result = max(result, res);}}}cout<<result;return 0;
}BFS:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};int res;
void bfs(vector<vector<int>>& c,int x,int y){if(c[x][y]==0) return;queue<pair<int,int>> q;q.push({x,y});c[x][y]=0;res++;while(!q.empty()){pair<int,int> it=q.front();q.pop();int xx=it.first;int yy=it.second;for(int i=0;i<4;i++){int nx=dx[i]+xx;int ny=dy[i]+yy;if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()){continue;}c[nx][ny] = 0;      // 标记为已访问q.push({nx, ny});}}
}int main(){//使用bfsint n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}int result=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res=0;bfs(c,i,j);result = max(result, res);}}}cout<<result;return 0;
}孤岛的总面积
孤岛的总面积

首先,明确什么是孤岛?——岛屿中无任何与边缘接触的地面!
1. 先通过dfs把与边缘接触的岛屿都记录为已访问
2. 再遍历记录剩余岛屿的面积
#include<iostream>
#include<vector>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};void dfs(vector<vector<int>>& c,int x,int y){//先处理当前坐标if(x<0||x>=c.size()||y<0||y>=c[0].size()||c[x][y]==0){return;}c[x][y]=0;for(int i=0;i<4;i++){int nx=x+dx[i];int ny=y+dy[i];dfs(c,nx,ny);}
}int main(){//孤岛是位于矩阵内部,所有单元格都不接触边缘的岛屿int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}// 移除接触边缘的岛屿for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if ((i == 0 || i == n-1 || j == 0 || j == m-1) && c[i][j] == 1) {//若是接触边缘的且是地面的则一定不符合要求!直接标记周围的为已访问dfs(c, i, j);}}}int res=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res++;//现在去掉了所有的不符合要求的岛屿,则直接统计}}}cout<<res;return 0;
}BFS:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};void bfs(vector<vector<int>>& c,int x,int y){queue<pair<int,int>> q;q.push({x,y});c[x][y]=0;while(!q.empty()){pair<int,int> it=q.front();q.pop();int cx=it.first;int cy=it.second;for(int i=0;i<4;i++){int nx=cx+dx[i];int ny=cy+dy[i];if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()||c[nx][ny]==0){continue;}c[nx][ny] = 0;  // 标记为已访问q.push({nx, ny});  // 入队,不是递归调用}}
}int main(){//孤岛是位于矩阵内部,所有单元格都不接触边缘的岛屿int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}// 移除接触边缘的岛屿for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if ((i == 0 || i == n-1 || j == 0 || j == m-1) && c[i][j] == 1) {//若是接触边缘的且是地面的则一定不符合要求!直接标记周围的为已访问bfs(c, i, j);}}}int res=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res++;//现在去掉了所有的不符合要求的岛屿,则直接统计}}}cout<<res;return 0;
}沉没孤岛
沉没孤岛
DFS:
#include<iostream>
#include<vector>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void dfs(vector<vector<int>>& c,int x,int y){c[x][y]=0;for(int i=0;i<4;i++){int nx=x+dx[i];int ny=y+dy[i];if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()||c[nx][ny]==0){continue;}dfs(c,nx,ny);}
}int main(){//孤岛是位于矩阵内部,所有单元格都不接触边缘的岛屿int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}vector<vector<int>> res(c);// 移除接触边缘的岛屿,也就是非孤岛for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if ((i == 0 || i == n-1 || j == 0 || j == m-1) && c[i][j] == 1) {dfs(c, i, j);}}}for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res[i][j]=0;//现在去掉了所有的不符合要求的岛屿,则直接统计}}}for(int i=0;i<n;i++){//最终输出for(int j=0;j<m;j++){cout<<res[i][j]<<" ";}cout<<endl;}return 0;
}BFS:(思路一样,只不过一个是深度搜索的逻辑,一个是广度搜索
#include<iostream>
#include<vector>
#include<queue>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void bfs(vector<vector<int>>& c,int x,int y){queue<pair<int,int>> q;q.push({x,y});c[x][y]=0;while(!q.empty()){pair<int,int> it=q.front();q.pop();int cx=it.first;int cy=it.second;for(int i=0;i<4;i++){int nx=cx+dx[i];int ny=cy+dy[i];if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()||c[nx][ny]==0){continue;}q.push({nx,ny});c[nx][ny]=0;}}
}int main(){//孤岛是位于矩阵内部,所有单元格都不接触边缘的岛屿int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}vector<vector<int>> res(c);// 移除接触边缘的岛屿,也就是非孤岛for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if ((i == 0 || i == n-1 || j == 0 || j == m-1) && c[i][j] == 1) {bfs(c, i, j);}}}for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(c[i][j]==1){res[i][j]=0;//现在去掉了所有的不符合要求的岛屿,则直接统计}}}for(int i=0;i<n;i++){//最终输出for(int j=0;j<m;j++){cout<<res[i][j]<<" ";}cout<<endl;}return 0;
}水流问题
水流问题

假设要遍历每个点,每个点都需要进行广度或深度搜索(这个是需要遍历所有节点的),则时间复杂度为:O(n*m*n*m),前一个n*m表示遍历每个点去深搜或者广搜的时间复杂度,后面的是指广搜和深搜的时间复杂度
显而易见会超时
假设使用逆向思维:(上面两题去先去找边缘的陆地,从而去找出不符合要求的岛屿)这一题,也可以从边缘出发,假设从第一边界和第二边界出发,去往高处走……
#include<iostream>
#include<vector>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};void dfs(vector<vector<int>>& c,int x,int y,vector<vector<bool>>& visited){//使用引用,这样可以直接赋值visited[x][y]=true;for(int i=0;i<4;i++){int nx=x+dx[i];int ny=y+dy[i];if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()){continue;}if(c[x][y]<=c[nx][ny]&&!visited[nx][ny]){//且未被访问!dfs(c,nx,ny,visited);}}
}int main(){int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));//数值表示该位置的相对高度for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}//水流流向等高或较低的相邻(上下左右)的地点//第一组边界是左和上vector<vector<bool>> firstb(n,vector<bool>(m,false));for(int i=0;i<n;i++){if(!firstb[i][0]) dfs(c,i,0,firstb);}for(int j=0;j<m;j++){if(!firstb[0][j]) dfs(c,0,j,firstb);}//第二组边界是右和下vector<vector<bool>> secondb(n,vector<bool>(m,false));for(int i=0;i<n;i++){if(!secondb[i][m-1]) dfs(c,i,m-1,secondb);}for(int j=0;j<m;j++){if(!secondb[n-1][j]) dfs(c,n-1,j,secondb);}for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(firstb[i][j]&&secondb[i][j]){cout<<i<<" "<<j<<endl;}}}return 0;
}BFS:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};void bfs(vector<vector<int>>& c,int x,int y,vector<vector<bool>>& visited){//使用引用,这样可以直接赋值queue<pair<int,int>> q;q.push({x,y});visited[x][y]=true;while(!q.empty()){auto it=q.front();q.pop();int cx=it.first;int cy=it.second;for(int i=0;i<4;i++){int nx=cx+dx[i];int ny=cy+dy[i];if(nx<0||nx>=c.size()||ny<0||ny>=c[0].size()){continue;}if(c[cx][cy]<=c[nx][ny]&&!visited[nx][ny]){//且未被访问!q.push({nx,ny});visited[nx][ny]=true;}}}}int main(){int n,m;cin>>n>>m;vector<vector<int>> c(n,vector<int>(m));//数值表示该位置的相对高度for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>c[i][j];}}//水流流向等高或较低的相邻(上下左右)的地点//第一组边界是左和上vector<vector<bool>> firstb(n,vector<bool>(m,false));for(int i=0;i<n;i++){if(!firstb[i][0]) bfs(c,i,0,firstb);}for(int j=0;j<m;j++){if(!firstb[0][j]) bfs(c,0,j,firstb);}//第二组边界是右和下vector<vector<bool>> secondb(n,vector<bool>(m,false));for(int i=0;i<n;i++){if(!secondb[i][m-1]) bfs(c,i,m-1,secondb);}for(int j=0;j<m;j++){if(!secondb[n-1][j]) bfs(c,n-1,j,secondb);}for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(firstb[i][j]&&secondb[i][j]){cout<<i<<" "<<j<<endl;}}}return 0;
}建造最大岛屿
104.建造最大岛屿

首先想到的是一个暴力的解法:遍历矩阵,若遇到海洋,则把其变为陆地,计算最大岛屿面积(深搜或者广搜),最终找这些最大岛屿面积中的最大值。
优化思路是:
先进行一次搜索把每块岛屿,进行编号+记录其面积
再遍历海洋,遇到海洋若其四周有岛屿,则直接1+该岛屿的面积,直接得出最大岛屿面积,而不需要再去深搜或广搜去计算这个最大岛屿面积(也就是主要优化掉上面蓝字部分
-  第一次遍历:给每个岛屿编号(从0开始增加),且记录每个岛屿的面积(也需要有一个count遍历先记录)(使用islandArea[id]来记录编号id的岛屿面积 
-  第二次遍历⭐:遇到海洋时,设置初始面积为1,查看四周的岛屿(遍历上下左右四个方向)(需要把岛屿位置和岛屿对应上,用islandID[x][y]来记录(x,y)属于哪个岛屿),若周围是陆地,则获取位置对应的编号,则加上这个岛屿的面积(前提是没有加上) -  这里还需要一个去重数组,记录是否加上过该岛屿面积。 
-  使用unordered_set去自动去重 
 
-  
#include<iostream>
#include<vector>
#include<unordered_set>
using namespace std;int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};vector<vector<bool>> visited;
vector<vector<int>> islandID;
vector<int> islandArea;
int currentIsland = 0;
int currentCount = 0;void dfs(vector<vector<int>>& c, int x, int y) {//第一次遍历visited[x][y] = true;islandID[x][y] = currentIsland;currentCount++;for(int i = 0; i < 4; i++) {int nx = x + dx[i];int ny = y + dy[i];if(nx < 0 || nx >= c.size() || ny < 0 || ny >= c[0].size() || visited[nx][ny] || c[nx][ny] == 0) {continue;}dfs(c, nx, ny);}
}// 计算填海后的面积,第二次遍历
int calculateArea(vector<vector<int>>& c, int i, int j) {unordered_set<int> neighborIslands;  // 用set自动去重int total = 1;  // 填海后当前位置for(int k = 0; k < 4; k++) {int ni = i + dx[k];int nj = j + dy[k];if(ni >= 0 && ni < c.size() && nj >= 0 && nj < c[0].size() && c[ni][nj] == 1) {int id = islandID[ni][nj];neighborIslands.insert(id);  // 插入进这个岛屿的id,且会自动去重}}// 加上所有不同岛屿的面积(没加过的岛屿面积for(int id : neighborIslands) {total += islandArea[id];}return total;
}int main() {int n, m;cin >> n >> m;vector<vector<int>> c(n, vector<int>(m));for(int i = 0; i < n; i++) {for(int j = 0; j < m; j++) {cin >> c[i][j];}}visited = vector<vector<bool>>(n, vector<bool>(m, false));islandID = vector<vector<int>>(n, vector<int>(m, -1));// 第一步:给岛屿编号for(int i = 0; i < n; i++) {for(int j = 0; j < m; j++) {if(c[i][j] == 1 && !visited[i][j]) {currentCount = 0;dfs(c, i, j);islandArea.push_back(currentCount);currentIsland++;}}}// 第二步:找最大填海面积int maxArea = 0;for(int i = 0; i < n; i++) {for(int j = 0; j < m; j++) {if(c[i][j] == 0) {int area = calculateArea(c, i, j);maxArea = max(maxArea, area);//记录最大的面积}}}// 如果全是陆地if(maxArea == 0 && !islandArea.empty()) {for(int area : islandArea) {maxArea = max(maxArea, area);}}cout << maxArea << endl;return 0;
}岛屿的周长
岛屿的周长

这题首先分析,单独一个地面的周长是多少?——4,若周围出现地面,则周长减去1
则我们只需要遍历每个地面,求出其周围(上下左右四个方向)是否有地面?有则减去1
则实际上不需要dfs或者bfs进行操作
#include<iostream>
#include<vector>
using namespace std;int main() {int n, m;cin >> n >> m;vector<vector<int>> grid(n, vector<int>(m));for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {cin >> grid[i][j];}}int perimeter = 0;int dx[] = {0, 0, -1, 1};int dy[] = {1, -1, 0, 0};for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (grid[i][j] == 1) {// 每个陆地格子初始周长贡献为4int count = 4;// 检查四个方向for (int k = 0; k < 4; k++) {int ni = i + dx[k];int nj = j + dy[k];// 如果相邻是陆地,减去1if (ni >= 0 && ni < n && nj >= 0 && nj < m && grid[ni][nj] == 1) {count--;}}perimeter += count;}}}cout << perimeter << endl;return 0;
}