DFS模板以及例题——油田,数房子,全球变暖(用DFS求连通块)

首先是DFS模板套路: 

void DFS(...)//v是顶点
{
    访问结点相关操作;
    for(从结点的第一个相邻接点;结点没有相邻接点了即终止条件;下一个相邻结点)
    {
        if(相邻结点满足条件如未访问过)
        {
            对相邻结点调用DFS();
        }
    }
    return;
}   
int main()
{
    初始化是否访问标记;
    for(int v=xxx;v<Vnum;v++){//xxx表示从第xxx个顶点开始遍历,第xxx个结点是一个连通分量的任一结点。
                              //调用一次DFS为访问了图的一个连通分量的所有结点
                              //若已知图为连通的,则不需for循环,直接调用DFS()即可
        if(满足条件如为访问过)
        {
            调用DFS();
        }
    }
    return 0;
}

例题1:油田(Oil Deposits, UVa 572,ZOJ1709,POJ1562)
输入一个m行n列的字符矩阵,统计字符“@”组成多少个八连块。如果两个字符“@”所在的格子相邻(横、竖或者对角线方向),就说它们属于同一个八连块。例如,下图中有两个八连块。

输入描述:

输入文件中包含多个测试数据,每个测试数据描述了一个网格。每个网格数据的第1行为两个整数:m、n,分别表示网格的行和列;如果m=0,则表示输入结束,否则1<=m<=100,1<=n<=100。接下来有m行数据,每行数据有n个字符(不包括行结束符)。每个字符代表一个小方块,如果为“*”,则代表没有石油,如果为“@”,则代表有石油,是一个pocket。

输出描述:

对输入文件中的每个网格,输出网格中不同的油田数目。如果两块不同的pocket在水平、垂直或者对角线方向上相邻,则被认为属于同一块油田。每块油田所包含的pocket数目不会超过100。

样例输入:

5 5

****@

*@@*@

*@**@

@@@*@

@@**@

样例输出:

2

本题代码: 

/*
输入:
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
输出:
2*/ 
/*注意要求多组输入*/
#include<iostream>
#include<cstring>//memset在这个头文件里 
using namespace std;
#define max 100+5
char a[max][max];//油田
int visited[max][max]; 
int m,n;
//现在位置x,y 
void dfs(int x,int y)
{
	visited[x][y]=1;//将现在的位置变为已访问过
	for(int i=-1;i<=1;i++)
	{
		for(int j=-1;j<=1;j++)
		{//遍历移动的八个方向,对每一个方向做一次dfs
			int nx=x+i,ny=y+j;//向x方向移动i,向y方向移动j 
			if(nx>=0&&nx<=m-1&&ny>=0&&ny<=n-1&&visited[nx][ny]==0&&a[nx][ny]=='@')//若下一个位置在园子内并且没被访问过并且是油田 
			{
				dfs(nx,ny);
			}
		}
	}
	return;
}
int main()
{
	while(cin>>m>>n)
	{
		int res=0;//进行dfs的次数,即为图的连通分量的个数 
		if(m==0)
			break;
		memset(visited,0,sizeof(visited));//将visited数组全初始化为0,表示没访问过;1表示已访问过 
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)
			{
				cin>>a[i][j];
			}
		}
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)//从有油田@开始dfs 
			{
				if(visited[i][j]==0&&a[i][j]=='@')
				{
					dfs(i,j);
					res++;
				}
			}
		}
		cout<<res<<endl; 
	}
	return 0;
}

另一种DFS模板套路:

 void dfs(int step)
 {
      if(边界成立)
      {
          。。。。(相关操作)
          return;
      }
      for(尝试每一种可能)
      {
         把这种可能标记表示走过
         。。。。(相关操作)

         继续下一步dfs(step+1);
         把这种可能标记去除
      }
      return;
 }

本题另一种代码:

#include<iostream>
#include<cstring>
using namespace std;
#define max 1000+5
int m,n;
int visited[max][max];
char a[max][max];
void dfs(int x,int y)
{
	if(x<0||x>=m||y<0||y>=n||visited[x][y]==1||a[x][y]!='@')
		return;
	visited[x][y]=1;
	for(int i=-1;i<=1;i++)
	{
		for(int j=-1;j<=1;j++)
		{
			int nx=x+i,ny=y+j;
			dfs(nx,ny);
		}
	}
}
int main()
{
	while(cin>>m>>n)
	{
		if(m==0)
			break;
		int res=0;
		memset(visited,0,sizeof(visited));//将visited数组全初始化为0,表示没访问过;1表示已访问过.一定不要忘记初始化!!!因为是全局变量,每一次一定要把visited数组都初始化为0且a数组以及m,n重新输入的
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)
			{
				cin>>a[i][j];
			} 
		}
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)
			{
				if(visited[i][j]==0&&a[i][j]=='@')
				{
					dfs(i,j);
					res++;
				}
			} 
		}
		cout<<res<<endl; 
	}
	return 0;
}
 

