最近迷上了一款手机数独游戏,游戏界面如下,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。因此也叫作9宫格游戏。
尝试自己动脑挑战游戏是一种乐趣,但是玩到后面的关卡,难度会越来越高,丧失游戏的乐趣,因此,尝试用算法来解决数独问题。
我们先来看看解决数独问题的基本思路,对于一个已知某些数字的九宫格棋盘,我们总是倾向于从优先填充那些能够准确推导出数字的空格,然后根据填充的数字,继续推导其他的数字,直到把整个表格填满。在简单的情况下,我们能保证每次都能准确的填充一些数字,而在一些困难度较高的情况下,可能出现某些空格可以填入2个数字或者更多的情况,这需要我们进行一些尝试,先填一个备选的数字,然后继续之前的步骤,如果能够把所有空格都填满,则说明该处填的数字是正确的,直接返回,如果到某一步出错,即出现某些点可选的数字为0,则表示之前填的数字是错误的,用下一个备选数字代替;重复上述步骤直到结束;
整理成算法就是下面的步骤:
- 根据九宫格的规则确定每个空格可以填充的数字,即每行,每列以及线宫内不能出现重复的数字,例如第一行第二列可填充数字为3,记作([1,2],[3]),类似的还有([2,3],[4,7]、([5,7],[2,6,9])等;
- 对所有空格进行排序,筛选出可填充数字最少的那个;例如上诉的例子中我们选择([1,2],[3])这个点;
- 对第二步选出的点进行填充,此时有三种情况:
- 若备选数字只有一个,则直接更新即可,
- 若备选数字有多个,则取一个数字填充到位置中,并将该点记录下来,
- 若备选数字为空,表示前一次的备选数字选择出错,则回到上一个记录的点,取下一个数字填充该位置;
- 对每个点填充后都需要更新空格的当前可填充数字情况;
代码如下,首先,我们定义两个类,表示空格点和备选点队列;
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]]