唯一解的数独题目生成器——理解回溯法

在生成数独题目时,流程是:生成一个完整的数独——随机挖空——解题——如果只有一个解,则生成该题目,如果有多个解,则重新挖空。

数独题目的生成涉及的一个算法是回溯法,这里面的完整数独的生成、求解都用到了回溯法。

1. 生成完整数独

在生成部分,先生成所有宫的1,再生成所有宫的2,以此类推。

主要有三个值:

boolean[][][] placeable = new boolean[9][9][9];// 第几个数字,第几宫,第几号位置

int[][] stepPos = new int[9][9];// 第几个数字,第几宫,值是位置

int[][] result=new int[9][9];

placeable 用来存储每个数字在每个宫的每个位置是否可以放置,初始时,只要该位置没有被占就可以放置,在生成数独的过程中,尝试在这个位置放置某数,如果与行列宫有冲突,则需更新placeable 。

stepPos 用来存储某个宫中已经放置的数字的位置。

result既是存储生成的数独。

 

生成阶段的回溯是通过一个双重循环实现的:


	private static int[][] tryGenerateFinalAnswer() {
		int[][] result=new int[9][9];
		for (int number = 0; number < 9; number++) {
			//注意这里没有gong++
			for (int gong = 0; gong < 9;) {
				ArrayList<Integer> PlaceableList = getPlaceableList(number, gong);
				int length = PlaceableList.size();
				//回溯算法的重点就是这里,如果当前一步走不下去,就返回上一步,重走
				if (length <= 0) {					
					resetPlaceable(result,number, gong);
					gong--;
					if (gong < 0) {
						number--;
						gong = 8;
					}
					removeNum(result, number, gong);
					
				}else {
					int pos = PlaceableList.get(random.nextInt(length));
					if (isCollide(result,number, gong, pos)) {
						placeable[number][gong][pos] = false;
					}else {
						addNum(result, number, gong, pos);
						gong++;
					}
				}
			}
		}
		return result;
	}

其实一开始写数独生成的时候,想的是先随机生成第一个宫的所有数字,第二个宫在不与第一个宫冲突的情况下生成所有数字,以此类推,就是以宫为主顺序生成数独。这样的做法理论上可行,但实际上需要非常大量的运算,而且极易容易冲突。因为后面一旦走不下去,往往要返回一个宫的所有数字,但是以数字为主顺序,走不下去就只需要返回一个宫的一个数字。

2. 随机挖空

随机挖空的部分比较简单,但是要注意使用另一个二维数组来存储生成的题目,不要在完整的数组上动,因为后面解题部分要对比完整的数组,看看解题是否正确。

3. 解题

解题部分时参考了另一位大佬的代码,之前看的时候距今已经过了一年有余,所以找不到他的博客了。

解题部分的思想非常的巧妙,首先将数独题目中1-9这9个数,转化成二进制中1的位置。000000100表示3,如001000000表示7,而题目中空缺的位置,则以111111111表示,这表示这个位置的候选数字是1~9。

然后根据已确定的数字分析更新空格的候选数字,比如候选值有2、3、6,则该空的二进制数就是000100110。当二进制中只有一个1,则表示该值可以确定,使用Integer.bitCount(data[m][i]) 方法判断二进制中有几个1。

这里的分析主要就是排除行列宫中已有的数字。

一直循环分析所有空格,直到不能从已确定的数字中分析出新的候选信息,就从一个空格的候选值中假设一个值填进格子中,再以此推测剩余的空格,直到所有格子都被填满,或者是填补下去,那么就返回之前的假设,填其他值,以此类推。这其实和我们正常解数独的思路是一样的。

如果题目已经求得两个解,则返回,重新生成题目。因为这个题目是从一个完整的数独挖空而来,所以不可能没有解。

解题的回溯部分主要通过递归实现:

	 private static void solve(int[][] data) {
	    	if(resultNum>1) {
	    		return;
	    	}
	        analyse(data);
	        int result = check(data);
	        if (result == 1) {
	            int[] position = findLessCandidatesPos(data);
	            int pv = data[position[0]][position[1]];
	            int pvcount = Integer.bitCount(pv);
	            for (int i = 0; i < pvcount; i++) {
	                int testV = 1 << ((int) (Math.log(Integer.highestOneBit(pv)) / Math.log(2)));
	                pv ^= testV;
	                int[][] copy = copyArray(data);
	                copy[position[0]][position[1]] = testV;
	                //这里不理解为什么要返回
	                if(i>1) {
	                	return;
	                }
	                solve(copy);
	            }	        
	        }else if (result == 0) {
	        	resultNum++;
	            System.out.println("------------------------------------第"+(resultNum)+"个答案---------------------"
	                    + (System.nanoTime() - startTime) / 1000000.0 + "ms---");
	            answer=data;
	    		binaryToInt(answer);
	            printByRow(answer);
	        }
	    }

解题中值得借鉴的就是将数字1~9表示成二进制中1的位置,这样可以很好的表示候选值。除了数独中的数字与候选值表示成二进制中1的位置,

在找候选值的方法中,也是用三个二进制数分别表示当前值所在的行、列、宫已经存在的数字,来计算候选值的。

在判断是否冲突的方法,也是用三个二进制数分别表示当前值所在的行、列、宫已经存在的数字,来判断是否冲突的。

唯一解的数独题目生成器代码://download.csdn.net/download/Michaelia_hu/12013857

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生成数独题目的方法可以通过编程语言Java来实现。为了生成一个合法的数独题目,需要确保以下条件: 1. 没有重复的数字出现在每一行、每一列和每一个3x3的九宫格中; 2. 开始时,数独题目中预填的数字(已知的数字)应该保证唯一性; 3. 生成的数独题目应该有唯一。 首先,可以创建一个9x9的二维数组,用于表示数独的九宫格。然后,通过递归的方式填充九宫格。 具体实现可以按照以下步骤进行: 1. 创建一个递归函数 `solveSudoku()`,用于填充九宫格中的数字; 2. 在递归函数中,首先判断数独题目是否已经填充完毕(即所有的空格都已经填入数字),如果是则返回 true; 3. 遍历九宫格中的空格,找到第一个没有数字的格子; 4. 对该格子尝试填入数字 1-9,判断是否合法(即是否满足数独的要求); 5. 如果填入的数字合法,则递归调用 `solveSudoku()` 函数继续填充下一个格子; 6. 如果填入的数字不合法,则尝试下一个数字,直到找到合法的数字或者遍历完所有数字; 7. 如果所有数字都尝试过,都不合法,则返回 false,同时回溯到上一个格子; 8. 当递归函数返回 true 时,表示已经成功填充了所有的格子,并且生成了一个合法的数独题目。 通过以上的步骤,就可以使用Java来生成一个合法的数独题目。具体的代码可以根据上述思路进行编写。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值