完成总结报告项目名称:数独游戏设计与实现组员:王郑合 2014204081栾杰 2014204080文宽 2014204104 二〇二〇年三月二十四日1 问题描述1.1 问题说明数独游戏起源于瑞士,由十八世纪的瑞士数学家欧拉发明,是一种数字拼图游戏,其游戏规则是:①在9×9的大九宫格内,已给定若干数字,其他宫位留白,玩家需自己按照逻辑推敲出剩下的空格里是什么数字。
②必须满足的条件:每一行与每一列都有1到9的数字,每个小九宫格里也有1到9的数字,并且一个数字在每行、每列及每个小九宫格里只能出现一次,既不能重复也不能少。
③每个数独游戏都可根据给定的数字为线索,推算解答出来。
1.2 数独求解描述由于数独游戏的推广与普及,在当今世界上有着大量的数独爱好者,本项目的目的就是按照数独的游戏规则,通过对数据结构的分析和人工智能算法的研究,利用计算机程序来实现对已知数独游戏的快速求解。
1.3 数独出题描述数独游戏挑战者的水平各异,对数独题目的难度要求各不相同,所以本项目致力于设计一种算法,使其在尽可能短的时间内生成不同难度等级的数独题,以满足不同水平游戏者的需求。
同时,该算法还要考虑到三个方面要求:可变化的难度、解的唯一性和算法复杂度最小化。
2 功能分析2.1 数独求解数独虽然号称是数学问题, 但在求解时几乎用不上数学运算方法,事实上它更像是一种思维方式。
数独游戏开始后,要想在空格中填入正确的数字,先要根据数独游戏规则对1-9分别进行逻辑判断,然后选择正确的数字填入空格。
另外,由于某个格子填入数据时,有可能还要对原来已填入的数据进行修正,所以可以考虑使用递推和回溯搜索来求解数独问题。
2.2 数独出题出题时,要能保证算法生成的数独题具有可变化的难度和唯一解,该算法内部应该包含有对数独题的求解和评级功能。
本项目使用了一种基于“挖洞”思想的数独题生成算法,将该算法的设计工作分为评级、求解和生成三部分工作。
利用随机数出现的概率不同来确定不同的难度,通过避免重填一个被“挖去”的格子,或者回溯到一个曾经无法“挖去”的格子,来降低算法的复杂性。
2.3 题目保存当用户需要退出却仍没有完成数独题目的解答时,可以选择是否保存当前的求解进度。
如果需要,本系统会帮助用户将目前未完成的数独题目的解答进度保存起来,以便用户下次使用本系统时,可以继续解答上次未完成的题目。
2.4 题目读取用户可以在程序开始运行后,选则读取一道之前保存起来的题目进行解答,被读取的题目将会显示到程序界面上。
3 系统设计3.1 功能结构图本程序主要有数独求解和数独出题两个功能,数独求解包括题目检验、解题和输入输出,数独出题包括答案检验、难度选择、出题和输入输出。
3.2 业务流程图3.3 类图SudokuDlg类:程序的界面类。
Solve类:实现数独题目求解功能。
Make类:实现数独题目出题功能。
Pre类:对数据进行预处理。
3.4 界面设计3.5 算法设计3.5.1 数独求解算法解决该数独求解问题时的要考虑的主要方面有:①判断题目合法性,即验证给出数据本身是否符合游戏规则,行、列以及小九宫中从不重复地出现数字1-9;②采用递推算法,若可以填入数字则填入数字,并入栈以便回溯,否则回溯至前面填入数字处重新填数;③所有空白处都要填满数据;其中,最重要的就是如何通过递推算法填入正确的数字,或者通过回溯算法重新填入数字并得到最终解,这是本算法的核心内容。
回溯法是解决数独问题的有效方法,有着较快的解题速度。
然而一般的常用的回溯算法仍然有不足之处,比如会进行重复的运算。
所以在采用回溯法之前,还可以利用一点小技巧,以缩短回溯算法的运行时间,甚至可将运行时间缩短接近于0。
这个小技巧即在回溯算法的基础上,采用有限递推算法进行预处理。
算法活动图如下:3.5.1 数独出题算法要想设计一个算法用以生成各种难度等级的数独题,通过对游戏规则的分析,首先从三个方面定义难度等级,分别是已知格总数、已知格的分布和穷举搜索复杂度。
本算法采用“挖洞”思想,经过以下步骤生成数独题:①用随机算法生成一个数独题目;②用求解算法生成终盘;③根据难度挖去部分数字;整个生成数独题的算法分为两步执行:先利用拉斯维加斯随机算法随机生成一个填满数字且符合游戏规则的终盘;而后根据所需求的难度等级抹去一些数字,难度等级由随机数出现的概率来决定。
算法活动图如下:4 系统实现4.1 开发平台本项目基于Visual Studio 2010平台的MFC,采用C++语言进行开发与测试。
4.2 运行环境本项目可在各个版本的Win7系统或者Windows XP系统下运行。
4.3 数据结构数据结构是计算机存储、组织数据的方式。
通常情况下,精心选择的数据结构可以带来拥有更高的运行或存储效率的算法。
一般认为,一个数据结构是由数据元素依据某种逻辑联系组织起来的,对数据元素间逻辑关系的描述称为数据的逻辑结构;数据必须在计算机内存储, 数据的存储结构是数据结构的实现形式,是其在计算机内的表示。
数独从形式上看是一个方阵, 十分适合用数组来表示。
4.3.1 主要数组本项目中所用到的主要数组有:①int sudoku[82]该数组的用途是存储题目以及保存最终结果,所有的9×9个数字被依次存储在该数组中,空白处则初始化为0。
之所以把数组范围设计成82 而不是81,是为了程序的易读性,使得数组元素的最大下标可达81,下同。
②int fix[82]该数组的用途是若sudoku数组中某位置的数字不为空,则fix数组相应位置的元素值为1,否则为0,即fix数组是用来记录哪些位置的数字是已经确定下来的。
③int possible[82][10]该数组的用途是记录所有未确定数字的所有可能性,possible数组各元素的值在初始化时被初始化为与其第二维的下标一致,即0-9(其中0表示空值);在中间计算过程中,若发现第一维某位置的某种可能性已不复存在,则将第一维下标是该位置而第二维下标是该不再可能的数字的元素值改为-1。
④int review[82]该数组的用途类似于栈,在回溯算法过程中起到至关重要的作用。
回溯之前,要把所有fix数组中值为0的位置依次存放进review数组;回溯中,由低到高依次逐渐确定这些位置的数值,无法确定者(即1-9的数字都不适合者)则应当回退到前一个位置,并修改其fix数组中的值,依次类推,直至review数组所存放的所有位置的值都能确定(即题目完成),或回退到最初点的前一个位置(即题目有误)。
4.3.2 相关函数本项目中为实现算法的数据结构所用到的主要函数如下:①void setPossible()该函数是用来设置possible数组中元素的值,其具体功能是:对于fix数组中每一个值为0的位置(即对于每一个没有确定下数字的位置),考察其可能的数字是哪些,并记录下来。
考察的方法是:在1-9这些数字中除去在当前行、列、九宫中已有的数字,剩下的即为可能的取值对象。
②bool fixPossible()该函数是用来修改fix数组和possible数组中元素的值,其返回值表示了在此次运行该函数过程中是否执行了修改。
其具体功能是:对于fix数组中每一个值为0的位置(即对于每一个没有确定下数字的位置),考察其可能的数字的个数,若为1,则将fix数组该位置的值改为1且sudoku数组该位置的值改为该唯一可能的数字(即该位置的值已确定下来),且返回真;若没有只有一种可能性的位置,则返回假。
③bool isExist(int i,int j)该函数用于回溯过程中,其用途是:判断sudoku数组中位置为i的元素的值是否可能为j,即判断的是位置i所在的行、列以及九宫中是否已经存在数字j,若存在则返回真,否则返回假。
4.4 求解算法实现4.4.1 有限递推在执行一次fixPossible函数之后,就可能会确定下一些原来没有确定的数字,那么此时possible数组的值必然也会变动。
这是因为确定下的数字越多,某些位置的可能数字的数目就会减少,那么此时就应再执行setPossible函数修改possible数组。
而修改之后可能又会出现一些只有一种可能性的位置,那么就应该再执行fixPossible函数。
于是,只要如此循环往复下去,就能最大限度地确定下根据题目本身含义和规则而确定下的内容。
确定的数字越多,对于将来回溯也就越有利。
经验表明,有些数独题直接就可以通过执行大约10多次的有限递推循环体解决。
一般情况下,有限递推的循环结束只有两种情况:一是推出了全部结果;二是fixPossible函数返回值为假,即不存在只有一种可能性的位置。
预处理的主要代码见附录。
4.4.2 回溯回溯是数独求解算法中最为关键的部分,其主要步骤是:①按下标的由低到高扫描fix数组,将值为0的位置记录进review数组,记录的顺序也是由低到高,最后记录下fix数组中为0位置的个数再加1;②把review数组看作栈,记录栈顶;③先设置一个bool型标志变量flag并初始化为true,下面各步骤都将在一个条件始终为真的死循环中进行,该循环由一条对flag进行真假判断的语句分成两大部分。
若当前栈顶是经过了回退操作而达到的,则flag会已被记录为false;否则flag为true。
死循环结束的条件是review数组(栈)中top与max的值相等,即解出最终结果,或者是无解,最终top小于0。
在死循环中,若flag为true,则进入步骤④,否则进入步骤⑤。
④若flag为true,则说明栈顶是经过正常的假设而达到的,并非由回退达到,那么根据possible数组以及isExist函数对栈顶所保存位置的可能出现的数字进行判断,把遇到的第一个可能数字放进sudoku数组,并把fix数组的该位置的值设为1,并判断是否已经解出结果,即review数组(栈)中top和max的值是否相等。
若相等,则得出结果并退出死循环;若发现1-9都不可能应用在栈顶所保存的位置,则说明前面的假设有错误,此时应当回退。
即fix数组和sudoku数组的该位置重设为0,top减1,并将flag的值设为false。
然后结束本轮循环体,继续下一轮的循环。
⑤若flag为false,说明是经过了回退才达到现在这个栈顶的,那么若仍然没有可能的数字可以应用在当前栈顶所保存的位置上,则继续执行出栈操作,即fix数组和sudoku数组的该位置的值重设为0,top减1;否则将遇到的第一个可能数字应用到该位置,即再设好sudoku数组和fix数组,将top加1,并将flag的值设为true。
然后结束本轮循环体,继续下一轮的循环。
回溯的主要代码见附录。
4.5 出题算法实现4.5.1 生成终盘数独出题时要先采用拉斯维加斯算法随机生成一个数独题的终盘。