最近突然对数独感兴趣,玩了好几天。慢慢摸索出了一些解题规律,就想着能不能用代码自动化实现。趁五一放假试着写了点代码,效果还行,至少不比网上一些教程里慢太多,因此就放到这里跟大家分享一下。

由于本人也是Python新手,很多语句可能不够简洁,还望见谅。

完整代码可见github链接:https://github.com/linking9230/sudoku.git

一、数独输入

我准备了两套数独题目作为测试,一套为困难难度的,另一套为专家难度。

困难:

专家:

我将数据放入到CSV文件中,空白格用0代替,方便读取和识别。

二、代码实现

数独的玩法大家应该都清楚,就是每一格的取值为1-9中的一个,并且在所在行,所在列,以及所在九宫格内不能重复。而游戏的难点就在于,每一个单元格,可填入的数字会有很多,这就使得我们不得不去寻找正确的数字。

当我实际在玩时,有很多小技巧,可以相对快速的找到正确的数字。但是对于编程而言,代码的优势在于计算速度快,因此使用穷举思想,也能够较快速找到正确的数字。

因此,我的第一个想法,就是利用递归循环解决问题。

——————————————————————————————————————
首先,我需要获取任意一个单元格可填入的值列表,代码如下:

def para(i):r=i//9c=i%9rr=r//3cr=c//3rr_d=rr*3rr_u=rr*3+3cr_d=cr*3cr_u=cr*3+3return r,c,rr_d,rr_u,cr_d,cr_udef ql(m,i):f_l=[0,1,2,3,4,5,6,7,8,9]r,c,rr_d,rr_u,cr_d,cr_u=para(i)a=list(set(list(m[r])+list(m[:,c])+list(m[rr_d:rr_u,cr_d:cr_u].flatten())))q_l=[x for x in f_l if x not in a]return q_l,r,c

第一个函数的目的是,我在实际循环过程中,是将99的数独矩阵,展开成了811的一维列表,方便我进行循环。而利用para函数,则可以从一维列表的index返回到9*9矩阵的参数,并且获得该单元格所在九宫格的范围(rr_d,rr_u,cr_d,cr_u即为该九宫格的行列上下限)

第二个函数也很好理解,我把所在行,所在列,所在九宫格的所有值集合起来,用一个set去重,那么剩下未出现的数字,就是该单元格可以选择的值,返回为一个列表。

——————————————————————————————————————
接着,就可以递归循环解答了(后来我才知道,这种递归循环,其实也就是深度优先循环,或者DFS算法,也算歪打正着)。

def dg(m,n,i,l,flag):#m:matrix#n:flatten matrix#i: 第i个单元格#l:最后一个空单元格的位置if flag==0:for j in range(i,l+1):if n[j]==0:breakq_l,r,c=ql(m,j)if len(q_l)==0:flag=-1return flagif j==l and len(q_l)>1:flag=-1return flagfor t in q_l:m[r,c]=tk=j+1if k==l+1:flag=1return flagelse:flag=0flag=dg(m,n,k,l,flag)if flag==1:breakif flag==-1:m[r,c]=0return flagif flag==1:return flag

这一步的算法是这样的,从第一个待填单元格开始,先获取可填值列表。在该列表中循环,先选取第一个数字填入到矩阵中,在此基础上,找到下一个待填单元格,同样获取列表,再选取第一个值填入。

当然这样填下去,大概率是会出错的。一旦出错,会出现的情况就是,在某个空单元格中,可填值列表为无。当遇到这种情况时,我们就停止递归,返回到上一级,在前一个空单元格的可填列表中,选择填入下一个数字,再继续进行递归循环。假如前一个空单元格的可填列表都试过了,在下一级依然出错,那就再返回到上上一级中,选择列表的下一个可选值,如此反复。

这样之后,一定能在最后选择到合适正确的值,使得递归能够顺利进行下去,直至最后一个待填单元格。理论上来说,到了最后一个单元格时,它的可填列表一定只能剩下一个数字,因此我们这里就可以判定,一旦它的可填列表大于了1,就说明之前填入的数字有误,需要返回到上一级。

在这一过程中,我用了一个flag参数,来调控程序是返回上一级还是继续下一级循环。flag=-1表示此时出错了,不在继续循环,返回到上一级中。当flag=0时,继续递归循环。当flag=1时,说明所有数据都被正确填入了,任务完成,也就不需要继续递归了。

在函数中,我return flag是为了将下一级的判定传回上一级。这一点也是debug试错试出来的,如果不return, 上一级的flag不会发生改变,永远是0。

另外还有一点很重要的是,当第k个单元格可填值列表循环完成之后,flag仍然为-1,需要再返回到第k-1个单元格时,一定要把k单元格的数字变为0,否则会保留列表中的最后一个数字,造成错误。

最后将上述整合成一个solver函数即可

def solver1(m,n):#find the last empty cellfor i in reversed(range(1,81)):if n[i]==0:l=ibreakflag=0i=0flag=dg(m,n,i,l,flag)return m