例题2.数房子问题

问题描述

代码如下: 

/*
样例输入: 
5 5
0 0 0 0 0
0 1 1 0 0
0 1 0 0 1
0 1 0 1 0
0 0 0 1 0
样例输出:
3 
*/ 
#include<iostream>
using namespace std;
#define max 105
int a[max][max];
int visited[max][max];
int m,n;
int der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; 
void dfs(int x,int y)
{
	visited[x][y]=1;
	for(int i=0;i<4;i++)
	{
		int nx=x+der[i][0];
		int ny=y+der[i][1];
		if(nx>=0&&nx<=m-1&&ny>=0&&ny<=n-1&&visited[nx][ny]==0&&a[nx][ny]==1)
		{
			dfs(nx,ny);
		}
	}
	return;
}
int main()
{
	cin>>m>>n;
	int res=0;
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(visited[i][j]==0&&a[i][j]==1)//为1表示是房子
			{
				dfs(i,j);
				res++;
			}
		}
	}
	cout<<res<<endl;
	return 0;
}

 另一种方法(emmm其实两种方法是一样的啦)

/*
样例输入: 
5 5
0 0 0 0 0
0 1 1 0 0
0 1 0 0 1
0 1 0 0 1
0 1 1 1 1
样例输出:
1 
*/
#include<iostream>
using namespace std;
#define max 105
int a[max][max];
int visited[max][max];
int m,n;
int der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; 
void dfs(int x,int y)
{
	if(x<0||x>=m||y<0||y>=n||visited[x][y]==1||a[x][y]==0)
		return;
	visited[x][y]=1;
	for(int i=0;i<4;i++)
	{
		int nx=x+der[i][0];
		int ny=y+der[i][1];
		dfs(nx,ny);
	}
}
int main()
{
	cin>>m>>n;
	int res=0;
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(visited[i][j]==0&&a[i][j]==1)//为1表示是房子
			{
				dfs(i,j);
				res++;
			}
		}
	}
	cout<<res<<endl;
	return 0;
}

全球变暖——第九届蓝桥杯第9题

标题:全球变暖

你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

注意是有多少会被完全淹没,不是剩下了多少!!!且要注意若淹没后陆地更凌乱了,可能会岛屿数反而会加多,这时完全淹没的岛屿数应为0。

代码中我输出了中间过程,最后一行的数字是要求的结果。

/*
输入: 
7 
.......
.##....
.##....
....##.
..####.
...###.
.......  
输出:
1 
*/ 
#include<iostream>
#include<cstring> 
using namespace std;
#define maxn 100
int n;
int ans;
int re_ans;
char a[maxn][maxn];
int visited[maxn][maxn];
int dir[4][2]={-1,0,1,0,0,-1,0,1};
void dfs(int x,int y)
{
	
	for(int i=0;i<4;i++)
	{
		int nx=x+dir[i][0];
		int ny=y+dir[i][1];
		if(nx>=0&&nx<n&&ny>=0&&ny<n&&visited[nx][ny]==0&&a[nx][ny]=='#')
		{
			visited[nx][ny]=1;
			dfs(nx,ny);
		}
	}
	return;
}
bool panduan(int i,int j)
{
	if(a[i-1][j]=='.'||a[i+1][j]=='.'||a[i][j-1]=='.'||a[i][j+1]=='.')
	return true;
	else
	return false;
}
void yanmo()
{
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='#'&&panduan(i,j))
			{
				a[i][j]='a';
			}
		}
	}
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='a')
			{
				a[i][j]='.';
			}
		}
	}
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			cin>>a[i][j];
		}
	}
	ans=0;
	memset(visited,0,sizeof(visited));
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='#'&&visited[i][j]==0)
			{
				visited[i][j]=1;
				dfs(i,j);
				ans++;
			}
		}
	}
	cout<<"淹没前有"<<ans<<endl;
	
	yanmo();
	cout<<endl;
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			cout<<a[i][j];
		}
		cout<<endl;
	}
	
	re_ans=0;
	memset(visited,0,sizeof(visited));//一定要注意重新清0!! 
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='#'&&visited[i][j]==0)
			{
				visited[i][j]=1;
				dfs(i,j);
				re_ans++;
			}
		}
	}
	cout<<"淹没后有"<<re_ans<<endl;
	if(re_ans<ans)
	cout<<ans-re_ans<<endl;
	else
	cout<<0;
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值