Python学习案例2——数独解题及出题程序

第二个求解案例,我想到了数独。曾经有一段时间对数独求解非常感兴趣,在学习C++语言时也编写了数独求解程序。本次Python学习,要求比之前高一点,把出题程序也一起编写了吧。

1、数独简介

数独 (英语:Sudoku)是一种逻辑性的数字填充游戏,玩家须以数字填进每一格,而每行、每列和每个宫(即3x3的大格)有1至9所有数字。游戏设计者会提供一部分的数字,使谜题只有一个答案。答案满足同一个数字不可能在同一行、列或宫中出现多于一次。

2、算法

2.1 数独解题算法

最简单直接的算法就是:从第一个空格开始,尝试填入数字;之后再尝试下一个空格;如果失败,就回退,成功就继续,直至所有空格填满。具体如下:

(1)使用9X9二维矩阵表示数独局面,0表示空格,非零表示已经填写数字。从第一个元素开始,转步骤(2)

(2)搜索下一个空格。

(2-1)搜索到空格,转步骤(3);

(2-2)搜索不到空格,说明所有空格填写完毕,搜索到一个成功解题方案,结束程序。

(3)按照数独规则搜索当前空格可填写的数字集合。

(3-1)搜索不到可填写数字。当前方案不可行,将当前格恢复为0,回退一步,重新填写当前格数字。

(3-2)搜索到可填写的数字集合。从集合中取出一个数字,填入当前格。转步骤(2)。

第(2)、(3)步可以用递归程序实现,比较好实现回退和继续搜索功能。

这个算法简单粗暴,求解一般的数独题目都没有问题。但是在后续出题程序中,用这个算法测试出题结果是否可行时,经过大量随机数据测试,发现以下问题:

(1)效率低。有时甚至程序长时间运行不出结果,只能强行中止。

(2)一次只能搜索一个解法,不能测试题目的求解结果是否唯一。

分析问题(1)的原因主要在于初始搜索的可选数字较多,造成程序需要搜索方案数目剧增。因此,可以计算当前局面中所有空格的可选数字数目,从最小可选数字数目空格开始尝试填写数字。这样可以大大减少需要搜索方案数目。同时,为了避免频繁计算所有空格的可选数字数目,将搜索到的空格的可选数字集合存起来,在设置空格新数字、回退时更新相关空格的可选数字集合。

问题(2)的原因在于搜索到一个方案就结束,可以设置搜索的可行方案数目上限(上限设置为2就可以搜索求解方案是否唯一),达不到上限就回退继续搜索。

修改后的算法如下:

(1)使用9X9二维矩阵表示数独局面,0表示空格,非零表示已经填写数字。设置求解方案数目上限为NUP,搜索当前局面所有空格的可选数字集合,存入二维数组lsava中。转步骤(2)

(2)根据lsava,搜索可选数字数目最小的空格。

(2-1)搜索到空格,转步骤(3);

(2-2)搜索不到空格,说明所有空格填写完毕。搜索到一个成功解题方案,存储当前成功方案至成功方案列表。如果成功方案列表总数等于NUP,结束程序;否则将当前格恢复为0,恢复相关空格可选数字集合至lsava,回退一步,重新填写当前格数字。

(3)从lsava中取得当前空格可填写的数字集合。

(3-1)数字集合为空。当前方案不可行,将当前格恢复为0,恢复相关空格可选数字集合至lsava。回退一步,重新填写当前格数字。

(3-2)搜索到可填写的数字集合。从集合中取出一个数字,填入当前格。更新相关空格可选数字集合至lsava,同时暂存更新之前的集合,用于后续可能的回退操作。转步骤(2)。

2.2 数独出题算法

出题方法一:

最先想到的出题方法是仿照解题过程,从空盘开始逐渐填写数字:

  1. 从全部为空的局面开始;
  2. 在所余空格中随机挑选一个;
  3. 在选定空格的可用数字集合中随机挑选一个数字填入空格;
  4. 利用数独解题算法判断当前局面是否有解,如果否,则回退一步,恢复当前空格,转到步骤(3);如果有解,进一步判断解是否唯一,如果唯一,则找到结果,返回,否则转步骤(2)。