困难版运算时间为0.045秒,专家版运算时间为5.106秒。

——————————————————————————————————

完成这一步后,我就开始思考如何进一步提高算法速度。当然了,那些高级的编程技巧我是不懂的,我所能改善的,只能是引入一些人为的先验技巧。

  • 寻找可填入列表只有一项的空格
    这个操作往往是我在实际玩数独中的第一步,有些空格由于受周围有数字的单元格的影响,仅能填入一个数字,那这个数字一定就是解。代码如下:
def wy(m,n): flag=0for i in range(81):if n[i]==0:q_l,r,c=ql(m,i)if len(q_l)==1:m[r,c]=q_l[0]n[i]=q_l[0]flag=1return m,n,flag

由于填入一个数字后,可能会产生下一个具有唯一值的空格,因此我在主函数中用了循环,并用flag调控。当再也找不到新的唯一值空格时,就开始上述的深度遍历循环。

主函数代码如下:

def solver2(m,n):flag=1while flag==1:m,n,flag=wy(m,n)l=0for i in reversed(range(1,81)):if n[i]==0:l=ibreakif l!=0:flag=0i=0flag=dg(m,n,i,l,flag)return m

运行后,困难版的运算时间为0.021秒,而专家版的运算时间为5.15秒。

————————————————————————————————————

  • 寻找可填列表的唯一项
    这个技巧稍微高级一点,但也不难理解。在一行中,假设有多个空格,每个空格的可填值都是一个列表,例如为[1,3,5],[1,3,6,9],[3,6,9]。在这种情况下我们可以看到,数字1,3,6,9都有可能在多个空格中出现,但是数字5,仅有可能在第一个空格中出现。因此我们就能判定,第一个空格中应该填入数字5。
    同样的判定,也能适用在一列中,或者一个九宫格中。因此就有了如下的算法:
def dq_l(m,n):dql={}for i in range(81):if n[i]==0:q_l,r,c=ql(m,i)dql[i]=q_lreturn dql#check all available list in one row and find the value which only exist in one celldef h_c(m,n):flag=0dql=dq_l(m,n)for i in range(9):la=[]k_l=[x for x in range(9*i,9*i+9) if x in dql.keys()]for k in k_l:la+=dql[k]ct=Counter(la)lb=[]for j,v in ct.items():if v==1:lb.append(j)for t in lb:for k in k_l:if t in dql[k]:r,c,_,_,_,_=para(k)n[k]=tm[r,c]=tk_l.remove(k)flag=1breakdql={}return m,n,flag#check all available list in one column and find the value which only exist in one celldef v_c(m,n):flag=0dql=dq_l(m,n)for i in range(9):la=[]k_l=[x for x in range(i,73+i,9) if x in dql.keys()]for k in k_l:la+=dql[k]ct=Counter(la)lb=[]for j,v in ct.items():if v==1:lb.append(j)for t in lb:for k in k_l:if t in dql[k]:r,c,_,_,_,_=para(k)n[k]=tm[r,c]=tk_l.remove(k)flag=1break            dql={}return m,n,flag#check all available list in one square and find the value which only exist in one celldef n_c(m,n):flag=0dql=dq_l(m,n)c_l=[x for x in range(0,9,3)]+[x for x in range(27,36,3)]+[x for x in range(54,63,3)]for i in range(9):la=[]u=c_l[i]n_l=[x for x in range(u,u+3)]+[x for x in range(u+9,u+12)]+[x for x in range(u+18,u+21)]k_l=[x for x in n_l if x in dql.keys()]for k in k_l:la+=dql[k]ct=Counter(la)lb=[]for j,v in ct.items():if v==1:lb.append(j)for t in lb:for k in k_l:if t in dql[k]:r,c,_,_,_,_=para(k)n[k]=tm[r,c]=tk_l.remove(k)flag=1break     dql={}return m,n,flag

在上述代码中,我先是建立了一个dql函数,目的是将所有空格的可填值做成一个字典,字典的键是空格的次序,值是列表。

接着,在h_c函数中,我逐行进行检查。先获取行中所有空格的位置,在dql字典中,将所有空格的列表集合起来,并使用Counter函数进行统计,找到仅出现1次的数字。一行中有可能会出现不止一个出现次数为1的数字,因此获得的是一个列表。那么在列表中循环,反向去找到该数字对应的空格,将该数字在矩阵中填入即可。

同时要注意,对于同一行有多个唯一值的情况,每循环一个数,就要将该数从列表中删除,避免Bug。

v_c为逐列检测,n_c为逐个单元格检测。由于每一次填入值都会对其他单元格产生影响,因此我们在主函数中也是用循环,并利用flag来调控。当再也找不到唯一值时,再使用dfs循环。

def solver3(m,n):flag=1while flag==1:m,n,flag=h_c(m,n)m,n,flag=v_c(m,n)m,n,flag=n_c(m,n)l=0for i in reversed(range(1,81)):if n[i]==0:l=ibreakif l!=0:i=0flag=0flag=dg(m,n,i,l,flag)return m

