题目1——数房子

当时做题时,有思路,就是码不出代码来,平时练习少了,呵呵。
今天突然想起了这道题,然后就边查资料边做,最后还是学习到了不少。
下面进入正题,首先我们来看看题目:

图片

题目什么意思呢?如下图,

图片

红色方框里的1都是要么左右、要么上下挨着的,而红色圆圈里的1之间却是隔着0的,所以方框里的1表示的同一栋房屋,圆圈里的1分别表示了一栋房屋。

如何判断他们是同一栋房屋呢?我们可以这样思考。
遍历整个表,当我们找到一个1后,就要审查它的两个方向,它的右边与下边,如果是1,那么这些1都代表的是同一座房屋。那么我们继续分别寻找右边跟下边的两个点的右边与下边的点。这样对于每个1,我们都先审查右边,再审查下边,进行递归地检查,就能判断出一个片区内的同一栋房屋了。

这里会出现一个问题,当遍历整个表时,我们会重复地查询到每一个1,而导致结果出错。
按题目的要求,连成一片的1,只算作一个1,那么我们就有必要在检测到的每一栋房屋,就要做下标记,当遍历整个表的时候,遇到同一座楼栋的1时,我们就不进行递归检测了。
所以,解决办法就是重新开辟另一个临时的表,大小与原表一样大, 用来进行标记作用。
这样,整个题目的思路我们就理清楚了。

根据分析的思路,我们开始编写计算房屋数量的函数。代码如下:

int Sum(int (*grid)[10],int n,int m)
{
//  static int tempgrid[10][10];
    int tempgrid[10][10];
    memset(tempgrid,0,sizeof(tempgrid));
    int i,j,flag=0;
    int sum=0;
    for(i=0;i<n;i++)
    {
        for(j=0;j<m;j++)
        {
            if(grid[i][j]==1&&tempgrid[i][j]==0)
            {
                markRoof(grid,tempgrid,i,j,n,m,&flag);              
                sum++;
                if(flag==1)
                {
                    sum--;
                    flag=0;
                }
            }
        }
    }
    return sum; 
}

首先,我们要做的便是声明一个临时的表,作为标记用。
(用static声明变量有好处,比如我们就不用再调用memset()函数对表进行初始化0的操作了。但在这里不适合,static修饰后,变量就是静态的了,当我们的输入数据不再是1组的时候,那么第一组数据的结果就会影响到第二组数据的结果,导致结果出错误)
接着,把整张表全部初始化为0。
然后,我们开始遍历整个表,当遇到第一个1时,我们就不断地递归查询属于同一楼栋的1,并把同一楼栋的1在临时表里进行标记。
当继续遍历到下一个1时,我们就要在临时表里查看下,这个1是不是属于以前标记过了的,若标记过,则说明它属于已经计数过了的楼栋,若没标记过,则说明这是一栋独立的楼,那么我们继续进行刚才的过程,递归的找出同属于这栋楼的所有1,并进行标记。

这里会出现一个问题,在递归的过程中,我们只检测了右边跟下边,若过程中左边出现了1,那么怎么办呢?解决办法就是:

if(flag==1)
{
    sum--;
    flag=0;
}

使用一个标志flag,当在递归检查的过程中,发现路径上的点曾经被标记过,那么说明这个起始点应该是属于已经检查过的某一栋房子的同一个屋顶,此时将sum减一,保证了不重复计数。

接着,我们编写递归检测房屋函数。代码如下:

void markRoof(int (*a)[10],int (*tempa)[10],int i,int j,int n,int m,int *flag)
{
    if(tempa[i][j]==1)
        *flag=1;
    tempa[i][j]=1;
    if(j+1<=m-1 && a[i][j+1]==1)
        markRoof(a,tempa,i,j+1,m,n,flag);
    if(i+1<=n-1 && a[i+1][j]==1)
        markRoof(a,tempa,i+1,j,m,n,flag);
}

函数形参有点多。第一个a,代表的是原表。第二个tempa,代表的是我们用来标记的表。第三、四个i、j 传入的是当前检测的位置。第五、六个m、n 传入的是原始表的大小,其作用是作为边界条件,我们检测的范围不能超出整个表是吧。
函数方法还是比较简单的,根据我们的思路,当能进行递归操作时,说明这个点一定代表它的值是1,所以这个函数一进来就先在临时数组中,把这个点标记为1了,避免以后遍历到此点时,又进行递归操作了。
另外,这里隐含了一个优先级问题,即先检测右边的项,再检测下边的项。

最后,我们就可以进行测试了。

    int main()
    {
    //  int i,j;
        int n=10,m=10;
        int grid[10][10]={
                            {0,0,0,0,0,0,0,0,0,0},
                            {0,0,0,0,0,0,0,0,0,0},
                            {0,0,0,1,0,1,0,0,0,1},
                            {0,1,1,1,0,0,0,0,0,1},
                            {0,0,0,0,1,0,0,0,0,1},
                            {0,1,1,1,0,0,0,1,0,0},
                            {0,0,0,0,0,0,0,0,0,0},
                            {0,0,0,0,0,0,0,0,0,0},
                            {0,0,0,0,0,0,0,0,0,0},
                            {0,0,0,0,0,0,0,0,0,0}};
    //  scanf("%d%d",&n,&m);
    //  for(i=0;i<n;i++)
    //      for(j=0;j<m;j++)
    //          scanf("%d",&grid[i][j]);
        printf("%d\n",Sum(grid,n,m));
        return 0; 
    }

输出值为6,表明程序没问题。

总结一下,编程的难点:

  • 写递归函数,要注意边界条件,要知道什么时候返回。
  • 用static修饰声明变量,其会自动初始化为0,较方便。但是,当变量被修饰为静态后,多次调用函数就会产生耦合,导致结果出错,要小心使用。
  • 要注意当想传入二位数组作为函数某个形参的方法,如代码所示,在形参声明时,要给出第二维的大小,这样我们在调用函数时才能直接用二维数组名称传入。具体解释如下博客:

http://blog.csdn.net/yunyun1886358/article/details/5659851

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值