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)的更多相关文章

  1. 旁友数独会伐啦?python秒解数独了解下伐啦?

    前几天和隔壁邻居玩斗地主被发现了,牌被没收了,斗地主是斗不了了,但我还想和邻居玩耍.如果你还想斗斗地主,戳:趁老王不在,和隔壁邻居斗斗地主,比比大小 想破脑袋终于让我想到一个游戏,数独!什么叫数独?数 ...

  2. 使用python解数独

    偶然发现linux系统附带的一个数独游戏,打开玩了几把.无奈是个数独菜鸟,以前没玩过,根本就走不出几步就一团浆糊了. 于是就打算借助计算机的强大运算力来暴力解数独,还是很有乐趣的. 下面就记录一下我写 ...

  3. LeetCode37 使用回溯算法实现解数独,详解剪枝优化

    本文始发于个人公众号:TechFlow,原创不易,求个关注 数独是一个老少咸宜的益智游戏,一直有很多拥趸.但是有没有想过,数独游戏是怎么创造出来的呢?当然我们可以每一关都人工设置,但是显然这工作量非常 ...

  4. 用C++实现的解数独(Sudoku)程序

    我是一个C++初学者,控制台实现了一个解数独的小程序. 代码如下: //"数独游戏"V1.0 //李国良于2016年11月11日编写完成 #include <iostream ...

  5. Jquery暴力解数独

      var arry= new Array(); var nums= new Array(); var snum; function numchain() { snum=0; for(var i=0; ...

  6. hdu 3111 DLX解数独

    思路:裸的DLX解数独.关键是建图,感觉还不如写个dfs直接,DLX写这个的代码很烦. #include<set> #include<map> #include<cmat ...

  7. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

  8. 【原创】一个基于简单剪枝的DFS解数独程序

    问题来源:leetCode Sudoku Solver Write a program to solve aSudoku puzzle by filling the empty cells. Empt ...

  9. [leetcode]37. Sudoku Solver 解数独

    Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must satisfy  ...

随机推荐

  1. SaltStack入门篇(二)之远程执行和配置管理

    1.远程执行 第一条命令: [root@linux-node1 master]# salt '*' test.ping linux-node2.example.com: True linux-node ...

  2. 06004_Redis的启动、使用和停止

    1.Redis的启动 (1)前端模式启动 ①直接运行bin/redis-server将以前端模式启动:切换到 /usr/local/redis/bin目录下,然后./redis-server : ②前 ...

  3. PHP基础知识试题

    转载于:http://www.php.cn/toutiao-415599.html 1.PHP中传值与传引用的区别,什么时候传值,什么时候传引用? 按值传递:函数范围内对值任何改变在函数外部都会被忽略 ...

  4. 同一个电脑配置两个Git问题

    拿到公司电脑后,正常配置gitlab,以及设置邮箱等等,可以使用公司邮箱,以及一系列设置 git config --global user.name "userName" git ...

  5. JavaScript里的循环方法之forEach,for-in,for-of

    JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标 ...

  6. JavaScript学习笔记(二)——函数和数组

    第二章 函数简介 1 第一个函数示例 <script language="JavaScript" type="text/JavaScript"> f ...

  7. 检查Linux服务器性能的关键十条命令

    检查Linux服务器性能的关键十条命令 概述 通过执行以下命令,可以在1分钟内对系统资源使用情况有个大致的了解. uptime dmesg | tail vmstat 1 mpstat -P ALL ...

  8. Lucky Conversion(找规律)

    Description Petya loves lucky numbers very much. Everybody knows that lucky numbers are positive int ...

  9. 实验一 MiniOS

    实验一.命令解释程序的编写实验 商软1班   杨晶晶  201406114102 一.        实验目的 (1)掌握命令解释程序的原理: (2)掌握简单的DOS调用方法: (3)掌握C语言编程初 ...

  10. “Hello World!”团队第六周的第五次会议

    今天是我们团队“Hello World!”团队第六周召开的第五次会议.博客内容: 一.会议时间 二.会议地点 三.会议成员 四.会议内容 五.todo list 六.会议照片 七.燃尽图 八.代码 一 ...