实验目的
N-Queen问题是经典的八皇后问题的推广,即在一个N×N的棋盘上放置N个皇后,使得任意两个皇后都不能在同一行、同一列或同一斜线上。这是一个NP完全问题,因此可以使用形式化方法中的pure SAT和SMT求解。本文使用 pure SAT 求解 N-Queen 问题, 并对比 PPT 中 SMT 的实现效率。
pure SAT 编码思路
1.导入Z3库和time库,定义n皇后问题的求解函数n_queen_sat(n),创建一个solver实例
2.创建一个n x n的棋盘,用布尔变量表示每个格子是否放置皇后
3.添加约束条件,保证每个格子至少有一个皇后
4.添加约束条件,保证任意两个皇后不在同一行、同一列或同一对角线上
5.使用Z3求解器找到一个满足约束条件的解
6.输出解,计算求解时间并输出
7.分别求解n=4,8,12,16,20,24,32的N皇后问题
和SMT对比
Time\Method | pure SAT | SMT |
4 | 0.13403749465942383 s | 0.26999998092651367 s |
8 | 0.12152719497680664 s | 0.304002046585083 s |
12 | 0.18607354164123535 s | 0.3803980350494385 s |
16 | 0.21963977813720703 s | 0.37200021743774414 s |
20 | 0.30319929122924805 s | 0.7380886077880859 s |
24 | 0.3517324924468994 s | 0.7714138031005859 s |
32 | 0.7708442211151123 s | 2.959766149520874 s |
40 | 0.7223820686340332 s | 26.008009433746338 s |
50 | 71.10545802116394 s | 26.6474769115448 s |
60 | 160.44336414337158 s | 27.320754528045654 s |
分析pure SAT和SMT 的效率
从表格中可以看出,在N值较小的情况下,两个代码的运行时间差别不大。但是当N值增大到50以上时,pure SAT的运行时间明显比SMT长很多。这是因为pure SAT使用了布尔变量来表示每个格子是否放置了皇后,然后约束每个格子至少放置一个皇后,以及每行、每列、每个对角线上最多只能有一个皇后。这样的约束条件是比较复杂的,因此在N值增大时,求解器需要花费更长的时间来找到可行解。而SMT则使用了整数变量来表示每行皇后所在的列数,然后约束每行皇后的列数不同,并且不允许出现对角线上的冲突。这样的约束条件比较简单,因此求解器可以更快地找到可行解。因此,在N值较大的情况下,SMT的效率比pure SAT高。
具体代码及注释:
1.pure SAT
from z3 import *
import time
def n_queen_sat(n):
# create a solver instance
solver = Solver()
# create a boolean variable
board = [[Bool("b_{}_{}".format(i, j)) for j in range(n)] for i in range(n)]
# ensure at least one queen is placed
for i in range(n):
solver.add(Or([board[i][j] for j in range(n)]))
# cannot contain two queens
for i in range(n):
for j in range(n):
for k in range(j+1, n):
solver.add(Or(Not(board[i][j]), Not(board[i][k])))
solver.add(Or(Not(board[j][i]), Not(board[k][i])))
for k in range(1, n):
if i+k < n and j+k < n:
solver.add(Or(Not(board[i][j]), Not(board[i+k][j+k])))
if i+k < n and j-k >= 0:
solver.add(Or(Not(board[i][j]), Not(board[i+k][j-k])))
# find a satisfying assignment to the constraints
start_time = time.time()
if solver.check() == sat:
model = solver.model()
for i in range(n):
row = ['Q' if is_true(model[board[i][j]]) else '.' for j in range(n)]
print(' '.join(row))
else:
print('No solution found')
end_time = time.time()
print('Time:', end_time - start_time, 's')
if __name__ == '__main__':
n_queen_sat(4)
n_queen_sat(8)
n_queen_sat(12)
n_queen_sat(16)
n_queen_sat(20)
n_queen_sat(24)
n_queen_sat(32)
n_queen_sat(40)
n_queen_sat(50)
n_queen_sat(60)
2. SMT
from z3 import *
import time
def n_queen_smt(n):
solver = Solver()
rows = [Int("r_{}".format(i)) for i in range(n)]
for i in range(n):
solver.add(rows[i] >= 0)
solver.add(rows[i] < n)
for j in range(i):
solver.add(rows[i] != rows[j])
solver.add(rows[i] != rows[j] + (i - j))
solver.add(rows[i] != rows[j] - (i - j))
start_time = time.time()
if solver.check() == sat:
model = solver.model()
for i in range(n):
row = ['Q' if model[rows[i]] == j else '.' for j in range(n)]
print(' '.join(row))
else:
print('No solution found')
end_time = time.time()
print('Time:', end_time - start_time, 's')
if __name__ == '__main__':
n_queen_smt(4)
n_queen_smt(8)
n_queen_smt(12)
n_queen_smt(16)
n_queen_smt(20)
n_queen_smt(24)
n_queen_smt(32)
n_queen_smt(40)
n_queen_smt(50)
n_queen_smt(60)