解数独(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_1(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_1(sudoku)
        end = time.clock()
        print(‘初始数独为:‘)
        show_str_sudoku(sudoku)
        print(‘解为:‘)
        show_dict_sudoku(solve_result)
        print("求解数独运行时间为: %f s" % (end - start))
时间: 2024-10-04 15:27:46

解数独(Python)的相关文章

python解数独

昨晚心血来潮在leetcode上pick one了一道算法题 https://leetcode.com/problems/sudoku-solver/ 解决代码如下: class Solution(object): def solveSudoku(self, board): """ :type board: List[List[str]] :rtype: void Do not return anything, modify board in-place instead. &

解数独算法的实现——剪枝优化

最近人工智能做个小实验,组队选了个数独游戏,顺便研究了一下.解数独感觉主流思想也就是深搜回溯了吧,优化就是各种剪枝方法. 1 引言 数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵(Latin Square),曾风靡日本和英国.现有解法包括基础解法:摒除法,余数法,进阶解法:区块摒除法(Locked Candidates).数组法(Subset).四角对角线(X-Wing).唯一矩形(Unique Rectangle).全双值坟墓(Bivalue Universal Grave).单数链(X

机器学习经典算法详解及Python实现---朴素贝叶斯分类及其在文本分类、垃圾邮件检测中的应用

摘要: 朴素贝叶斯分类是贝叶斯分类器的一种,贝叶斯分类算法是统计学的一种分类方法,利用概率统计知识进行分类,其分类原理就是利用贝叶斯公式根据某对象的先验概率计算出其后验概率(即该对象属于某一类的概率),然后选择具有最大后验概率的类作为该对象所属的类.总的来说:当样本特征个数较多或者特征之间相关性较大时,朴素贝叶斯分类效率比不上决策树模型:当各特征相关性较小时,朴素贝叶斯分类性能最为良好.另外朴素贝叶斯的计算过程类条件概率等计算彼此是独立的,因此特别适于分布式计算.本文详述了朴素贝叶斯分类的统计学

[LeetCode] Sudoku Solver 解数独,递归,回溯

Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by the character '.'. You may assume that there will be only one unique solution. A sudoku puzzle... ...and its solution numbers marked in red. Hide Tags B

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

原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector machine,简称SVM.通俗来讲,它是一种二类分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解. (一)理解SVM基本原理 1,SVM的本质--分类 给定一些数据点,它们分别属于两个不同的类,现在要找到一个线性分类器把这些

跳舞链解数独 静态数组优化

前几天有人问我之前写的那个跳舞链解数独的程序的内存泄漏问题如何解决,因此回顾了一下我的那个程序.现在看来那个程序简直不忍直视,于是大刀阔斧的改了.主要是把动态内存分配都改为了静态预分配,这样就可以避免频繁的调用malloc和free.同时静态分配的好处就是内存访问局部性比较好,cache不容易miss.而且在一行四个节点连续分配的情况下,就没有必要存储左右指针了.而且在连续分配的时候,指针都可以蜕变为数组索引,访问就比较简单了.还有一个好处就是整个程序可读性大大增强.现在这个版本的代码如下,利用

使用双向十字链表(或Dancing Links)解数独游戏

#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; struct Data { void assign(int x,int y,int z) { row=x; col=y; val=z; } int row,col,val; } data[730]; struct Node { Node(int x=0,int y=0): row(x),col(y),up(this)

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

问题来源:leetCode Sudoku Solver Write a program to solve aSudoku puzzle by filling the empty cells. Empty cells are indicated by the character *.*. You may assume that there will be only one unique solution. 问题链接: https://oj.leetcode.com/problems/sudoku-

机器学习经典算法详解及Python实现--聚类及K均值、二分K-均值聚类算法

摘要 聚类是一种无监督的学习(无监督学习不依赖预先定义的类或带类标记的训练实例),它将相似的对象归到同一个簇中,它是观察式学习,而非示例式的学习,有点像全自动分类.说白了,聚类(clustering)是完全可以按字面意思来理解的--将相同.相似.相近.相关的对象实例聚成一类的过程.机器学习中常见的聚类算法包括 k-Means算法.期望最大化算法(Expectation Maximization,EM,参考"EM算法原理").谱聚类算法(参考机器学习算法复习-谱聚类)以及人工神经网络算法