Z3是由Microsoft Research开发的高性能定理证明器。接下来将使用Python3中的Z3库来实现对数独问题的解决。
关于Python中Z3的使用入门,可以参考这篇博文https://blog.csdn.net/yalecaltech/article/details/90575076
问题分析
数独问题就是在一个
9
×
9
9\times 9
9×9的格网的每个格子中填入0~9九个数字之一,使得每一行、每一列、每一宫(将整个盘面均分为9个
3
×
3
3\times 3
3×3的九宫格,即为九个宫)中的数都不重复。记
A
i
j
A_{ij}
Aij为第
i
i
i行第
j
j
j列的数,本问题的约束条件就是
∀
i
∀
j
∀
k
(
i
≠
k
∧
j
≠
k
→
(
A
i
j
≠
A
i
k
)
∧
(
A
i
j
≠
A
k
j
)
)
\forall i\forall j\forall k(i\neq k\land j\neq k\rightarrow(A_{ij}\neq A_{ik})\land(A_{ij}\neq A_{kj}))
∀i∀j∀k(i=k∧j=k→(Aij=Aik)∧(Aij=Akj))
∀
i
∀
j
∀
m
∀
n
(
(
(
1
≤
i
,
m
≤
3
)
∨
(
4
≤
i
,
m
≤
6
)
∨
(
7
≤
i
,
m
≤
9
)
)
∧
(
(
1
≤
j
,
n
≤
3
)
∨
(
4
≤
j
,
n
≤
6
)
∨
(
7
≤
j
,
n
≤
9
)
)
∧
(
i
≠
m
∨
j
≠
n
)
→
(
A
i
j
≠
A
m
n
)
)
\forall i\forall j \forall m\forall n(((1\le i,m\le 3)\lor(4\le i,m\le 6)\lor(7\le i,m\le9))\land ((1\le j,n\le 3)\lor(4\le j,n\le 6)\lor(7\le j,n\le9))\land(i\neq m\lor j\neq n)\rightarrow(A_{ij}\neq A_{mn}))
∀i∀j∀m∀n(((1≤i,m≤3)∨(4≤i,m≤6)∨(7≤i,m≤9))∧((1≤j,n≤3)∨(4≤j,n≤6)∨(7≤j,n≤9))∧(i=m∨j=n)→(Aij=Amn))
最初格子内的提示数因不能更改也应该作为约束条件加入求解器中。
完整代码如下:
from z3 import *
# 约束变元数组,变元名为a_行_列
arr=[[Int('a_%d_%d'%(i+1,j+1)) for i in range(9)] for j in range(9)]
# 初始条件,数字表示提示数,0表示未填入
problem=((0,2,9,0,0,0,4,0,0),
(0,0,0,5,0,0,1,0,0),
(0,4,0,0,0,0,0,0,0),
(0,0,0,0,4,2,0,0,0),
(6,0,0,0,0,0,0,7,0),
(5,0,2,0,0,0,0,0,0),
(7,0,0,3,0,0,0,0,5),
(0,1,0,0,9,0,0,0,0),
(0,0,0,0,0,0,0,6,0))
# 数独规则约束
Sudoku=[And(arr[i][j]>0,arr[i][j]<10) for i in range(9) for j in range(9)] # 数字为1~9的整数
for i in range(9):
for j in range(1,9):
for k in range(j):
Sudoku+=[And(arr[i][j]!=arr[i][k],arr[j][i]!=arr[k][i])] # 任一行各数两两不能相同,任一列各数两不能相同
d=[(v,h) for v in [-1,0,1] for h in [-1,0,1]] # 小九宫格中各格相对中心格的偏移
for i in [1,4,7]:
for j in [1,4,7]:
for k in range(1,9):
for l in range(k):
Sudoku+=[arr[i+d[k][0]][j+d[k][1]]!=arr[i+d[l][0]][j+d[l][1]]] # 九宫格中数两两不能相同
def SolveSudoku(board):
"""使用Z3约束求解器求解数独,若有解,返回表示可行解的9*9列表;若无解返回None"""
global Sudoku,arr
for i in range(9):
for j in range(9):
if board[i][j]!=0:
Sudoku+=[arr[i][j]==board[i][j]] # 将初始数字作为约束条件添加
s=Solver()
s.add(Sudoku)
if s.check(): # 检查是否有解
m=s.model() # 获取解的模型
res=[[m[arr[i][j]] for j in range(9)] for i in range(9)] # 生成可行解的列表
return res
else:
return None # 无解返回None
# 按行打印结果
for i in SolveSudoku(problem):
print(i)
这里关于每个宫中的约束使用了一个常用技巧,通过偏移数组d将二维的区域变得可以一维遍历
代码运行结果如下