Swing数独游戏(二):终盘生成之随机法

在上一篇博文介绍了用矩阵转换法来产生数独终盘。关于矩阵转换法产生终盘矩阵请参考如下的文章。

[b]Swing数独游戏(一):终盘生成之矩阵转换法 [/b]==> [url]http://mouselearnjava.iteye.com/blog/1941483[/url]

本篇博文将对数独游戏随机产生数独终盘展开说明。

[size=medium][color=blue][b]用什么样的随机方法?[/b][/color][/size]

读过一些文章,关于随机产生数据终盘比较流行的是将拉斯维加斯算法与回溯法相结合。比如下面的文章都有提及:

[url]http://zhangroup.aporc.org/images/files/Paper_3485.pdf[/url]
[url]http://wenku.baidu.com/view/f9e3f17101f69e31433294e1.html[/url]

[img]http://dl2.iteye.com/upload/attachment/0089/2557/e887ce09-1027-319f-b4d1-cf4a7947f2f8.jpg[/img]

看了如下PPT: Java算法[url]http://wenku.baidu.com/view/0a67634de518964bcf847c80.html[/url]

[img]http://dl2.iteye.com/upload/attachment/0089/2564/4105d102-57b2-3228-8aa9-dd18e581007e.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0089/2566/3e2b4b61-33d4-3787-8b86-da572118371d.jpg[/img]

从上面的两个PPT截图中可以看出,拉斯维加斯算法思想去解决N皇后时所花费的时间不少。
数独中的判断不比N皇后少,如果想要在1秒内或者更短的时间产生1000个数独终盘,个人觉得并不是很容易(当然了,这个希望以后去证明一下是正确的 :))。

所以,我想选择另外的途径去完成。

[size=medium][color=blue][b]我的随机方法思想[/b][/color][/size]

在给出我的随机方法思想之前,我们先来看看数独终盘的数量。

[b]终盘数量[/b]

终盘数量数独中的数字排列千变万化,那么究竟有多少种终盘的数字组合呢?
6,670,903,752,021,072,936,960(约有[b]6.67×10的21次方[/b])种组合,2005年由Bertram Felgenhauer和Frazer Jarvis计算出该数字,并将计算方法发布在他们网站上,[b]如果将等价终盘(如旋转、翻转、行行对换,数字对换等变形)不计算,则有5,472,730,538个组合[/b]。数独终盘的组合数量都如此惊人,那么数独题目数量就更加不计其数了,因为每个数独终盘又可以制作出无数道合格的数独题目。
-- 参考自[url]http://baike.baidu.com/link?url=ePXUCvpBaRKBkEA3pVfOkg3m-NBozO6a4GDS0N3E5_gK1nnJCDzd5O-YL1w7c5S3[/url]


[color=blue][b]按照这个数量,如果我们将一个[1,2,3,4,5,6,7,8,9]的数组随机化,然后将其作为一行数据添加到一个二维数组中去,该行能满足数独终盘规则的概率是很大的。[/b][/color]

[b]基于这个假设(假设的有效性会在文章后面验证),我的随机算法思想如下:[/b]

[list]
[*][b]1. 写一个方法用于获取一个由1到9九个数随机排列的一维数组。
[*]2. 循环行(下标从0到8),将这个随机产生的一维数组作为当前行的内容,如果是第一行(行标为0),那么直接作为该行的内容。如果是其它行,则验证数据是否都符合条件。
[*] 如果符合条件,则再产生一个由1到9九个数随机排列的一维数组作为下一行的内容并验证数据是否可用。
[*] 如果不符合条件,则将该行数据设置为0,调整row和col,产生一个由1到9九个数随机排列的一维数组,重新对该行验证。
[*]3. 程序中为了防止产生一维随机数组的方法调用很多次而没有产生结果,设置一个最多调用该方法次数的阈值,当达到这个阈值还没有产生结果,重新从 row =0 col =0 开始。[/b]
[/list]

实现的程序如下:

package my.utils.algorithm.sudoku;

import java.util.Random;

