六角幻方--详解第五届蓝桥杯大赛Java本科B组决赛真题

来源:互联网 发布:网络借贷还不起怎么办 编辑:程序博客网 时间:2024/05/16 08:02
六角幻方

题目要求:

把 1 2 3 ... 19 共19个整数排列成六角形状,如下:



    * * *
   * * * *
  * * * * *
   * * * * 
    * * *

要求每个直线上的数字之和必须相等。共有15条直线哦!

再给点线索吧!我们预先填好了2个数字,第一行的头两个数字是:15 13,参见下图


黄色一行为所求。

请你填写出中间一行的5个数字。数字间用空格分开。

这是一行用空格分开的整数,请通过浏览器提交答案,不要填写任何多余的内容(比如说明性的文字等)


解析:这是一道典型的dfs(深度优先搜索)算法题目,可是细想一下,还有问题!题目要求15条直线上每条直线数字之和相等,这个要怎样判断?这19个位置又要怎样存储?二维数组?可是每行长度不一致!

一开始确实有点摸不着头脑,小弟不才,下面我说一下我的方法,未必是最优解法,但是能解出来,算是抛砖引玉,哪位大牛如果有更优解法,欢迎交流大笑


先说下我的大致思想,然后再上代码:

1.首先这19个数存在一个5*5的二维数组中,尽管除了第3行其他行后面都会空出一或两个位置,但是这并不影响最终结果

2.使用常规dfs算法,依次递归求解,递归过程中注意在合适的位置加以判断(判断每行的累加和是否相等),判断结果可以用来“剪枝”(具体解释看代码)

3.通过dfs算法,依次填入数字到数组中,过程中一旦满足某一行数字已经填满,那就直接判断当前行的累加和是否和其他行一样,若满足则继续递归寻找下一个,不满足就没必要继续递归,注意一定要及时判断,而不是留到最后再判断,这样便于降低时间复杂度


下面是代码,代码注释很多,请仔细看注释:

