图论基础介绍传送门:代码随想录第50天 | 图论 基础介绍(新篇章-CSDN博客
岛屿数量 深搜
深度搜索是dfs,思路是:遍历矩阵的所有,找到地面,此时岛屿数+1,接着进行dfs,把与该地面相连的所有地面(上下左右四个方向),全部设为0,也就是地面记为已访问。再接着去找新的地面
1. 下面这是一种写法 ,当然也可以用visited数组来去记录地面已经访问.(重点在于这个dfs无结束条件------这是因为在for循环去遍历上下左右过程中就已经把不符合条件的给筛掉了。)
cpp
#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不是先进行操作当前位置,而是操作下一个位置,则需要提前设为0
dfs(c,i,j);
}
}
}
cout<<res;
return 0;
}
版本二:也可以把终止条件写在外面:
cpp
#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]设为0
dfs(c,i,j);
}
}
}
cout<<res;
return 0;
}
方法1 是 :dfs是处理下一个节点,判断是否能合法,则再传进dfs函数的就是合法节点。
方法2 是:dfs先处理当前节点,且处理节点前要判断是否符合要求:被访问或者是否为地面?
岛屿数量 广搜
其实这个广搜的思路和深搜的思路一样:都是先遍历矩阵,找到地面,然后岛屿数量++,接着寻找与这块地面相连的所有地面!设为已访问(可以采用visited数组,或者直接设矩阵对应位置为0)
区别是:BFS 使用队列,将当前陆地的邻居加入队列,并标记为已访问
这个过程的重点在于:只要加入队列,立刻标记,而不是:从队列中取出节点再标记
cpp
#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:
cpp
#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(){
//使用dfs
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 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:
cpp
#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(){
//使用bfs
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 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;
}
孤岛的总面积

首先,明确什么是孤岛?------岛屿中无任何与边缘接触的地面!
-
先通过dfs把与边缘接触的岛屿都记录为已访问
-
再遍历记录剩余岛屿的面积
cpp
#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:
cpp
#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:
cpp
#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:(思路一样,只不过一个是深度搜索的逻辑,一个是广度搜索
cpp
#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表示遍历每个点去深搜或者广搜的时间复杂度,后面的是指广搜和深搜的时间复杂度
显而易见会超时
假设使用逆向思维:(上面两题去先去找边缘的陆地,从而去找出不符合要求的岛屿)这一题,也可以从边缘出发,假设从第一边界和第二边界出发,去往高处走......
cpp
#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:
cpp
#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;
}
建造最大岛屿

首先想到的是一个暴力的解法:遍历矩阵,若遇到海洋,则把其变为陆地,计算最大岛屿面积(深搜或者广搜),最终找这些最大岛屿面积中的最大值。
优化思路是:
先进行一次搜索把每块岛屿,进行编号+记录其面积
再遍历海洋,遇到海洋若其四周有岛屿,则直接1+该岛屿的面积,直接得出最大岛屿面积,而不需要再去深搜或广搜去计算这个最大岛屿面积(也就是主要优化掉上面蓝字部分
-
第一次遍历:给每个岛屿编号(从0开始增加),且记录每个岛屿的面积(也需要有一个count遍历先记录)(使用islandArea[id]来记录编号id的岛屿面积
-
第二次遍历⭐:遇到海洋时,设置初始面积为1,查看四周的岛屿(遍历上下左右四个方向)(需要把岛屿位置和岛屿对应上,用islandID[x][y]来记录(x,y)属于哪个岛屿),若周围是陆地,则获取位置对应的编号,则加上这个岛屿的面积(前提是没有加上)
-
这里还需要一个去重数组,记录是否加上过该岛屿面积。
-
使用unordered_set去自动去重
-
cpp
#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进行操作
cpp
#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) {
// 每个陆地格子初始周长贡献为4
int count = 4;
// 检查四个方向
for (int k = 0; k < 4; k++) {
int ni = i + dx[k];
int nj = j + dy[k];
// 如果相邻是陆地,减去1
if (ni >= 0 && ni < n && nj >= 0 && nj < m && grid[ni][nj] == 1) {
count--;
}
}
perimeter += count;
}
}
}
cout << perimeter << endl;
return 0;
}