新的一月开始了,好吧我坦白,其实我写这篇博客也算是滥竽充数吧,因为每个月只要您写四篇原创博文,就会获得少许C币以及点亮酷炫的”恒“勋章,所以大家还请踊跃创作啊

  百无聊赖之际,我从网上下载了一款数独的游戏,游戏规则很简单,一个正方形,里面共有9*9个方格,每个方格中必须有一个数字【数字只能是1到9的某一个数值】,而且还有3个限制条件:1)每一行的数字不能重复【即每一行中每个数字能且只能出现一次;2)每一列的数字也不能重复;3)每一个小九宫区域内的数字也不能重复【所谓小九宫区域,指的是大正方形被分成相等的9个小正方形,每个正方形都是3*3个方块】; 当时我感觉这种游戏挺有难度也挺好玩,所以试了试,但半途中一拍大腿:为什么不让电脑帮我解决问题呢?

  说干就干,其实写程序这种事,并不比做题本身来的轻松,经过N次的debug,最终貌似修成正果,其源代码如下:

 

 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
	int num[9][9], flag[81], backarr[81][2];
	int i, r, c, rend, cend, back, layer, row, col, count;
	clock_t start, end;
randstart:
	for (r = 0; r < 9;r++ )
	for (c = 0; c < 9; c++)
		num[r][c] = 0;
	for (i = 0; i < 81; i++) flag[i] = 0;
	for (r = 0; r < 81; r++) 
	for (c = 0; c < 2; c++)
		backarr[r][c] = 0;
	i = r = c = rend = cend = back = layer = row = col = count = 0;//以上这些都是用来把所有的数据与变量清零,可以不用管它们
	
	srand((unsigned)(time)(NULL));//下面开始随机生成局面
	i = rand() % 12 + 12; //我们要生成的局面开始的固定数值最少12个,最多24个,因为本人能力有限,写的算法可能招呼不来
	printf("您好,欢迎来到数独世界\n");

	while (count < i)
	{
		layer = rand() % 81;
		if (!flag[layer])
		{
			rend = rand() % 9 + 1;
			row = layer / 9; col = layer % 9;
			for (r = 0; r < 9; r++) if (num[r][col] == rend) goto randend;

			for (c = 0; c < 9; c++) if (num[row][c] == rend) goto randend;

			for (int kl = ((layer / 9) / 3) * 3; kl < ((layer / 9) / 3) * 3 + 3; kl++)
			for (int kh = ((layer % 9) / 3) * 3; kh < ((layer % 9) / 3) * 3 + 3; kh++)
			if (num[kl][kh] == rend) goto randend;

			flag[layer] = 1;
			num[row][col] = rend;
			count++;
		}
	randend:;
	}
	//到这里局面已生成
	
