序言:
本文记录了用穷举法解数独。
0. 规则
对下面这个9 × 9的方格盘:
有些格子是已经填上数字的,有些格子是空白的。解题者需要在空白的格子填上数字(1-9之间),使得每一行,每一列,每一小九宫格内的数字不重复。注意,方格盘内所有数子均在1-9之间。
1. 思路
(1)方格盘边长N = 9,小九宫边长n = 3
(2)对每个格子标号,左上角为
(
0
,
0
)
\red{(0, 0)}
(0,0),往右横坐标递增,往下纵坐标递增,右下角为
(
8
,
8
)
\red{(8, 8)}
(8,8)。
(3)按照自左向右,自上而下顺序,对于每个空白的格子,尝试填入1-9。
举例:比如现在要在左上角第一个格子,也就是
(
0
,
0
)
\red{(0, 0)}
(0,0)这个格子填数字,遍历1-9这9个数字,它们填入之前必须满足
- 在第0行里没出现过;
- 在第0列里没出现过;
- 在第0个小九宫格里面没出现过。
如不满足它便不能够被填入。
(4)如果有数字能够满足上述3个要求,比如,我们可以在
(
0
,
0
)
\red{(0, 0)}
(0,0)这个格子填 ‘1’ 或 ‘6’。对于这种多种选择的,这里就可以先可以暂且填 ‘1’,然后继续往下填,直到某个格子没有选择了,也就是说1-9都填不进去的时候,说明刚才填’1’的策略不对,是条死路。这时候就要倒退回去,把’1’拿走,填’6’再往下做尝试。即,回溯法。
2. Python实现
(1)方格盘用二维列表maze
存储,空白格子用’0’表示
(2)3个判断。对于某一格子
(
r
,
c
)
\red{(r, c)}
(r,c),
- 所在行:
maze[r]
- 所在列:
[i[c] for i in maze]
- **当前小九宫格:
[i[j] for j in range(c_beg, c_beg + n) for i in maze[r_beg: r_beg + n]]
c_beg = r // n * n, r_beg = c // n * n
分别是当前小九宫格左上角格子的横、纵坐标n = 3
是小九宫格的边长
取小九宫格这个确实理解起来比较困难,需要画个图多思考一下。
以上三点能表示出来,后面就全部交给回溯法:
python3代码:
n = 3
N = n * n
a = [N * [0] for j in range(N + 1)]
def print_maze():
print('— ' * N)
for i in range(0, N):
for j in range(0, N):
if a[i][j] == 0:
print(' ', end='')
else:
print(a[i][j], end='')
if j % n != (n - 1):
print(' ', end='')
else:
print('|', end='')
print()
if i % n == n - 1:
print('— ' * N)
def get_sub(r: int, c: int)->list:
r_beg = int(r // n) * n
c_beg = int(c // n) * n
return [i[j] for j in range(c_beg, c_beg + n) for i in a[r_beg:r_beg + n]]
def check(arr: list, k)-> bool:
return not k in arr
def dfs(r: int, c: int):
if r == N:
print_maze()
return
if c == N:
dfs(r + 1, 0)
return
if a[r][c] == 0:
for k in range(1, N + 1):
row = a[r] # 当前行
col = [i[c] for i in a] # 当前列
'''截取子九宫'''
sub = get_sub(r, c)
if check(row, k) and check(col, k) and check(sub, k):
a[r][c] = k
dfs(r, c + 1)
a[r][c] = 0
else:
dfs(r, c + 1)
if __name__ == '__main__':
with open(r'test.txt', 'r') as f:
lines = f.readlines()
for i in range(len(lines)):
a[i] = [int(j) for j in lines[i].split()]
beg_r = 0
beg_c = 0
print('题目: ')
print_maze()
print('答案: ')
dfs(beg_r, beg_r)
3. 解题示例