问题描述:
球迷看台可以容纳M* N的人数,现统计一共有多少球迷群体,以及最大球迷群体的人数
PS:
1、相同球迷群体指的是会选择相邻的座位,相邻的座位就是前后相邻,左右相邻,斜对角相邻。
2、给定一个M*N的球场,0代表该位置没有人,1代表该位置有人。
要求输入:
第一行输入两个数字,M和N,使用英文逗号分隔
接下来M行,每行N个数字,使用英文逗号分割
输出描述:
一行,两个数字,第一个数字代表球迷群体个数,第二个表示最大的球迷群体人数,同样逗号隔开
话不多说,直接上代码,然后再分析
package com.Map;
import java.util.Scanner;
public class Shijiebei1 {
public static int[][] Graph;
public static int[][] Vis;
public static int sums=0;//用于保存每个群体的人数
public static int[][] position= {{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
public static int row;
public static int col;
public static int Max=0;//用于保存最大群体的人数
public static int ans=0;//用于保存球迷群体数
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while(sc.hasNext())
{
String str=sc.next();
String[] arr=str.split(",");
row=Integer.parseInt(arr[0]);
col=Integer.parseInt(arr[1]);
Graph=new int[row][col];
Vis=new int[row][col];
ans=0;
Max=0;
for(int i=0;i<row;i++)
{
String str1=sc.next();
String[] arr1=str1.split(",");
for(int j=0;j<col;j++)
{
Graph[i][j]=Integer.parseInt(arr1[j]);
}
}
if((col==row)&&(col==1)&&(Graph[0][0]==1))
{
ans=1;
Max=1;
}
else if((col==row)&&(col==1)&&(Graph[0][0]==0))
{
ans=0;
Max=0;
}
else {
for(int m=0;m<row;m++)
{
for(int n=0;n<col;n++)
{
Dfs(m,n,0);
}
}
}
System.out.println(ans +","+Max);
}
sc.close();
}
//深度优先遍历
public static void Dfs(int nowRow,int nowCol,int Steps)
{
if(Steps==0)
{
sums=0;
}
if(isFan(nowRow,nowCol))
{
Vis[nowRow][nowCol]=1;
sums++;
}else
{
return;
}
for(int k=0;k<8;k++)
{
int nnowRow=nowRow+ position[k][0];
int nnowCol=nowCol+ position[k][1];
if(isFan(nnowRow,nnowCol))
{
Dfs(nnowRow,nnowCol,Steps+1);
}
}
//只有完整进行过一次深度优先遍历的群体且中途不会退回主函数(也就是一个新群体)才会走到这里,往下执行。
Max=Math.max(Max, sums);
if(Steps==0)
{
ans++;
}
}
//判断该位置是否符合条件
public static boolean isFan(int nRow, int nCol){
if((nCol >= 0) && (nCol < col) &&( nRow >= 0) && (nRow < row) && (Vis[nRow][nCol] == 0) && (Graph[nRow][nCol] == 1)){
return true;
}else{
return false;
}
}
}
分析:
整个程序可以分为四部分,第一部分是变量的声明,第二部分是判断函数isFan()。第三部分是深度优先遍历函数Dfs(),最后一部分是主函数测试部分(包括按照要求的输入输出)
先来看第一部分
1、由于这里大部分的变量以及数组都是要贯穿整个类,所以都定义成静态变量,同样把函数都定义成静态函数,可以用来直接引用静态函数和静态变量,省去了定义类对象的麻烦。关于静态变量和静态函数可以参考我的另一篇帖子
关于静态变量和静态方法
2、这里需要三个数组,第一个Graph[][]毫无疑问是是用来存储数据的,第二个Vis[][]是用来判断当前位置是否被访问过,因为根据深度优先遍历原则,被访问过的位置不需要再递归调用Dfs。第三个数组应该是8*2的数组,用于与当前位置相加来获得当前位置的相邻位置。其中Graph[][]通过键盘输入;Vis不初始化,默认为0,通过将Vis[i][j]=1来表示该位置已经被访问;Position[][]按照方位初始化好。
第二部分
这部分就是用来判断当前位置。其中位置是否越界的判断
(nCol >= 0) && (nCol < col) &&( nRow >= 0) && (nRow < row)
必须要写在检查该位置是否有人以及是否被遍历过前面,否则的话会发生异常
(Vis[nRow][nCol] == 0) && (Graph[nRow][nCol] == 1)
因为(&&)保证了没有越界的情况下才会去判断该位置是否有人以及是否被遍历过,这样最起码保证了没有越界,所以不会发生异常。但是如果当前位置加上了position[][]之后发生了越界,由于判断越界在前,这个时候就不会再继续往下判断了。因为(&&)的特点是遇到false就停下,后面的不看了,这样做可以保证没有异常。
先来说最后一部分的测试吧
这里不得不说java的输出真的是太特么麻烦了,既然相比C++.Java提供了很多现成的包,那么它势必也得有比C++差很多的地方。
这里主要说一下java的按照题目要求的输入。
如果是直接输入两个数字,由空格相隔开,或者这两个数字分别在两行的话,可以直接用
int a=sc.nextInt();
int b=sc.nextInt();
亲测有用。但是题目要求通过逗号相隔,那就太特么麻烦了。具体思路是
(1)将要输入的数先按照字符串形式传入一个字符串中,然后用“,”对这个字符串进行分割,把分割出来的一个个数字再放到一个字符串数组中。(此时这个字符串数组里每个位置存储的实际上就是一个个逗号隔开的整型,但是字符串数组里面是用“ ”把一个个隔开的)
String str=sc.next();
String[] arr=str.split(",");
这两行代码就是实现了输入一行数据,每个数据之间用逗号隔开,不用逗号输入的话会报错。
比如输入10,10(一行数据输入多少都可以,但是要保证以逗号相隔,下面为二维数组赋值同样的道理)
它会将10,10以字符串的形式分别传入到arr[0],arr[1]
所以接下来,如果想利用实为整型但是却存储在字符串数组中的“10”,“10”就要用到parseInt()函数,将字符串转换化成整形数据。
row=Integer.parseInt(arr[0]);
col=Integer.parseInt(arr[1]);
所以这两行就是把输入的10,10分别赋给row和col作为二维数据的边界。至此四行代码才满足了题目的要求在一行输入两个数,且逗号相隔(真是哔了狗了)。这还没完。第一行输入两个数,接下来还要在row行中每行输入col个数,仍然是逗号相隔(二维数组赋值)。思路和前面一样。下面为二维数组赋值代码如下:
for(int i=0;i<row;i++)
{
String str1=sc.next();
String[] arr1=str1.split(",");
for(int j=0;j<col;j++)
{
Graph[i][j]=Integer.parseInt(arr1[j]);
}
}
接下来主函数剩下的就没有什么好说的了。if,if,else是用来判断极端情况的(只有一个座位,有人和没人)。这里要注意调用Dfs函数时,有第三个参数0,很重要,后面会讲。还有这些整个都是放在一个while(sc.nextInt())的循环体中,我试了去掉了这个外层循环也不影响最后的结果,我查了,百度一向如此,查不到你真的想要的,所以要是谁知道为什么,请告诉我!!!但是用了整个外层循环的话,不要忘记在最后加上sc.close()。
最后来说整个算法的核心Dfs()
(1)因为调用Dfs有两个渠道,第一个是主函数调用的,另一个是Dfs自身递归(因为深度优先遍历就是找到一个节点,然后遍历和它相邻的,然后再遍历和他相邻相邻的,然后直到没有,再一点点回到上一级,也就是递归),所以参数Steps的第一个作用就是判断这个Dfs是从哪里来的,要是是Dfs递归本身来的,就不用把sums 置成0了。所以在递归本身调用Dfs时候是Steps+1。这里我做了实验,只要这个第三个参数传入不是0其他的都可以(所以无论Steps加多少都行)。还有超级重要的一点(就是这个Steps它只是个形参变量,所以到最后它的值还是0,不会因为Steps+多少而改变,因为无论Steps+多少,最终的结果都是作为参数传进去了,不会改变Steps自身的大小。也可以换个想法就是再定义一个变量G=Steps+。然后G作为参数传进去,所以无论Steps加了多少,都是改变了G,不会改变了Steps本身),这个非常重要,因为这跟Steps的下一个功能相关。
if(Steps==0)
{
sums=0;
}
(2)第二个判断很好理解,就是判断是否符合继续往下走的条件。这里举个例子,就是如果从主函数来了一个位置,如果他满足条件(因为从主函数来的所以知道能够满足在范围内,所以主要看是不是1且有没有被遍历过),要是符合话那就sums++,并且可以继续往下for循环对这个位置深度优先遍历。但要不是0的话或者被遍历过了那么就没必要对这个位置进行遍历了。所以else中是return而不是return 0.又一个重点来了
return的意思是直接退出这个方法,返回主函数。 继续往下走,然后再在主函数中调用Dfs(m,m,0),接下来的什么for循环以下的都不会被执行。
if(isFan(nowRow,nowCol))
{
Vis[nowRow][nowCol]=1;
sums++;
}else
{
return;
}
继续往下看,要是主函数来的位置满足条件,就会进入到for循环,然后获得这个位置八个相邻位置第一个满足条件的进行判断,然后再进入到Dfs函数,进行重复的操作,直到不满足条件,然后一步步退出来到原始那个从主函数进来的位置(这里就不深讲深度优先遍历的内部过程了,头疼,真的)
所以可以看出来整个Dfs()函数中两次调用isFan函数,第一次是判断从主函数来的位置是否满足条件,第二次是判断满足条件的位置的八个相邻点是否满足条件,要是满足的话就还会用到第一个isFan,要是不满足(就是已经被遍历过了)也是直接退回主函数。所以那些是0的点以及那些被遍历过的点是不会经历Dfs()中for循还以后的语句了。所以最后总结重点:
那些在主函数来的符合条件的,以及能对这个位置进行一次完整的Dfs的,都会是一个新的群体,所以这个时候才会有以下两句(这两句都是只有新群体完整被深度优先遍历过才会执行的)
Max=Math.max(Max, sums);
if(Steps==0)
{
ans++;
}
通过这两句,可以比较当前最大群体人数以及当前群体数。(Steps第二个作用,只有能走到这一步的,才算是是一个新群体)最后在主函数中输出。