困难版的运行时间为0.026秒,专家版的运行时间已经压缩到了2.59秒,节省了将近一半的时间。

总结

用时 仅用深度遍历 寻找唯一值+深度遍历 按行列九宫格寻找唯一值+深度遍历
困难版 0.045 0.021 0.026
专家版 5.127 5.465 2.628

可见对于相对简单的数独矩阵,用方法二就能显著提升运算性能,但是对于较困难的矩阵,方法三的提升效果更加显著。

以上均为个人探索的过程,有后续优化的想法,欢迎分享。

Python数独算法相关推荐

  1. 数独算法+文件读取+结果保存到文件+Python3版本

    操作系统:win10 64bit Python版本:Python 3.7.3 数独算法很多,在网上找了个C语言的,感觉还不错,用python改了一下,增加文件操作,从文件读取数独题目,运行后,把结果保 ...

  2. 用python设计数独的心得体会_python实现数独算法实例

    本文实例讲述了python实现数独算法的方法.分享给大家供大家参考.具体如下: # -*- coding: utf-8 -*- ''' Created on 2012-10-5 @author: Ad ...

  3. python:实现9×9二维数组数独算法(附完整源码)

    python:实现9×9二维数组数独算法 from __future__ import annotationsMatrix = list[list[int]]# assigning initial v ...

  4. python图像数独_Python判断有效的数独算法示例

    本文实例讲述了Python判断有效的数独算法.分享给大家供大家参考,具体如下: 一.题目 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 1. 数字 1-9 在 ...

  5. 完成数独的算法 python_python实现数独算法实例

    本文实例讲述了python实现数独算法的方法.分享给大家供大家参考.具体如下: # -*- coding: utf-8 -*- ''' Created on 2012-10-5 @author: Ad ...

  6. python 回溯算法总结

    python 回溯算法 回溯算法理论基础 组合 组合总数III 电话号码的字母组合 组合总和 组合总和ii 分割回文串 复原IP地址 子集问题 子集问题II 递增序列 全排列 全排列II 重新安排行程 ...

  7. GitHub超4.1万星,最全Python入门算法来了

    本文来自公众号:超级数学建模 微信号 :supermodeling 今天,阿广给大家推荐一个好资源,一个在 Github 上超过 2.7 万星标的项目:最全算法及Python实现. 该项目主要包括两方 ...

  8. 计算机书籍-Python机器学习算法大全

    书名:Python机器学习算法 作者:赵志勇 出版社: 电子工业出版社 ISBN:9787121313196 去当当网了解

  9. python中算法(sklearn)的最优超参数寻优:skopt贝叶斯搜索

    python中算法(sklearn)的最优超参数寻优:skopt贝叶斯搜索 Jeff Dean在ICML 2019上进行了有关AutoML的演讲,并将自动化分为4个级别 手动构造预测变量,不引入学习的 ...

最新文章

  1. centos7开启,关闭防火墙
  2. 360加固一键脱壳工具2020_如何脱壳加固过的Apk并利用其API“走近数据库”
  3. 某计算机公司的库存管理,《管理系统中计算机应用》应用题数据流程图汇总题及参考答案...
  4. DB2 pureScale在线备份恢复实例1
  5. 98k用计算机图片,98K (HandClap)_谱友园地_中国曲谱网
  6. 关于SSH的分工(网友讨论集合贴)
  7. Intelij IDEA中web项目抛org.apache.ibatis.binding.BindingException: Invalid bound statement(not found):异常
  8. ps aux 中的状态说明
  9. Ubuntu20 运行不了网络助手NetAssist
  10. ASCII码不可见字符过滤处理
  11. ZZULIOJ1046-1050Python解法
  12. 神经网络BP反向传播算法原理和详细推导流程
  13. Linux Deamon函数
  14. postgresql Connection to database failed: FATAL: no pg_hba.conf entry for host 10.129.195.161, us
  15. 安装 Linux mint后的一些设置
  16. 域名被微信/QQ红了(被封锁、被屏蔽、被和谐)后最好的解决方法
  17. 【重识云原生】第六章容器6.3.5节——Controller Manager概述
  18. iOS 第三方 汇总
  19. python 根号化简_Python 基础
  20. 计算机wps系统的造字程序在哪里,Winxp系统自带造字程序在哪|Winxp系统自带造字程序的使用方法...

热门文章

  1. python报告水印怎么弄_如何用python操作word添加水印?这里有个问题,希望大家看一下!...
  2. CSS特效三:简单的便签效果
  3. 信息学奥赛一本通——2062:【例1.3】电影票
  4. 评联想收购IBM PC
  5. 51Nod 1463
  6. 为什么iPhone 12 中国版不支持5G毫米波?
  7. 我学历低,学软件测试能找到工作吗?
  8. VB 四言古诗词赏析 制作
  9. 豪杰DVD播放器 3.0.8 URL重新定向栈溢出漏洞
  10. 话说ipsec之倚天屠龙记版