public class SudokuPuzzleGenerator {

private Random random = new Random();

/**运行此程序300次,最大值是217,最小值11,平均约等于50
* 阈值设置为220, 能满足大部分程序,二维矩阵不会置为0,重新再产生值。
*/
private static final int MAX_CALL_RANDOM_ARRAY_TIMES = 220;

/**记录当前buildRandomArray()方法调用的次数*/
private int currentTimes = 0;

public int[][] generatePuzzleMatrix() {

int[][] randomMatrix = new int[9][9];

for (int row = 0; row < 9; row++) {
if (row == 0) {
currentTimes = 0;
randomMatrix[row] = buildRandomArray();

} else {
int[] tempRandomArray = buildRandomArray();

for (int col = 0; col < 9; col++) {
if (currentTimes < MAX_CALL_RANDOM_ARRAY_TIMES) {
if (!isCandidateNmbFound(randomMatrix, tempRandomArray,
row, col)) {

/*
* 将该行的数据置为0,并重新为其准备一维随机数数组
*/
resetValuesInRowToZero(randomMatrix,row);
row -= 1;
col = 8;
tempRandomArray = buildRandomArray();
}
} else {
/*
* 将二维矩阵中的数值置为0,
* row赋值为-1 col赋值为8, 下一个执行的就是row =0 col=0,
*
* 重头开始
*/
row = -1;
col = 8;
resetValuesToZeros(randomMatrix);
currentTimes = 0;
}
}
}
}
return randomMatrix;
}

private void resetValuesInRowToZero(int[][] matrix, int row)
{
for (int j = 0; j < 9; j++) {
matrix[row][j] = 0;
}

}

private void resetValuesToZeros(int[][] matrix) {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
matrix[row][col] = 0;
}
}
}

private boolean isCandidateNmbFound(int[][] randomMatrix,
int[] randomArray, int row, int col) {
for (int i = 0; i < randomArray.length; i++) {
/**
* 试着给randomMatrix[row][col] 赋值,并判断是否合理
*/

randomMatrix[row][col] = randomArray[i];
if (noConflict(randomMatrix, row, col)) {
return true;
}
}
return false;
}

private boolean noConflict(int[][] candidateMatrix, int row, int col) {
return noConflictInRow(candidateMatrix, row, col)
&& noConflictInColumn(candidateMatrix, row, col)
&& noConflictInBlock(candidateMatrix, row, col);
}

private boolean noConflictInRow(int[][] candidateMatrix, int row, int col) {
/**
* 因为产生随机数矩阵是按照先行后列,从左到右产生的 ,该行当前列后面的所有列的值都还是0, 所以在行比较的时候,
* 只要判断该行当前列与之前的列有无相同的数字即可。
*
*/
int currentValue = candidateMatrix[row][col];

for (int colNum = 0; colNum < col; colNum++) {
if (currentValue == candidateMatrix[row][colNum]) {
return false;
}
}

return true;
}

private boolean noConflictInColumn(int[][] candidateMatrix, int row, int col) {

/**
* 与noConflictInRow(...)方法类似:
*
*
* 因为产生随机数矩阵是按照先行后列,从左到右产生的,该列当前行后面的所有行的值都还是0,
*
* 所以在列比较的时候, 只要判断该列当前行与之前的行有无相同的数字即可。
*
*/

int currentValue = candidateMatrix[row][col];

for (int rowNum = 0; rowNum < row; rowNum++) {
if (currentValue == candidateMatrix[rowNum][col]) {
return false;
}
}

return true;
}

private boolean noConflictInBlock(int[][] candidateMatrix, int row, int col) {

/**
* 为了比较3 x 3 块里面的数是否合理, 需要确定是哪一个Block,我们先要求出3 x 3的起始点。 比如: Block 1
* 的起始点是[0][0] Block 2 的起始点是[3]][0]
*
* ... Block 9 的起始点是[6][6]
*/

int baseRow = row / 3 * 3;
int baseCol = col / 3 * 3;

for (int rowNum = 0; rowNum < 8; rowNum++) {
if (candidateMatrix[baseRow + rowNum / 3][baseCol + rowNum % 3] == 0) {
continue;
}
for (int colNum = rowNum + 1; colNum < 9; colNum++) {
if (candidateMatrix[baseRow + rowNum / 3][baseCol + rowNum % 3] == candidateMatrix[baseRow
+ colNum / 3][baseCol + colNum % 3]) {
return false;
}
}
}
return true;

}

