一 问题描述
编程计算由 " 1 " 围成的下列图形的面积。面积的计算方法是统计" 1 "所围成的闭合曲线中水平线和垂直线交点的数目。如下图所示,在10*10的二维数组中,有" 1 "围住了15个点,因此面积为15。如图所示
以下提供了几个测试的案例:
二 解题思路
这道题可以用BFS广度优先搜索算法来解题,通过BFS找到所有不被“ 1 ”包围的“ 0 ”,将其置为“ 1 ”,最终去判断二维数组中还剩下多少个“ 0 ”就可以算出面积,重点可以关注图3,既然要算“ 1 ”围起来的面积,那么可以从图形的四边出发,因为如果不考虑四边的话,假设以图3为例,假设从非边缘的“ 0 ”出发,那么通过广度搜索算法,能够被遍历到的只是蓝色圈起来的那一部分。这样子,我们之后算“ 0 ”的个数,就会大于11,因为左上角的“ 0 ”也被算进去了,实际上,它是不应该被算进去的,因为它们并没有被" 1 "完全包围。这就是为什么要考虑四边的情况,因为从4个边出发,最终总是会把所有不被“ 1 ”包围的所有“ 0 ”遍历到。
三 核心代码实现
BFS我们可以利用队列这种数据结构来实现,但是在程序之中我们有几个点需要注意
1. 数据的输入问题
题目并没有告诉我们图形的行和列的值,因此,需要考虑用户输入的数据要如何进行存储?,我们可以利用文件的结尾来判断用户输入是否结束,把用户输入的行数和列数用两个变量记录起来。具体代码实现如下:
char c; int m,n; //m代表行,n代表列 int i=1,j=1,sum=0; //sum代表总共输入的字符个数 c = getchar(); while(c!=EOF) { if(c!=' ') //如果输入的不是空格 { if(c!='\n') //如果输入的不是空格,也不是换行,那么一定是0或者1,需要进行记录 { a[i][j]=c-'0'; //数组从(1,1)开始 sum = sum+1; //记录0和1的个数 j=j+1; //列数加1 } else //到达行尾 { n=j-1; //n代表列,j需要-1,因为上一次循环的时候j已经++了,需要减回来 i=i+1; //i代表行数,此时到达下一行,i需要+1 j=1; //从新的一行开始输入,j重新置为1 } } c=getchar(); } m=sum/n; //循环结束后,输入完毕,知道了列数,知道了总的0或1的个数,就可以算出行数,m代表行数
2. 当前点怎么扩展到周围的4个点?
因为是BFS,所以需要从周围一层一层进行扩展,那么当前点怎么扩展到四周呢?
我们可以用两个数组来进行点的扩展,在原来的基础上进行四个方向的拓展,假设当前坐标在(x,y) ,以下两个数组代表上下左右四个方向 :
int dx[4]={-1,1,0,0}; int dy[4]={0,0,-1,1};
(x,y)的上面那个单元格坐标为:(x+dx[0],y+dy[0])
(x,y)的下面那个单元格坐标为:(x+dx[1],y+dy[1])
(x,y)的左边那个单元格坐标为:(x+dx[2],y+dy[2])
(x,y)的右边那个单元格坐标为:(x+dx[3],y+dy[3])
3. 每一个坐标点怎么表示?
可以用一个结构体struct对坐标点(x,y)进行封装
struct Point{ int x; int y; };
4. 如何扫描不被1包围的0,并且将其置1?
可以利用队列这个数据结构来写代码,从上下左右四条边出发扫描,我们以下图为例,演示一下
第一步:扫面最上面的边(其它三条边的广搜步骤和第一条边一样),第一个坐标为(1,1),将其入队
第二步:判断队列是否为空,如果不为空,将队头元素弹出,并且判断这个坐标的值是否为0,如果是,将它的值置1,代表这个坐标已经访问过了,并且扫描这个被弹出的元素的上下左右四个方向的坐标,如果它周围的四个坐标还没有被访问过的话(值为0),就将其入队,请注意,要特别注意画下划线的那句话,代码的顺序如果写错,那可是天差地别的!!!,此时,可以看出,我们要将如下两个点入队
第三步:重复第二步,将队头元素弹出,此时就是弹出(2,1),并判断它是不是为0,如果是的话,将坐标对应的值置为1,然后继续扫描这个坐标上下左右四个方向的坐标.......以此类推。
注意:一定要去判断当前弹出队列的这个点是不是已经被访问过了,因为有的点可能会被入队列多次,如果不去判断这个点是否被访问过,会出现同一个点的四周被扫描两次,出现混乱。
四 完整代码实现
整个程序的完整代码如下:
#include<bits/stdc++.h>
using namespace std;
struct Point{
int x;
int y;
};
int m,n,s,sum;
int a[100][100];
void prt(int m,int n)
{
int i,j;
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
cout<<left<<setw(2)<<a[i][j];
}
cout<<endl;
}
}
queue<struct Point> q;
void epd(int i,int j)
{
int k,ni,nj;
//代表上下左右四个方向
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
struct Point p;
p.x=i;p.y=j;
q.push(p); //入队
while(!q.empty())
{
p=q.front(); q.pop();
i=p.x; j=p.y;
if(a[i][j]==0) //这个不能可有可无,访问的是一个没有访问过的点,而且可达,访问过就不用入队了 ,如有的点会入队多次,比如2,2 ,如果有if,我们还会再判断,这个点是不是0,此时它已经不是了
{
a[i][j]=1;
for(k=0;k<=3;k++) //探索四个方向
{
ni=i+dx[k];
nj=j+dy[k]; //新坐标
if(1<=ni&&ni<=m&&1<=nj&&nj<=n&&a[ni][nj]==0)
{
//cout<<ni<<" "<<nj<<endl;
p.x=ni;p.y=nj;
q.push(p);
}
}
}
}
}
int main()
{
int i,j,ans=0;
char c;
i=1;j=1;sum=0;
c=getchar();
while(c!=EOF)
{
if(c!=' ')
{
if(c!='\n')
{
a[i][j]=c-'0';
sum = sum+1;
j=j+1;
}
else //到达行尾
{
n=j-1; //n代表列
i=i+1;
j=1;
// cout<<"haha"<<endl;
}
}
c=getchar();
}
m=sum/n;
//prt(m,n);
//探索四条边
for(j=1;j<=n;j++)
{
epd(1,j);
epd(m,j);
}
for(i=1;i<=m;i++)
{
epd(i,1);
epd(i,n);
}
// prt(m,n); if不能可有可无
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
if(a[i][j]==0) ans++;
cout<<ans;
return 0;
}