该算法的实现详见3.2。运行后发现,尽管采用了2.1中的优化后求解策略,但是由于随机设置数字过程很容易出现很多不可解局面,耗费大量搜索时间,有时甚至无法在10分钟内求解出结果。

为了解决这个问题,查阅网络文献,有了出题方法二:

主要思路是从一个完成的数独结果开始,随机挖除一定数目的空格,然后再判断当前局面解是否唯一。这样就避免的无解情况,搜索效率大幅提升。

挖除的空格数目越多,题目求解难度越高,同时多解概率也越高。可以设置空格数目的上限和下限值,调节所出题目的难度。

方法2需要大量数独结果,本算法使用一个固定数独结果,通过随机交换1-9数字位置产生所需大量数独结果,理论上可以产生9!= 362880个结果。再考虑题目上随机位置挖洞,可以产生的数独题目足够用了。具体如下:

  1. 通过随机交换1-9数字位置,产生一个数独结果;
  2. 设置空格数目的上限和下限值;
  3. 随机产生上限空格个位置,挖空位置,并保存挖空前的数值。
  4. 从空格上限开始,向下限方向搜索;
  5. 填入一个挖空前的数字。判断当前局面结果是否唯一,如果唯一,则结束;否则继续步骤(5)。
  6. 如果搜索不到唯一解,转置步骤(1)尝试下一个数独结果。

 

3、算法实现

3.1 数独解题

主程序Shudu1.py,输入输出及程序调用。

''''

    数独搜索

    读取用户输入文件,搜索结果。如果得不到结果,则提示失败。

'''

import sys

import CShudu1 as csd

#  主程序

if len(sys.argv)<2 :

    print("使用方法: ")

    print(sys.argv[0],"FileName")

    print("FileName为输入文件文件名称前缀,真正输入文件为 Filename.dat, 输出文件名称为FileName.res。")

    exit(1)

fin=sys.argv[1]+".dat"

fout=sys.argv[1]+".res"

print("输入文件:",fin,"输出文件",fout)

#初始化

cs=csd.CShudu()

nret=cs.ReadData(fin)

if nret!=0:

    print("读取输入文件错误!")

    exit(2)

print("原始数据:")

cs.PrintData()

# 开始查找

cs.CalLsava()

nr=cs.getNumRes(1)

if len(cs.lsres)>=1 :

    print("成功找到{}个结果!".format(len(cs.lsres)))

    cs.loadres(0)

    cs.PrintData()

    nret=cs.WriteData(fout)

    if nret!=0:

        print("写输出文件错误!")

        exit(3)

else :

    print("本题无解!")

 

   

子程序CShudu1.py,以类的形式封装数独解题算法。

import random

'''

封装为类的数独搜索

'''

class CShudu :

    def __init__(self): # 初始化

        # 数据

        self.clear()

 

    def clear(self): # 清除数据

        #当前局面数据

        self.data=[[0 for i in range(9)] for j in range(9)]

        #当前局面每个格子的可用数据列表

        self.lsava=[[set() for i in range(9)] for j in range(9)]

        #找到的可行解列表

        self.lsres=[]

    # 从保持data数据至lsres

    def saveres(self):

        self.lsres.append([self.data[i].copy() for i in range(9)])

        #print("in saveres",self.lsres)

    # 从lsres装载数据至data

    def loadres(self,k):

        #print("in loadres",self.lsres)

        self.data=[self.lsres[k][i].copy() for i in range(9)]

    # 从文件读矩阵数据

    def ReadData(self,fname) :

        try:

            f=open(fname,"r")

        except FileNotFoundError:

        #打开文件错误

            return 1

        for i in range(9):

            str=f.readline()

            lstr=str.split()

            #文件读取错误

            if len(lstr) < 9 : return 2

            self.data[i]=[int(lstr[j]) for j in range(9)]

        f.close()

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值