/**
* 返回一个有1到9九个数随机排列的一维数组,
*/
private int[] buildRandomArray() {
currentTimes++;
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int randomInt = 0;
/*
* 随机产生一个1到8的随机数,使得该下标的数值与下标为0的数值交换,
*
* 处理20次,能够获取一个有1到9九个数随机排列的一维数组,
*/
for (int i = 0; i < 20; i++) {
randomInt = random.nextInt(8) + 1;
int temp = array[0];
array[0] = array[randomInt];
array[randomInt] = temp;
}

return array;
}

/**
* @return the currentTimes
*/
public int getCurrentTimes() {
return currentTimes;
}

/**
* @param currentTimes the currentTimes to set
*/
public void setCurrentTimes(int currentTimes) {
this.currentTimes = currentTimes;
}

}

[b]

[size=medium][color=blue][b]假设有效性验证及阈值设定[/b][/color][/size]

针对上述的代码,我跑10组,每组30个实例,看看这300个例子中,产生数独终盘所需要调用随机产生由1到9的一维数组的次数各是多少, 结果如下:

[img]http://dl2.iteye.com/upload/attachment/0089/2535/0c1df9c0-bad5-3fa5-8393-d4298f635528.jpg[/img]

从上面的结果图中可以看出:[[color=blue]b]300个实例中,调用次数最小的为11,接近理想最小调用次数9.

最大值为217次,平均约50次。而且大部分实例调用的次数在100以内。

从这些数据[/color]分析中,上述假设基本上是合适的,阈值最后选择了接近实验最大值217的220.[/b]

[size=medium][color=blue][b]效果和结论[/b][/color][/size]

选择产生不同的数目的数独终盘,看看执行的时间。
[img]http://dl2.iteye.com/upload/attachment/0089/2553/1e7bb208-1dd9-3469-97e3-f370f9296e14.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0089/2555/772bbfe7-f06e-3f93-91cb-ecb4616f65d6.jpg[/img]


[b]结论:[/b]

[b]1. 在打印二维数组到控制台显示的情况下,产生10万个随机数独终盘的时间差不多1分钟左右。
2. 在打印二维数组到控制台显示的情况下,产生1000个随机数独终盘的时间差不到1秒,这个正是我们之前所期望的。
3. 产生大数目的随机终盘,将二维数组打印到控制台显示的时间占据总时间的60%左右。
如果不在控制台输出二维数组,花费的时间将少很多。 看上面的折线就可以知道。[/b]

[b]总的来说,效果还是比较不错的,比较适合一下需要产生很多的数独终盘的情况,比如:初始化1000个或者更多个数独终盘并写入文件,然后作为数独游戏的每关内容。[/b]

在下一篇博文中将介绍使用“挖洞”的思想去生成数独难题。

使用在Console打印出随机产生二维矩阵的的测试代码,及其产生50000个和100000个二维矩阵的结果截图如下:[/b]


public class Main {
public static void main(String[] args) {
SudokuPuzzleGenerator sudokuPuzzleGenerator = new SudokuPuzzleGenerator();
long start = System.currentTimeMillis();
int numOfSudokuMatrix = 10000;
for (int count = 1; count <= numOfSudokuMatrix; count++) {
System.out.println("第" + count + "个");
int[][] randomMatrix = sudokuPuzzleGenerator.generatePuzzleMatrix();
printMatrix(randomMatrix);
}

long end = System.currentTimeMillis();

System.out.println("产生"+numOfSudokuMatrix+"个数独矩阵花费时间: " + ((end - start) / 1000.0)
+ " 秒");


}

public static void printMatrix(int[][] randomMatrix) {
for (int rowNum = 0; rowNum < 9; rowNum++) {
for (int colNum = 0; colNum < 9; colNum++) {
System.out.print(randomMatrix[rowNum][colNum] + " ");
}
System.out.println();
}
}
}



[img]http://dl2.iteye.com/upload/attachment/0089/2537/6ab07468-e52e-305e-959b-5facb63f356d.jpg[/img]

[img]http://dl2.iteye.com/upload/attachment/0089/2539/83da46e8-2e96-3a04-b66c-0e4208ce3d24.jpg[/img]


在下一篇博文中将介绍使用“挖洞”的思想去生成数独难题。

转载请注明出处:[url]http://mouselearnjava.iteye.com/blog/1941693[/url]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值