解数独(Python)
0.目录
1.介绍
2.一些通用函数
3.全局变量(宏变量)
4.数独预处理(约束传播)
5.解数独(深度优先搜索+最小代价优先)
6.主函数
7.总代码
1.介绍
数独是一个非常有趣味性的智力游戏,数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵(Latin Square)。
参与者需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个宫内的数字均含1-9,不重复。
一个数独谜题是由81个方块组成的网格。大部分爱好者把列标为1-9,把行标为A-I,把9个方块的一组(列,行,或者方框)称为一个单元,把处于同一单元的方块称为对等方块。谜题中有些方块是空白的,其他的填入了数字。
每个方块都属于3个单元,有20个对等方块。
当每个单元的方块填入了1到9的一个排列时,谜题就解决了。
本文采用解空间搜索的深度优先搜索(最小代价优先)加约束传播算法来解数独。
代码总体分为五个部分:
1.通用函数
2.全局变量(宏变量)
3.数独预处理(约束传播)
4.解数独(深度优先搜索+最小代价优先)
5.主函数
2.一些通用函数
import time
def cross(A, B):
# 例如:A = 'ABC', B = '123'
# 则返回['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
return [a+b for a in A for b in B]
def arr_to_dict(A, B):
# 例如:A = ['A', 'B', 'C'], B = ['1', '2', '3']
# 则返回{'A': '1', 'B': '2', 'C': '3'}
return dict(zip(A, B))
def str_to_arr(str_sudoku):
# 传入:str_sudoku = '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......'
# 返回['4', '.', '.', '.', '.', '.', '8', ... , '.', '.']
return [c for c in str_sudoku if c in cols or c in '0.']
def show_str_sudoku(str_sudoku):
# 解析字符串形式的数独并展示
for i, value in enumerate(str_sudoku):
if i%3 == 0 and i%9 != 0:
print('|', end=' ')
print(value, end=' ')
if (i+1)%9 == 0:
print()
if i == 26 or i == 53:
print('------+-------+------')
def show_dict_sudoku(dict_sudoku):
# 解析字典形式的数独并展示
width = 1 + max(len(dict_sudoku[s]) for s in squares)
line = '+'.join(['-' * (width * 3)] * 3)
for r in rows:
print(''.join(dict_sudoku[r + c].center(width) + ('|' if c in '36' else '') for c in cols))
if r in 'CF': print(line)
print()
cross函数:输出A、B交叉组合而成的字符串
arr_to_dict函数:将数组形式的数独转化为字典形式的数独
str_to_arr函数:将字符串形式的数独转化为数组形式的数独
show_str_sudoku函数:解析字符串形式的数独并显示
show_dict_sudoku函数:解析字典形式的数独并显示
3.全局变量(宏变量)
用Python按如下方式来实现单元、对等方块、方块的概念:
cols = '123456789'
rows = 'ABCDEFGHI'
# squares表示 9*9个元素编号:['A1', 'A2', 'A3', ... , 'I8', 'I9']
squares = cross(rows, cols)
# unitlist表示 3*9个单元列表:
unitlist = ([cross(rows, c) for c in cols] + [cross(r, cols) for r in rows] + [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')])
# units表示 某个元素编号:与之相关的3个单元列表
units = dict((s, [u for u in unitlist if s in u]) for s in squares)
# peers表示 某个元素编号:与之相关的20个元素编号
peers = dict((s, set(sum(units[s], []))-set([s])) for s in squares)
squares代表81个元素编号
unitlist代表27个不能出现重复数字的单元
units表示某个元素编号以及与之对应的3个单元列表
peers表示某个元素编号以及与之相关的20个元素编号
4.数独预处理(约束传播)
初始数独的样子:

以下是简单的预处理函数:
# 一.数独预处理
def parse_sudoku(str_sudoku):
# values代表各位置上可能的取值:{'A1': '123456789', 'A2': '123456789', ... , 'I8': '123456789', 'I9': '123456789'}
values = dict((s, cols) for s in squares)
# arr_sudoku为数组形式, dict_sudoku为字典形式, 均为81位
arr_sudoku = str_to_arr(str_sudoku)
dict_sudoku = arr_to_dict(squares, arr_sudoku)# {'A1': '4', 'A2': '.', ... , 'I8': '.', 'I9': '.'}
for key,value in dict_sudoku.items():
if value in cols and not assign(values, key, value):
return False
return values
def assign(values, key, value):
# 从values[key]中删除除了value以外的所有值,因为value是唯一的值
# 如果在过程中发现矛盾,则返回False
other_values = values[key].replace(value, '')
if all(eliminate(values, key, num) for num in other_values):
return values
else:
return False
def eliminate(values, key, num):
# 从values[key]中删除值num,因为num是不可能的
if num not in values[key]:
return values
values[key] = values[key].replace(num, '')
return values
共三个函数。values[key]代表在key这个位置上的可能取值。
parse_sudoku函数:预处理的入口函数
assign函数:从values[key]中删除除了value以外的所有值
eliminate函数:从values[key]中删除值num
处理完后的数独为:

以上只是简单的进行的数独的预处理。
但是其实根据数独的规则,我们可以得到以下两条原则:
(1).如果一个方块只有一个可能值,把这个值从方块的对等方块(的可能值)中排除;
(2).如果一个单元只有一个可能位置来放某个值,就把值放那。
于是我们根据这个策略可以改写eliminate函数:
def eliminate(values, key, num):
# 从values[key]中删除值num,因为num是不可能的
if num not in values[key]:
return values
values[key] = values[key].replace(num, '')
# 这里采用了约束传播
# 1.如果一个方块只有一个可能值,把这个值从方块的对等方块(的可能值)中排除。
if len(values[key]) == 0:
return False
elif len(values[key]) == 1:
only_value = values[key]
# 从与之相关的20个元素中删除only_value
if not all(eliminate(values, peer, only_value) for peer in peers[key]):
return False
# 2.如果一个单元只有一个可能位置来放某个值,就把值放那。
for unit in units[key]:
dplaces = [s for s in unit if num in values[s]]
if len(dplaces) == 0:
return False
elif len(dplaces) == 1:
only_key = dplaces[0]
if not assign(values, only_key, num):
return False
return values
于是数独的预处理结果变为了:

这样是不是就把问题规模一下子简化了很多。
5.解数独(深度优先搜索+最小代价优先)
因为没有规定数独只有唯一解,所以以下程序实际上求解了数独的所有解。
# 二.解数独
def solve_sudoku(str_sudoku):
return search_sudoku(parse_sudoku(str_sudoku))
def search_sudoku(values):
if values is False:
return False
if all(len(values[s]) == 1 for s in squares):
return values
# 选择可能值数目最少的方块, 进行深度优先搜索
n, key = min((len(values[key]), key) for key in squares if len(values[key]) > 1)
return some_result(search_sudoku(assign(values.copy(), key, num)) for num in values[key])
def some_result(values):
for result in values:
if result:
return result
return False
solve_sudoku函数:是真正的解数独的入口,将数独预处理完毕的结果抛给search_sudoku函数求解
search_sudoku函数:是一个递归函数,采用的代价函数是选择可能值数目最少的方块,然后进行深度优先搜索遍历。
some_result函数:是在深度优先搜索的结果中找出满足条件的数独返回。如果想要所有解,那么可以改成返回一个解的列表。
如果想要程序更快,那么就可以只找一个解。可以在深度优先搜索的循环代码中,返回找到的满足条件的解即可。
6.主函数
if __name__ == '__main__':
# str_sudoku为字符串形式, 为81位
str_sudoku = ['4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......']
# str_sudoku = ['4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......',
# '003020600900305001001806400008102900700000008006708200002609500800203009005010300',
# '.....6....59.....82....8....45........3........6..3.54...325..6..................']
for sudoku in str_sudoku:
start = time.clock()
solve_result = solve_sudoku(sudoku)
end = time.clock()
print('初始数独为:')
show_str_sudoku(sudoku)
print('解为:')
show_dict_sudoku(solve_result)
print("求解数独运行时间为: %f s" % (end - start))
解出来数独的结果为:

7.总代码
'''
数独是一个非常有趣味性的智力游戏
参与者需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,
并满足每一行、每一列、每一个宫内的数字均含1-9,不重复。
'''
__author__ = 'PyLearn'
import time
def cross(A, B):
# 例如:A = 'ABC', B = '123'
# 则返回['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
return [a+b for a in A for b in B]
def arr_to_dict(A, B):
# 例如:A = ['A', 'B', 'C'], B = ['1', '2', '3']
# 则返回{'A': '1', 'B': '2', 'C': '3'}
return dict(zip(A, B))
def str_to_arr(str_sudoku):
# 传入:str_sudoku = '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......'
# 返回['4', '.', '.', '.', '.', '.', '8', ... , '.', '.']
return [c for c in str_sudoku if c in cols or c in '0.']
def show_str_sudoku(str_sudoku):
# 解析字符串形式的数独并展示
for i, value in enumerate(str_sudoku):
if i%3 == 0 and i%9 != 0:
print('|', end=' ')
print(value, end=' ')
if (i+1)%9 == 0:
print()
if i == 26 or i == 53:
print('------+-------+------')
def show_dict_sudoku(dict_sudoku):
# 解析字典形式的数独并展示
width = 1 + max(len(dict_sudoku[s]) for s in squares)
line = '+'.join(['-' * (width * 3)] * 3)
for r in rows:
print(''.join(dict_sudoku[r + c].center(width) + ('|' if c in '36' else '') for c in cols))
if r in 'CF': print(line)
print()
cols = '123456789'
rows = 'ABCDEFGHI'
# squares表示 9*9个元素编号:['A1', 'A2', 'A3', ... , 'I8', 'I9']
squares = cross(rows, cols)
# unitlist表示 3*9个单元列表:
unitlist = ([cross(rows, c) for c in cols] + [cross(r, cols) for r in rows] + [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')])
# units表示 某个元素编号:与之相关的3个单元列表
units = dict((s, [u for u in unitlist if s in u]) for s in squares)
# peers表示 某个元素编号:与之相关的20个元素编号
peers = dict((s, set(sum(units[s], []))-set([s])) for s in squares)
# 一.数独预处理
def parse_sudoku(str_sudoku):
# values代表各位置上可能的取值:{'A1': '123456789', 'A2': '123456789', ... , 'I8': '123456789', 'I9': '123456789'}
values = dict((s, cols) for s in squares)
# arr_sudoku为数组形式, dict_sudoku为字典形式, 均为81位
arr_sudoku = str_to_arr(str_sudoku)
dict_sudoku = arr_to_dict(squares, arr_sudoku)# {'A1': '4', 'A2': '.', ... , 'I8': '.', 'I9': '.'}
for key,value in dict_sudoku.items():
if value in cols and not assign(values, key, value):
return False
return values
def assign(values, key, value):
# 从values[key]中删除除了value以外的所有值,因为value是唯一的值
# 如果在过程中发现矛盾,则返回False
other_values = values[key].replace(value, '')
if all(eliminate(values, key, num) for num in other_values):
return values
else:
return False
def eliminate(values, key, num):
# 从values[key]中删除值num,因为num是不可能的
if num not in values[key]:
return values
values[key] = values[key].replace(num, '')
# 这里采用了约束传播
# 1.如果一个方块只有一个可能值,把这个值从方块的对等方块(的可能值)中排除。
if len(values[key]) == 0:
return False
elif len(values[key]) == 1:
only_value = values[key]
# 从与之相关的20个元素中删除only_value
if not all(eliminate(values, peer, only_value) for peer in peers[key]):
return False
# 2.如果一个单元只有一个可能位置来放某个值,就把值放那。
for unit in units[key]:
dplaces = [s for s in unit if num in values[s]]
if len(dplaces) == 0:
return False
elif len(dplaces) == 1:
only_key = dplaces[0]
if not assign(values, only_key, num):
return False
return values
# 二.解数独
def solve_sudoku(str_sudoku):
return search_sudoku(parse_sudoku(str_sudoku))
def search_sudoku(values):
if values is False:
return False
if all(len(values[s]) == 1 for s in squares):
return values
# 选择可能值数目最少的方块, 进行深度优先搜索
n, key = min((len(values[key]), key) for key in squares if len(values[key]) > 1)
return some_result(search_sudoku(assign(values.copy(), key, num)) for num in values[key])
def some_result(values):
for result in values:
if result:
return result
return False
if __name__ == '__main__':
# str_sudoku为字符串形式, 为81位
str_sudoku = ['4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......']
# str_sudoku = ['4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......',
# '003020600900305001001806400008102900700000008006708200002609500800203009005010300',
# '.....6....59.....82....8....45........3........6..3.54...325..6..................']
for sudoku in str_sudoku:
start = time.clock()
solve_result = solve_sudoku(sudoku)
end = time.clock()
print('初始数独为:')
show_str_sudoku(sudoku)
print('解为:')
show_dict_sudoku(solve_result)
print("求解数独运行时间为: %f s" % (end - start))
解数独(Python)的更多相关文章
- 旁友数独会伐啦?python秒解数独了解下伐啦?
前几天和隔壁邻居玩斗地主被发现了,牌被没收了,斗地主是斗不了了,但我还想和邻居玩耍.如果你还想斗斗地主,戳:趁老王不在,和隔壁邻居斗斗地主,比比大小 想破脑袋终于让我想到一个游戏,数独!什么叫数独?数 ...
- 使用python解数独
偶然发现linux系统附带的一个数独游戏,打开玩了几把.无奈是个数独菜鸟,以前没玩过,根本就走不出几步就一团浆糊了. 于是就打算借助计算机的强大运算力来暴力解数独,还是很有乐趣的. 下面就记录一下我写 ...
- LeetCode37 使用回溯算法实现解数独,详解剪枝优化
本文始发于个人公众号:TechFlow,原创不易,求个关注 数独是一个老少咸宜的益智游戏,一直有很多拥趸.但是有没有想过,数独游戏是怎么创造出来的呢?当然我们可以每一关都人工设置,但是显然这工作量非常 ...
- 用C++实现的解数独(Sudoku)程序
我是一个C++初学者,控制台实现了一个解数独的小程序. 代码如下: //"数独游戏"V1.0 //李国良于2016年11月11日编写完成 #include <iostream ...
- Jquery暴力解数独
var arry= new Array(); var nums= new Array(); var snum; function numchain() { snum=0; for(var i=0; ...
- hdu 3111 DLX解数独
思路:裸的DLX解数独.关键是建图,感觉还不如写个dfs直接,DLX写这个的代码很烦. #include<set> #include<map> #include<cmat ...
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector ...
- 【原创】一个基于简单剪枝的DFS解数独程序
问题来源:leetCode Sudoku Solver Write a program to solve aSudoku puzzle by filling the empty cells. Empt ...
- [leetcode]37. Sudoku Solver 解数独
Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must satisfy ...
随机推荐
- Java——RMI
之前分布式系统调用用的是比较老的EJB,当时还是作为服务调用方,去调用别的系统的服务.最近发现新公司里面,用的是RMI,查了下发现EJB的底层实现就是RMI,也算是熟悉了... 一,使用JDK 中的R ...
- 「Leetcode」13. Roman to Integer(Java)
分析 把具体的情况一个一个实现即可,没有什么幺蛾子. 代码 class Solution { public int romanToInt(String s) { int ans = 0; for (i ...
- ArrayList 源码分析 -- 扩容问题及序列化问题
目录 一.前言二.ArrayList 的继承与实现关系 2.1 ArrayList.java 2.2 抽象类AbstractList.java 2.3 接口List.java ...
- 2.1 Oracle之DML的SQL语句之单表查询以及函数
1.SQL简介 对于不同的数据库来说,SQL语句是相通的,关系型数据库都以SQL语句为操作的标准,只是相应的数据库对应的函数不相同. SQL(Structured Query Language,结构化 ...
- 【Python 开发】Python目录
目录: [Python开发]第一篇:计算机基础 [Python 开发]第二篇 :Python安装 [Python 开发]第三篇:python 实用小工具
- 袋鼠云研发手记 | 开源·数栈-扩展FlinkSQL实现流与维表的join
作为一家创新驱动的科技公司,袋鼠云每年研发投入达数千万,公司80%员工都是技术人员,袋鼠云产品家族包括企业级一站式数据中台PaaS数栈.交互式数据可视化大屏开发平台Easy[V]等产品也在迅速迭代.在 ...
- MySQL 中的数据类型介绍
1.MySQL 数据类型 MySQL中定义数据字段的类型对你数据库的优化是非常重要的. MySQL支持多种类型,大致可以分为三类:数值.日期/时间和字符串(字符)类型. 2.数值类型(12) 2.1. ...
- 曾经我是一个只会excel的数据分析师,直到我遇到了……
我是一个数据分析师. 准确来说我是一个当年只会excel数据透视表,就天不怕地不怕地来当数据分析师的人.当年的某一天,我的老板Q我: 小刘啊,我小姨子给了我一个全国市委书记的名单,你帮我看看,有什么规 ...
- Oracle 11g用exp无法导出空表的处理方法
Oracle 11G在用EXPORT导出时,空表不能导出. 11G中有个新特性,当表无数据时,不分配segment,以节省空间 解决方法: 1.insert一行,再rollback就产生segment ...
- Macbook Pro开机黑屏了。
问题描述:点了appstore的更新,然后重启黑屏.(说明:黑屏是屏幕没亮:灰屏是屏幕亮了是灰黑色的.) 黑屏问题大,灰屏问题小. 开机按option没反应的跳到步骤四 一.数据 苹果电脑黑屏了,想搞 ...