package _2014年国赛B组;public class 第二题 {private static boolean vis[] = new boolean[19];private static int sum = 0;public static void main(String[] args) {int[][] num = new int[5][5];num[0][0] = 15;num[0][1] = 13;vis[14] = true;vis[12] = true;//这里表示第1行第3列开始dfsdfs(num,0,2);}/** * 这个是递归实现dfs的方法 * @param num 存储的数组 * @param x 当前正在查找的行 * @param y 当前正在查找的列 */private static void dfs(int[][] num, int x, int y) {if(x==5&&y==0){//这里打印最终结果!syso(num);return;}//依次从1循环到19for(int i = 1;i<20;i++){//如果当前数字i已经被使用,直接continue,加快循环if(vis[i-1]){//这里vis下标之所以i-1,是因为i从1开始,所以vis数组下标从0到18分别依次对应i的值为1到19!//这是第一次“剪枝”但是这种情况下算法时间复杂度依然很高,必须再次剪枝(在下方)continue;}else{//当前数字i没有被占有,那就把数字给num数组,并且让对应的vis数组为true//表示数字i被占有,防止其他位置再次使用该数字vis[i-1] =true;num[x][y] = i;//这里测试当前第x行第y列数组放数字i时,数组是否满足题目条件test(num,x,y);//测试完表示当前数字i已经使用完毕,把vis数组归位vis[i-1] = false;}}}private static void test(int[][] num, int x, int y) {//这里需要分情况讨论,首先每行的列数不一样,但是明显有规律//规律就是第x行最多有4-|x-2|列(数学规律,很容易观察出)//所以需要判断当前y值与4-|x-2|的大小关系,如果y<4-|x-2|//说明当前列还不是这一行的最后一列,那下次递归就是dfs(num,x,y+1);//num表示当前数组,x是行,y+1是列if(y<4-Math.abs(x-2)){//这里当y<4-|x-2|时,需要分情况看,分别是x =2,3,4时要特殊判断if(x==2){//x=2,y=0时,判断最左上那条直线是否满足累加和与其他直线相等if(y==0){//如果相等那就递归下一列if(num[0][0]+num[1][0]+num[2][0]==sum){dfs(num,x,y+1);}//这里没有写else,因为当不满足上面if条件时已经没必要继续递归下一列了//所以直接退出test即可,这里可以加个return,更好理解(其实不加也是直接结束了test,下面的ifelse都是没有机会执行的)//这是第二次“剪枝”,加快循环进行}else{//这里else是跟着if(y==0)的,不要搞错!dfs(num,x,y+1);}}else if(x==3){//x=3,y=0同上,不再解释if(y==0){if(num[0][1]+num[1][1]+num[2][1]+num[3][0]==sum){//这里if满足条件,继续递归,不满足就相当于直接return,这是第3次“剪枝”dfs(num,x,y+1);}}else{//这里else也是跟着if(y==0)的,不要搞错!dfs(num,x,y+1);}}else if(x==4){//x=4,y=0同上,不再解释if(y==0){//这里if满足条件,继续递归,不满足就相当于直接return,这是第4次“剪枝”if(num[2][0]+num[3][0]+num[4][0]==sum&&num[0][2]+num[1][2]+num[2][2]+num[3][1]+num[4][0]==sum){dfs(num,x,y+1);}}else if(y==1){//这里if满足条件,继续递归,不满足就相当于直接return,这是第5次“剪枝”if(num[1][0]+num[2][1]+num[3][1]+num[4][1]==sum&&num[1][3]+num[2][3]+num[3][2]+num[4][1]==sum){dfs(num,x,y+1);}}else{dfs(num,x,y+1);}}else{//这里else表示当x=0,1时直接递归dfs(num,x,y+1);}}else{//else表示当前行的列数已经足够,要换行了//如果x=0,表示当前第1行已经填满,可以计算出第一行的累加和,便于后续和这个累加和比较if(x==0){sum = num[0][0]+num[0][1]+num[0][2];dfs(num,x+1,0);}else{//else表示当x!=0时,即当前不是第一行填满时要计算当前行的累加和//这里计算第x行累加和,存入temp中(这里直接把5列都加上了,实际也可以加个判断,只加前4-|x-2|列)int temp = num[x][0]+num[x][1]+num[x][2]+num[x][3]+num[x][4];//如果相等,进一步判断,否则相当于return(尽管我没明写return,但逻辑上是一样的)if(sum==temp){if(x==2){//x=2表示当前第3行已经填满,需要判断六边形右上角直线是否满足要求if(num[0][2]+num[1][3]+num[2][4]==sum){//这里if满足条件,继续递归,不满足就相当于直接return,这是第6次“剪枝”dfs(num,x+1,0);}}else if(x==3){//x=3同理,判断右上第二条直线if(num[0][1]+num[1][2]+num[2][3]+num[3][3]==sum){//这里if满足条件,继续递归,不满足就相当于直接return,这是第7次“剪枝”dfs(num,x+1,0);}}else if(x==4){//x=4同理,这里需要判断两条直线if(num[2][4]+num[3][3]+num[4][2]==sum&&num[0][0]+num[1][1]+num[2][2]+num[3][2]+num[4][2]==sum){//这里if满足条件,继续递归,不满足就相当于直接return,这是第8次“剪枝”dfs(num,x+1,0);}}else{//这个else表示x=1时直接递归下一层dfs(num,x+1,0);}}}}}/** * 这个方法就是循环输出最终结果 * @param num 待输出的数组 */private static void syso(int[][] num) {StringBuilder sb = new StringBuilder(); for(int i = 0;i<5;i++){for(int j = 0;j<5-Math.abs(i-2);j++){//这里题上没有严格要求行末不能有空格,所以就没有判断直接输出了sb.append(num[i][j]).append(" ");}sb.append("\n");}System.out.println(sb.toString());}}
输出结果如下:15 13 10 14 8 4 12 9 6 5 2 16 11 1 7 19 18 17 3 

上面就是全部代码,该加的注释,我都加的非常详细了最后总结下,这道题就是在原来dfs的基础上多加几个判断罢了,添加判断时一定要细心,还要记得及时剪枝,加快循环,可以快速得出结果

实测我的机器是1600ms左右出结果,实际上这道题目,一开始我并没有剪枝,导致点了运行程序就去吃饭去了,半个小时后回来居然还没有运行完再见所以剪枝操作一定要加上,毕竟这19个数字的全排列时间复杂度是O(n!),所以越早判断,越早剪枝,越容易加快速度,降低时间复杂度,得出结果!




1 0
原创粉丝点击