数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵,20世纪70年代,经过美国及日本学者的推广和改良,定名为数独(Sudoku),大致的意思是“独个的数字”或“只出现一次的数字”。
标准的九宫格数独包含9×9个格子,且每3×3的区域组成一宫,数独的规则要求在空出来的格子里填如1~9的数字,要满足每行、每列和每宫内的数字都不重复,也就是行、列及宫里都是由不重复的1~9构成。数独还包含了一些6×6、不规则九宫等个性数独,本篇仅讨论标准九宫格数独的情况。
手动解的技巧有唯余解法、基础排除法、区块排除法、数对唯余法等,进阶的有唯一矩形法、数对占位法、双分支匹配等。
【数独示例及手动解法概览】
(数独解法概览来自标准数独)
用电脑解最通用的还是穷举整个解空间,根据数独规则进行剪枝和回溯。效率和递归深度、需要缓存的中间过程有关,递归深度主要由挖空的个数决定。最简单的穷举算法是对每个单元格都用1~9分别尝试,满足条件继续尝试下一个挖空的格,直到所有单元格都填了合适的数字,且检查符合数独规则就算找到一个解。唯一解要求当前盘面有且只有这一个解。
进一步的做法是为每个挖空的格子维护一个候选数列表,用这个列表中的值进行试数,出现矛盾就回溯,很暴力但其实挺有效的。更高级一点的舞蹈链法及利用模拟退火等方法,也还是离不开试数和回溯的思路。因此下面主要实现的是基于候选数的回溯解法。
首先数独中的数值我们可以用一个一维长度为81的数组表示,也可以用二维9×9的数组表示,下面采用9×9的数组表示,例如一个数独,其盘面用二维数组表示如下:
【图】
回溯的思路是:从第一个挖空的单元格开始,根据其相关20格(本行、本列及所在宫内的单元格)生成候选数列表lst,lst的生成直接地利用了唯余法进行排除,对列表lst中的值进行向下尝试,尝试下一个挖空的单元格,当不满足数独规则时,回退到上一个挖空的单元格。
写成代码如下:
【图】
再把getPrem(x,y,board)
及cheackNotSame(x,y,v,board)
实现后,就可以变成完整的代码了,
1 | class sovSudoku: #求解数独 |
对于上面的最难数独,在本机上求解效果如下,耗时在秒级,回溯性能也不是很差。
【图】
网上再找几个数独进行测试,各自耗时如下:
【图】
在leetcode上有两道数独相关的题目,第37题就是根据输入的数独(用9×9的二维数组表示)求结果。它是用'.'
代表挖空,把上面的代码改一下,提交运行的效果如下:
【图】
运行时间在秒级以下,因为回溯会有多次栈调用,内存花费在10多MB。大于平常的一些练习题。
第36题是检查当前盘面的合法性,不考虑该数独能否求解,只需要根据数独规则判断是否满足数独条件,将以上代码修改后提交的结果如下:
【图】
数独游戏GUI
有了上面的检查数独是否合法以及解数独的代码后,再加上生成数独的代码就可以写一个小游戏训练自己了。81个单元格,假设每次挖掉n 个数字形成一个数独题目,根据排列组合的算法,一共有C(81,n)种挖法。要保证数独有唯一解,则至少要保留17个提示数,也就是说n 最多只能是81-17=64。n取1,2这种数也没什么好玩的,只挖一两个空太好解了,因此n应该有个合理的最小值,如果每行挖两个空,那就是18个空,因此n可以取[18,64],从量级上我们就能看出,就算我们每天接触1万个数独,穷尽一生接触到的数独题目数量也只占冰山一角,因此不需要担心会刷到重复的数独,概率太低。直接随机某个位置随机填入一个数字再随机其他位置来生成数独效率并不高,比较合理的做法是程序内部有几个完整的数独,通过数字置换和随机挖空来产生新的数独。
考虑数独的特点,如果我们有一个数组[6,8,5,1,9,4,3,2,7],表示将数独中的数字1变成数字6,把2变成8,以此类推……,类似凯撒加密的做法。由数独的特点可以推出新生成的数独也是符合规则的。
挖空操作就是随机挖去n处的值,再验证是否有唯一解,就可以生成一个数独题目了。
GUI程序的流程还是遵从:
导入tk库,创建主窗体->添加控件->处理交互->进入主事件循环
最后实现的GUI如下:
【几个盘面以及交互,初始数独,填写验证,电脑解】
各按钮交互简介:
- 生成数独: 随机生成一个数独;
- 验证答案: 没填完的情况下,根据当前所填进行验证;填完了,不满足条件则提示,满足说明解答正确,进行弹窗;
- 电脑解:电脑对当前基础盘面进行计算,把值渲染到数独上(可以对字体、颜色进行进一步个性化);
- 清空:把所有值都清空,方便用户输入一个盘面。
部分代码如下,继续用内置的tkinter库实现。
1 | root=tk.Tk() |
打包GUI为exe文件
还是用pyinstaller
把程序变成exe可执行文件,大小是8点多M,作为Python导出的exe文件,这个大小是有优势的,结果如下:
【内存大小,图】
本文从解数独的手动解法引入,讲到解数独常用的回溯法,并且按照思路实现回溯代码,通过这一思路去解两个LeetCode题,为了可玩性增加随机生成一个数独的代码,并把以上功能整合为一个GUI程序,用于平时的数独训练,并且把这一GUI脚本打包为一个exe文件,在Windows系统下使用。