[编程之美] PSet1.15 构造数独

本文参考http://blog.csdn.net/hustspy1990/article/details/7464698

问题:

构造一个9*9的方格矩阵,玩家要在每个方格中,分别填上1至9的任意一个数字,
让整个棋盘每一列、每一行以及每一个3*3的小矩阵中的数字都不重复。

 方法一:回溯法 

第一种方法使用回溯法,原理是构建一棵搜索树(深度为9*9),然后对每一个格子用不同的数字进行尝试,如果不与数独构造法则冲突则继续搜索,否则返回到上一个节点换个数继续搜索。这个方法的缺点很明显,在搜索过程中,对于每一个数都要考察其是否满足数独规则,即是否与所在小矩阵中的数字重复和是否存在行列重复。此外如果运气不好的话搜索过程可能会很漫长。好处是生成的数独足够“随机”,除了数独规则外没有什么规律可循,所以如果游戏的留白比较多的话将是一个很考验智商的题目。同时我们似乎可以用这种方法来得到所有合法的数独。

本实现中在搜索过程中使用自加作为选择下一个候选数据的方法。 

#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;

//下面的代码是构造数独的代码
//解法一:回溯法--在当前位置选出备选数字,然后看下一次迭代是否能找到数字满足数独规则,假设在下一次迭代数字累加到
//10以后都未能找到满足条件的数字,则将这一次迭代结果清空并返回上一次迭代的结果+1

//行匹配、列匹配、九宫格匹配
bool isSuDoMatch(int **SuDoKu , int i , int j)
{   
	int temp = SuDoKu[i][j];
	for(int x=0 ; x<9 ; x++)
		if(x!=i && SuDoKu[x][j]==temp)
			return false;

	for(int y=0 ; y<9 ; y++)
		if(y!=j && SuDoKu[i][y]==temp)
			return false;

	int JGG_m = i/3;//(i,j)所属九宫格起始坐标
	int JGG_n = j/3;

	for(int m=JGG_m*3; m<JGG_m*3+3 ; m++)
		for(int n=JGG_n*3; n<JGG_n*3+3 ; n++)
			if(m!=i && n!=j && SuDoKu[m][n]==temp)
				return false;

	return true;
}
void printSuDoKu(int **SuDoKu)
{
	printf("创建的数独如下:\n");
	for(int i=0 ; i<9 ; i++)
	{
		for(int j=0 ; j<9 ; j++){
			printf("%4d" , SuDoKu[i][j]);
		}
		printf("\n");
	}

}

void createSuDoKu(int **SuDoKu)
{
	for(int i=0 ; i<9 ; i++)
		for(int j=0 ; j<9 ; j++)
			SuDoKu[i][j]=0;

	//------添加这部分代码使数独更有随机性,不加这部分也可以
	srand(time(0));//设定随机数种子为当前时间,使每次产生随机数不全相同
	int k=0;
	for(int i=0 ; i<9 ; i++){
		k = rand()%81;
		SuDoKu[k/9][k%9] = i+1;
	}
	//-------------------
	int i=0;
	int j=0;
	int cordinate=0;
	while(true)
	{
		i = cordinate/9;
		j = cordinate%9; 
		while(true){
			SuDoKu[i][j]++;
			if(SuDoKu[i][j] == 10){//回溯
				SuDoKu[i][j] = 0; 
				cordinate--;
				break;
			}
			else if(isSuDoMatch(SuDoKu , i , j)){//这个数字有效
				cordinate++;
				break;
			}
		}
		if(cordinate == 81){//整个9*9矩阵全部填充完毕
			printSuDoKu(SuDoKu);
			break;
		}
	}
}


int main()
{
	int **SuDoKu = new int* [9];
	for(int i=0 ; i<9 ; i++){
		SuDoKu[i] = new int [9];
	}
// 	int SuDoKu[9][9]={0};
 	createSuDoKu(SuDoKu);
	return 0;
}

方法二:置换矩阵法

方法一的解法虽然经典,但并不是唯一的正解。

 假设已经有一个 3X3 的矩阵是排列好的, 具体数字先用字母代替. 将小矩阵放到数独正中间;

    通过置换行, 填充数独中间三行;

    通过置换列, 填充数独中间三列;

    还剩下 4 个小方块, 由相邻的矩阵变换可得;

下面提供的方案我暂时没弄明白这个转移矩阵从何而来,留待日后在看哈···

const int transform[3][3] = {{1,2,0}, {0,1,2}, {2,0,1}};//都行,暂时没弄明白这东西怎么弄出来的
//const int transform[3][3] = {{0,1,2},{2,0,1},{1,2,0}};

void GenerateGrid(int* grid);
void Show(int (*grid)[3]);

int main(int argc, char *argv[])
{
	int grid[3][3];
	GenerateGrid(grid[0]);
	Show(grid);

	return 0;
}

//随机生成3*3的九宫格
void GenerateGrid(int* grid)
{
	int index = 0;
	int sum = 36; //0~8之和

	memset(grid, 0, sizeof(int) * 9);
	srand((unsigned)time(0));

	for(int i = 0; i < 8; ++i)
	{
		index = rand() % 9;//0-8
		while(grid[index] != 0)
		{
			index = rand() % 9;
		}
		grid[index] = i + 1;//将1-8数字随机的分配在不同的网格中
		sum -= index;//定位最后一个未填入数字的下标
	}

	grid[sum] = 9;
}


//输出已完成的数独
void Show(int (*grid)[3])
{
	for(int i = 0; i < 9; ++i)
	{
		for(int j = 0; j < 9; ++j)
		{
			//printf("%2d",grid[transform[j/3][i%3]][transform[i/3][j%3]]);//二者都行,没明白这东西怎么弄出来的
			printf("%2d",grid[transform[i/3][j%3]][transform[j/3][i%3]]);
		}
		puts(""); //换行
	}
}
    原文作者:天剑客
    原文地址: https://blog.csdn.net/spaceyqy/article/details/38174725
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