/*
	printf("首先请输入您遇到的局面,开始有几个固定数字?\n");
	scanf("%d", &r);
	printf("好了,数字已确定,下面请初始化局面,其中第一个数字代表数独棋盘的位置,范围是0到80;而第二个数字是填入位置中的数值,范围是1到9:\n");
	for (i = 0; i < r; i++)
	{
		scanf("%d%d", &row, &col);
		num[row / 9][row % 9] = col;
		flag[row] = 1;
	}
*/
	printf("这是我们随机初始化后的局面,有%d个固定数值,如下所示\n",i);
	for (r = 0; r < 9; r++)
	{
		for (c = 0; c < 9; c++)
		{
			if (num[r][c] != 0)
				printf("%d", num[r][c]);
			else printf("*");
		}
		printf("\n");
	}
	i = r = c = rend = cend = back = layer = row = col = count = 0; //变量清零,方便后来的算法思考
	printf("您可以按下回车键查看这个谜题的解答\n");
	system("pause");

	start = clock(); //记录下程序开始“思考”的时间
	while (layer < 81) //因为要测试81次,layer代表81层的每一层
	{
		if (layer < 0) 
		{
			printf("不好意思,这是一个无解的死局!请再给我一次机会...\n"); goto randstart;
	    }
		if (1 == flag[layer]) //这个分支用来测试是否计算到了固定数值,如果是,则跳过这一块
		{
			if (back == 1) { goto no_match; } //如果正处于回溯状态,就当成无满足条件的递归式回到上一层
			layer++; // 不然则当成前进式计算下一层
			continue;
		}
	
		row = layer / 9; col = layer % 9; //把这一层用来转换定位行和列的具体坐标

		for (i = backarr[layer][back] + 1; i < 10; i++) //注意那个backarr,它用来当作测试数值的下限值
		{
			for (c = 0; c < 9; c++)//检查横列
			if (c >= col)
			{
				if (flag[row * 9 + c] == 1 && num[row][c] == i)  goto end;//i不符合条件,刷新i
			} 
			else if (num[row][c] == i)  { goto end; }

			for (r = 0; r < 9; r++) //检查竖列
			if (r >= row)
			{
				if (flag[r * 9 + col] == 1 && num[r][col] == i)  goto end;
			}
			else if (num[r][col] == i)  goto end;

			rend = ((row / 3) * 3) + 3; cend = ((col / 3) * 3) + 3; //检查九宫格
			for (r = (row / 3) * 3; r < rend; r++)
			for (c = (col / 3) * 3; c < cend; c++)
			if ((r * 9 + c) >= layer)
			{
				if (flag[r * 9 + c] == 1 && num[r][c] == i)  goto end;
			}
			else if (num[r][c] == i)  goto end;


			goto match_go;

		end:continue;
		}
		goto no_match;

	match_go:
		num[row][col] = i; //i暂时符合限制条件,程序继续前进式计算
		backarr[layer][1] = i; 
		back = 0;
		layer++;
		continue;

	no_match:
		back = 1; //没有符合条件的数值,将状态职位回溯
		layer--;
	}
	end = clock(); //程序“思考”结束时间
	printf("一共用时%d毫秒,答案如下:\n", end - start);
	for (r = 0; r < 9; r++) //走到这里表示程序已经找到答案,下面就是给出答案
	{
		for (c = 0; c < 9; c++)
		{
			if (num[r][c] != 0)
				printf("%d", num[r][c]);
			else printf("*");
		}
		printf("\n");
	}
	
	system("pause");
}

 

 

  其运行结果如图:

数独规则算法 Java 数独规矩_数独

 

  在这里有必要多说两句,首先,程序本身为什么用迭代而不是递归。其实我在构思程序框架的时候,就隐约感觉到可以用迭代方式。而所谓数独游戏,说白了不过就是一个有关排列组合的数学题,既然是排列组合,就牵扯到暴力搜索,而一般暴力搜索除了递归方式外,迭代也可以胜任。考虑到函数调用的堆栈开销和数独本身恐怖的81层深度,所以我最后选择了迭代方式。【最后不得不说的是,算法的效率比我本身预期快的多,估计用递归也查不到哪去】

  其次,在随机生成初始化谜题时,如果运气不好,有可能会生成一个无解的死局,没关系,程序会发现并自动生成新的谜题。

  最后,细心的朋友可能会注意到【看那一段被注释掉的代码】——我写这个程序最初的目的是让它帮我解决某个特定的问题,而不是现在随机的制造问题。有关解决某个特定问题的版本,我们暂且称之为数独作弊器吧,其实我也做出来了,当初因为自己的能力有限,作弊器写好的代码无法运行,然后通过查阅资料和求助大牛,最终解决了这个问题,这是一个有关老版本scanf函数如何兼容新IDE的故事,其中内容,我会另开一片博文。

  不知道csdn能否上传小附件,反正我找了半天没找到,算了,如果有朋友对本程序感兴趣,可以问我要可执行文件,谢谢浏览!