去年参加Intel的线程挑战赛,一直想写个博客,看见Intel在搞多核编程征文,就发上来了。如果您觉得文章对您有用,请投我一票,谢谢!投票地址:http://intel.csdn.net/multicoreblog/show.aspx
数独(Sudoku)是一种逻辑谜题游戏,解答方法是将数字放到栅格里,但同行同列或者同一个子块里不能有相同的数字。通常栅格一般取 9x9。这样的话,每一行每一列以及这 9 个 3x3 的不重叠子块的每一个,都会包括整数 1-9 的一个具体实例。除了 9x9 的栅格外,也有可能会用 16x16 或者 6x6 的,还有一些变化形式并不使用方形的子块。
问题:写一个分析程序判断 6x6 的数独(Sudoku)谜题是否有唯一解。正确的解应该将数字 1-6 不重复的放到每一行每一列。同样的,不相重叠的 2x3 子块(2行,3列)必须包含这六个整数的一个唯一的实例。例如:
2 3 1 | 4 5 6
6 5 4 | 3 2 1
-------------
1 4 3 | 5 6 2
5 6 2 | 1 3 4
-------------
3 1 6 | 2 4 5
4 2 5 | 6 1 3
输入文件描述:当程序开始执行时,将输入文件的名字送入,通常是以命令行参数的形式。输入文件会包含一定数量的行,每行有 36 个非空子符。每行代表了在一个可能的 6×6 数独(Sudoku)谜题的初始状态下,全部字符的逐行排列。迷题初始状态中的空格将由星号字符代替。文件的结尾表示输入的结束。
输出:输出要标准化。应该指出每一个输入谜题是有唯一解,无解还是有多解。
一次输入 3 个谜题的输入示例:
*314*******1**356**621**3*******561*
***4****41*2*4321**6534*5*16****6***
5******5******5******5******5******5
(第一行对应了上面已解决的那个谜题在初始状态下的字符排列)
输出举例:
谜题 # 1 有唯一解
谜题 # 2 无解
谜题 # 3 有多解
计时:以时钟时间(Wall-clock time)为准(包括输入输出时间,即I/O时间)
算法分析:
数独由m x n 个 m x n 的子块,形成m x n x m x n = 36的栅格,(本题的m为2,n为3),根据规则单元格中可填写的数字为 1到m x n,同时每个单元格Cell[i,j]必须遵守以下三个规则:
1. Cell[i,j]值在第i行是唯一的。
2. Cell[i,j]值在第j行是唯一的。
3. Cell[i,j]值在它所属的子块中是唯一的。
当每个Cell[i,j]都存在满足以上三个规则的值,我们便认为该数独有解。
换而言之如果两个单元格满足以下条件则这两个单元格存在关联关系,不能填入相同的值。
1. 单元格的横坐标相同。
2. 单元格的纵坐标相同。
3. 单元格所在子块相同。
与Cell[i,j]存在互斥关系的单元格一共有m x n x 3 - m – n - 1 个。
栅格的每个单元格用一个int值表示,初始填为(1 << XGRID_BSIZE) – 1【低XGRID_BSIZ个bit的值为1】,表示该单元格可以填的值为[1, XGRID_BSIZE]。 当一个单元格填入值t,就把它的关联单元格第t个bit置为0,表示该这些关联单元格不能填入t。再将这个填入值t的单元格高bit置为1【或上XFILLEDMASK】。例如:
单元格的值为 0x0003 表示该单元格填入1和2是合法的
单元格的值为 0x0005 表示该单元格填入1和3是合法的
单元格的值为 0x8008 表示该单元格一填入4。
加载迷题的时候,直接对有初始值的单元格进行填值,填值前对合法性进行检查,如果不合法直接返回无解。
迷题加载完成后,对所有未填值的单元格进行检测,找到一个可能性最少的【为1的bit位最少的单元格】单元格Cell[i,j]进行尝试填入合法的值【这样做可以降低堆栈的深度,减少分支的数量。】,如果在处理该Cell[i,j]的关联单元格时发现该关联单元格的值变为为0,表示该关联单元格没有了合法值,对Cell[i,j]尝试填入其他合法值。当所有单元格都填值完成,表示找到了该迷题的一组解。使用简单的递归调用即可完成求解。为追求效率,可以将递归展开。
首先定义一些宏、类型及结构。
// 填值失败
#define XFILL_INVALID 0x7FFFFFFF
// 已填值标记
#define XFILLEDMASK 0x8000
// 每个小块的宽度
#define XGRID_BW 3
// 每个小块的高度
#define XGRID_BH 2
// 每个小块的大小
#define XGRID_BSIZE (XGRID_BW * XGRID_BH)
// 整个网格的大小
#define XGRID_AREA (XGRID_BSIZE * XGRID_BSIZE)
// 整个网格的大小SSE对齐
#define XGRID_AREASSE ((XGRID_AREA + 7) >> 3 << 3)
// 每个单元格关联的单元格数量
#define XGRID_MUTUALITY (XGRID_BSIZE * 3 - XGRID_BW - XGRID_BH - 1)
// 每个单元格关联的单元格数量SSE对齐
#define XGRID_MUTUALITYSSE ((XGRID_MUTUALITY + 7) >> 3 << 3)
// 修改关联单元格的条件
#define XGRID_CHANGE_MUTUALITY_SIZE (XGRID_AREA * 30 / 100)
// 堆栈的最大深度
#define XGRID_STACK_SIZE (XGRID_AREA - XGRID_CHANGE_MUTUALITY_SIZE)
// 当数独的子块大于等于9的时候可以动态清除关联以提高效率
#if (XGRID_BSIZE >= 9)
#define XGRID_MUTUALITYSSE_CLEAR
#endif
// 用于保存单元格的值
typedef int XCells[XGRID_AREASSE];
// 用于保存网格的数据结构
typedef struct tagXGrid
{
__declspec(align(16)) XCells xCells; // 单元格的值
#ifdef XGRID_MUTUALITYSSE_CLEAR
// 单元格的关联单元格索引数组
__declspec(align(16)) uint8 pMutuality[XGRID_AREA][XGRID_MUTUALITYSSE];
// 单元格的关联单元格数量
__declspec(align(16)) uint8 nMutuality[XGRID_AREA];
#endif
// 已经填充的单元格位置
__declspec(align(16)) int pFilledPos[XGRID_AREA];