当前位置:   article > 正文

利用python,求解数独_用python解数独

用python解数独
  1. import numpy as np
  2. import time
  3. time1 = time.time()
  4. '''
  5. 整体灵感就是
  6. 1 求出每个数字为0的位置可以填的数,并将其位置和能填的数分别以key和value的方式
  7. 存储到字典里面
  8. 2 将字典里的数据按照所能填写的数据的多少进行排序,先在能填的数少的里面选取一个
  9. 进行填写
  10. 3 将填写的过程记录到列表里面,这个地方暂时想到的就是用列表记录填写过程
  11. 4 更新1、2步,若出现某一步可填写的数据为空,说明之前某一步的选择有问题,回溯回
  12. 去,更换数值,然后回到步骤1
  13. 5 当所有的数都填完后,退出循环
  14. '''
  15. def nine(data):
  16. nine_block = np.zeros([3,3,3,3], dtype = int)
  17. for i in range(3):
  18. for j in range(3):
  19. nine_block[i,j] = data[3*i:3*(i+1),3*j:3*(j+1)]
  20. return nine_block
  21. def num_set(data, nine_block):
  22. pick_set = {}
  23. for i in range(9):
  24. for j in range(9):
  25. if data[i,j] == 0:
  26. pick_set[str(i)+str(j)] = set(np.array(range(10))) - \
  27. (set(data[i,:]) | set(data[:,j]) | \
  28. set(nine_block[i//3,j//3].ravel()))
  29. return pick_set
  30. def try_insert(data):
  31. insert_step = []
  32. while True:
  33. pick_set = num_set(data, nine(data))
  34. if len(pick_set) == 0: break
  35. pick_sort = sorted(pick_set.items(), key = lambda x:len(x[1]))
  36. item_min = pick_sort[0]
  37. key = item_min[0]
  38. value = list(item_min[1])
  39. insert_step.append((key, value))
  40. if len(value) != 0:
  41. data[int(key[0]), int(key[1])] = value[0]
  42. else:
  43. insert_step.pop()
  44. for i in range(len(insert_step)):
  45. huishuo = insert_step.pop()
  46. key = huishuo[0]
  47. insert_num = huishuo[1]
  48. if len(insert_num) == 1:
  49. data[int(key[0]), int(key[1])] = 0
  50. else:
  51. data[int(key[0]), int(key[1])] = insert_num[1]
  52. insert_step.append((key, insert_num[1:]))
  53. break
  54. tiem2 = time.time()
  55. print('\nFinished! using time:', tiem2-time1, 's')
  56. print(data)
  57. if __name__ == '__main__':
  58. data = "0 0 0 0 0 0 0 6 0 \
  59. 0 0 0 0 0 4 7 0 5 \
  60. 5 0 0 0 0 0 1 0 4 \
  61. 1 0 0 0 0 2 4 0 0 \
  62. 0 0 8 0 7 0 0 0 0 \
  63. 0 3 0 6 0 0 0 0 0 \
  64. 2 0 0 0 0 9 0 0 1 \
  65. 0 0 6 0 8 0 0 0 0 \
  66. 0 7 0 3 0 0 0 0 0 "
  67. data = np.array(data.split(), dtype = int).reshape((9, 9))
  68. print(data)
  69. try_insert(data)

别不多说,代码先行,运行结果如下

      用时1.91秒,还算能看,其实很早就想做点什么,但碍于能力有限,一直拖着,没有实践。刚开始考虑用excel实现求解数独来着,奈何同样涉及到VBA,而在下对VBA只是略懂皮毛,只能作罢。

      至于学习Python的过程,其实在很早的时候,就听同窗加挚友洪同学提及过Python,当时第一感觉和帕金森音略同,故而与之戏称帕金森云云,真正开始学习Python其实要追溯到17年12月份,到写此文时也有近6个月的时间,不知不觉,小半年了。进度之缓慢,说来实在惭愧。

      好了,回到正文,其实基本的思想在代码里面也有所提及,下面就着重讲解下各部分代码在干嘛吧!

      numpy这个东西很好用,里面提供了数组的各种花样操作,对于初学者来说,降低了入门难度,故而我很喜欢^_^。而time模块在这里其实只起到了计算程序运行时间的作用,就不再说了。

      首先,定义函数nine(data),这个函数的主要功能是将9×9的数据,变成3×3的块,于是在事先,就先利用了numpy模块生成了这样的块,这也就是下面这行代码所干的事。

nine_block = np.zeros([3,3,3,3], dtype = int)

紧接着,将原来的数据按块填进已生成的nine_block中,就是两个for循环,就实现了这个需求。

  1. for i in range(3):
  2. for j in range(3):
  3. nine_block[i,j] = data[3*i:3*(i+1),3*j:3*(j+1)]

至于这里的赋值为什么是这样来赋值,其实很容易理解,拿所要填的第一块来说,也就是nine_block[0,0],是不是希望将data里面的前三行前三列数据填过来,这个时候那就是data[0:3, 0:3]; 再看水平方向的第二块,是不是就是data[0:3, 3:6],以此类推。那么跟nine_block的索引i, j结合起来就不难看出这个赋值表达式意义了。

        接着,定义函数num_set(data, nine_block),此函数的作用为,将data中所有为0的位置的可填数找到,并存储到一个字典里面,其中这个字典的key为0值的行索引和列索引,这里用str(i)+str(j)实现,而该位置可填的数究竟怎么确定的,其实,这就要回到数独的玩法上了,因为在填数时玩家必须保证该位置所属的行、列以及块里面没有这个数的时候,否则就违反了游戏规则,那么这里的可填数就使用了这个规则,就是在(1 ~ 9)的数字集合删去已经在相关位置出现的数,那么这样就找到了该位置可填的数的集合。在这里主要用到了集合的子、交、并、补概念。

  1. pick_set = {}
  2. for i in range(9):
  3. for j in range(9):
  4. if data[i,j] == 0:
  5. pick_set[str(i)+str(j)] = set(np.array(range(10))) - \
  6. (set(data[i,:]) | set(data[:,j]) | \
  7. set(nine_block[i//3,j//3].ravel()))

可能你会注意到在这里的相应的所属块取出来后,可能无法直接用set(nine_block[i//3,j//3]),但我把这个块状的数据ravel()后,就是拉平后,可以使用了,至于内在原因,还在摸索当中。

这个函数的返回值是长这样的^_^

                                                                

比如说'00':{3,4,7,8,9},这就表明此时在data中位于第1行,第1列的位置,其可填的数为3,4,7,8和9。其他的也是如此。

        最后定义的函数为try_insert(data),实现了数据的插入,步骤分为两步,即插和改。由于对递归实现的方法不甚理解,所以就不班门弄斧了,这里使用的是利用列表来记录每一次的插入。插入一个值,同时在插入下一个值之前,就更新了我们的可选字典,这样以便快速验证插入数值的正确性。那么究竟怎么判断我首次插入的值是否准确呢,其实这个地方就是深度优先的概念了,就是说,一条路走到黑,直到走不通为止,因为但凡我前面的数填的不对,走到后面就会发现,在某一个位置就会出现没有可填数的情况,但怎么告诉程序说前面填错了呢?思考三分钟...

        这个时候我们的记录插入情况的列表就派上用场了,可以根据插入情况进行回溯,怎么个回溯法呢?简单来说就是由后往前找,在第一个分叉的地方,换另外的数,并把之前走不通的数舍去,下面就一个例子进行说明,假如下面是我的某一次的插入过程

                                                                

在程序里面默认的插入方法是:当有多个选择时,先插入可选数集合中的第1个数。在如上的这个插入过程当中,发现,在进行对索引值为'55'位置的数进行插入时,竟然发现此处没有可插入数,那么,问题来了,必然是前面出错了,可是错误出在哪呢?无从得知,只知道错误出现了,那么就此例而言,错误会出现在索引值为'45', '75', '01'这些可填值只有1个的位置上吗,显然不是,因为可填数只有1个,那我只能填这个了,暂且称之为被动正确(临时想到的),那么这个错误很有可能是出在诸如索引值为'00', '21'这样的可填数不唯一的地方,可是怎么回去呢,思考三分钟...

        一言以蔽之 改,如何改,不卖关子了,就是把'70', '10', '62'这些被动正确的地方数重新变为0,然后在'00'这个位置把插入数不选9,选3,然后往下走,如果选3之后后面走通了,那么皆大欢喜,但是,万一走不通怎么办,如果又走不通,说明不是'00'处的问题,而是问题还在前面,如'21', '40'这样的位置,但如何回溯到更前一步呢?即然在前面情况不变下,'00'处选9走不通,那何不在首次回溯时,就暂时在'00'处的可选值中将9丢弃,这样在'00'处选3的情况下,如果还走不通,那么我们再次进行回溯时,就不会还是找'00'了,而是会找到'21'的位置。如此循环,直到所有的值都填上为止。

        代码就不再贴了,前面可能没有说,在选值进行插入时,我提前将可选集合按照可填数的多少进行了排序,先填可选数少的,这样可以大大提高程序运行效率。^_^


                
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/434325
推荐阅读
相关标签
  

闽ICP备14008679号