Python练手项目之数独(二)——出题

上文中给出了数独的生成方法,那么如何出题呢?最常见的是在随机生成数独之后进行挖洞,挖洞的过程中判断挖完洞的宫格是否有唯一解答,当然不追求唯一解答也可以,只要有解也是一种选择。
话不多说,直接打码。
首先需要在类中添加一个成员变量,用于挖洞过程中记录当前数独数据

class Sudoku:
    def __init__(self):
        # 初始化九宫格
        self.grids = np.zeros((9, 9), dtype='i1', order='C')
        self.possibleNums = {
   1, 2, 3, 4, 5, 6, 7, 8, 9}

        # 挖洞时保存挖好的数独
        self.uniqueGrids = None

先给出挖洞后是否有唯一解的方法。

    # 挖洞后判断是否该九宫格只有唯一的答案
    def checkUnique(self, row, col):
        for value in range(1, 10):
            if self.grids[row, col] != value:
                # 假设挖掉这个数字
                self.grids[row, col] = 0
                if value in self.get_possible(row, col):
                    # 更换一个数字之后检查是否还有另一解
                    self.grids[row, col] = value
                    if self.dfs():
                        return False

                # 上面进行深度优先过程已经改变了 self.grids的值,恢复更换这个数字之前的状态
                self.grids = self.uniqueGrids.copy()

        # 已尝试所有其他数字发现无解,即只有唯一解
        return True

下面就是挖洞方法了,参数level实际上从81个格子中挖掉的数量.。可以通过这个参数来设置难度。挖掉的数越多,难度相对来说。

    def generateByDigHole(self, level):
        # level 表示要挖掉的数字个数,通常挖掉 50 - 60 个左右,
        # 最多挖去63-64个可以得到有唯一解的宫格,但是需要计算的时间会长很多
        self.uniqueGrids = self.grids.copy()

        digged = 0
        while digged <= level:
            row = random.randint(0, 8)
            col = random.randint(0, 8)
            # 该格子已经挖过了
            if self.uniqueGrids[row, col] == 0:
                continue

            # 挖掉该格子后能生成唯一的九宫格。如果有就继续挖,如果没有唯一解就不挖这个格子
            if self.checkUnique(row, col):
                # 保存挖洞后的状态
                self.uniqueGrids[row, col] = 0

                # 挖完洞后继续挖,直到挖出指定数量的格子
                self.grids = self.uniqueGrids.copy()

                digged += 1

                # 难度特别大时的副产品,
                if digged >= 52:
                    print("After dig {}:".format(digged))
                    self.output_sudo()

好了,数独类就这样了。如果想测试一下性能,或者观察数独解答或挖洞过程中迭代的次数,可以添加相关的统计变量,然后在方法中相关位置添加就可以了。这里只是展示最基本的功能,个人感觉越简单、越清晰越好。
既然数独类已经完成,下面进行测试代码。

def main():
    mySudoku = Sudoku()

    # 在生成算法中,包括生成一个最终解以及挖洞。生成一个最终解由于采用的是随机算法,
    # 因此分析起来比较复杂,不过将n取11的时候已经有99 % 概率生成正解了,
    # 也就是99 % 的概率只需要尝试一次,因此不妨就设为O(V + E)。
    while not mySudoku.lasVegas(11):
        pass

    mySudoku.output_sudo()

    digCount = 56
    # 通过测试发现,checkUnique并不能确保挖出来的宫格只有唯一解。
    mySudoku.generateByDigHole(digCount)
    mySudoku.output_sudo()

    # 重新将挖洞后的数独利用深度优先算法解答
    mySudoku.dfs()
    mySudoku.output_sudo()


if __name__ == '__main__':
    main()

经过我的测试,挖54个·洞生成一个有唯一解的数独在一秒内就可以完成。但是如果挖55个或以上的具有唯一解的数独需要的时间就慢慢变长了。如果要挖出60个以上的洞,那么可能需要喝杯茶才行。当然,有兴趣的朋友看看算法上有没有改善的空间,或者利用空间换时间的方法等。

下面给一个我挖57个洞的输出数据,共参考。

   +-------+-------+-------+
   | 1     |     5 |       |
   |       |   3   |       |
   |       |   4   |       |
   +-------+-------+-------+
   |       |       | 8     |
   |       | 1     |       |
   |       |       | 6     |
   +-------+-------+-------+
   |       |   2   |       |
   |       |       |       |
   | 7     |       | 9 4   |
   +-------+-------+-------+
   根据初始11个数随机生成的数独
   +-------+-------+-------+
   | 1 2 3 | 8 9 5 | 4
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值