数独游戏解法

最近迷上了一款手机数独游戏,游戏界面如下,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。因此也叫作9宫格游戏。

尝试自己动脑挑战游戏是一种乐趣,但是玩到后面的关卡,难度会越来越高,丧失游戏的乐趣,因此,尝试用算法来解决数独问题。

我们先来看看解决数独问题的基本思路,对于一个已知某些数字的九宫格棋盘,我们总是倾向于从优先填充那些能够准确推导出数字的空格,然后根据填充的数字,继续推导其他的数字,直到把整个表格填满。在简单的情况下,我们能保证每次都能准确的填充一些数字,而在一些困难度较高的情况下,可能出现某些空格可以填入2个数字或者更多的情况,这需要我们进行一些尝试,先填一个备选的数字,然后继续之前的步骤,如果能够把所有空格都填满,则说明该处填的数字是正确的,直接返回,如果到某一步出错,即出现某些点可选的数字为0,则表示之前填的数字是错误的,用下一个备选数字代替;重复上述步骤直到结束;

整理成算法就是下面的步骤:

  1. 根据九宫格的规则确定每个空格可以填充的数字,即每行,每列以及线宫内不能出现重复的数字,例如第一行第二列可填充数字为3,记作([1,2],[3]),类似的还有([2,3],[4,7]、([5,7],[2,6,9])等;
  2. 对所有空格进行排序,筛选出可填充数字最少的那个;例如上诉的例子中我们选择([1,2],[3])这个点;
  3. 对第二步选出的点进行填充,此时有三种情况:
    • 若备选数字只有一个,则直接更新即可,
    • 若备选数字有多个,则取一个数字填充到位置中,并将该点记录下来,
    • 若备选数字为空,表示前一次的备选数字选择出错,则回到上一个记录的点,取下一个数字填充该位置;
  4. 对每个点填充后都需要更新空格的当前可填充数字情况;

代码如下,首先,我们定义两个类,表示空格点和备选点队列;

import copy


class Point(object):
    def __init__(self, x, y, optional_list):
        self.x = x
        self.y = y
        self.optional_list = optional_list

    def update(self, array):

        # 删除同行的值;
        array_row = array[self.x, :]
        for row_element in array_row:
            if row_element in self.optional_list:
                self.optional_list.remove(row_element)

        # 再删除同列的值;
        array_col = array[:, self.y]
        for col_element in array_col:
            if col_element in self.optional_list:
                self.optional_list.remove(col_element)

        # 最后删除周围的值;
        start_row = self.x // 3 * 3
        start_col = self.y // 3 * 3
        array_round = array[start_row: start_row + 3, start_col: start_col + 3].ravel()
        for round_element in array_round:
            if round_element in self.optional_list:
                self.optional_list.remove(round_element)

    def pop(self):
        return self.optional_list.pop()

    def get_size(self):
        return len(self.optional_list)

    # 按照optional_list大小排序;
    def __lt__(self, other):
        return len(self.optional_list) < len(other.optional_list)

    # 当不存在元素时,为假值;
    def __bool__(self):
        return len(self.optional_list) != 0

    def __repr__(self):
        return str(self.x + 1) + " " + str(self.y + 1) + " " + str(self.optional_list)


class BlurPoint(object):
    """定义未准确点"""
    def __init__(self, point, array):
        self.point = point
        self.array = copy.deepcopy(array)

    def get_x(self):
        return self.point.x

    def get_y(self):
        return self.point.y

    def next(self):
        return self.point.optional_list.pop()

    def get_array(self):
        return self.array

    def __bool__(self):
        return self.point.get_size() != 0

    def __repr__(self):
        return str(self.point)

然后为程序入口,input_array为输入的九宫格,

from numpy import array
from utils import Point
from utils import BlurPoint

input_array = array([[1, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [0, 0, 0, 0, 0, 0, 0, 0, 0],
                     ])


# 初始化所有未处理的点;
def init_all(array_list):
    processing_point = []
    array_rows, array_cols = array_list.shape
    for i in range(array_rows):
        for j in range(array_cols):
            # 已填充数据的点;
            if input_array[i, j] == 0:
                point = Point(i, j, list(range(1, 10)))
                point.update(input_array)
                processing_point.append(point)

    return processing_point


if __name__ == '__main__':

    vague_points = []

    # 初次扫描数组,初始化每个点类;
    processing_points = init_all(input_array)

    # 循环直到processing_points为空;
    while processing_points:
        processing_points.sort()
        first_point = processing_points.pop(0)
        # if len(first_point.optional_list) != 1:
        #     print(len(first_point.optional_list))
        # 只有一个元素,直接更新即可;
        if first_point.get_size() == 1:
            proper_figure = first_point.pop()
            input_array[first_point.x, first_point.y] = proper_figure

            for ele in processing_points:
                ele.update(input_array)

        # 没有可填充的元素,表示前面插值错误;
        elif first_point.get_size() == 0:
            # 取模糊集中的最后一个元素,
            blur_status = vague_points[-1]
            # 取下一个可能的值和当时保存的数组状态;
            blur_figure = blur_status.next()
            blur_array = blur_status.get_array()

            # 将当时保存的数组赋给input_array,注意,这里不是浅拷贝;
            input_array[:] = blur_array[:]
            input_array[blur_status.get_x(), blur_status.get_y()] = blur_figure

            # 判断是否需要删除该模糊点;
            if not blur_status:
                vague_points.remove(blur_status)

            # 更新整个列表;
            processing_points = init_all(input_array)

        # 可填充元素大于1,表示有多种可能;
        else:
            blur_figure = first_point.pop()
            blur_point = BlurPoint(first_point, input_array)
            vague_points.append(blur_point)

            input_array[first_point.x, first_point.y] = blur_figure

            for ele in processing_points:
                ele.update(input_array)

    print(input_array)

我们测试输入的九宫格只有第一个数字给出,为1,看看程序的输出:

[[1 9 8 7 6 5 4 3 2]
 [7 6 5 4 3 2 9 8 1]
 [4 3 2 9 8 1 7 6 5]
 [9 5 7 1 4 8 3 2 6]
 [8 2 4 6 7 3 5 1 9]
 [6 1 3 5 2 9 8 4 7]
 [5 8 9 3 1 6 2 7 4]
 [3 4 6 2 9 7 1 5 8]
 [2 7 1 8 5 4 6 9 3]]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值