【形式化方法】PartB:LA/LP Applications(N皇后问题)

N-Queen Problem:

在作业3(挑战问题)中,我们在SAT之前解决了N个皇后的问题(4个皇后)。这个问题是关于把N个皇后放在一个N*N的棋盘上,这样就没有两个皇后互相威胁了。一种解决方案要求没有两个皇后共享同一行、列、对角线或反对角线。下图显示了N = 4的样本N -皇后谜题的解:

这个问题的目标是在一个N*N棋盘,找出存在多少个解。

SAT实现的基本思想是通过Bool值构造n-queen谜题约束。实际上,我们可以用LA来求解n-queen问题,它比SAT更容易理解,也更高效。其思路与求解子集和问题的思路相同。我们使用一个二维的0-1标志F来代表棋盘的每个单元格,F有值:

 满足 0 < i <N, 0< j < N 。我们可以建立n-queen谜题的约束条件如下:

  • 每一行只有一个皇后:   0 < i <N 
  • 每一列只有一个皇后:0 < j < N
  • 每条对角线最多有1个皇后:-N < d < N
  • 每条反对角线最多有1个皇后:  0 < d < 2N -1

Exercise 11: 阅读queen.py Python文件中的代码,完成n_queen_la()方法,该方法使用0-1 ILA解决n-queen问题。您可以通过参考我们上面讨论的模型来构造约束,或者您可以提出您自己的约束。

 

# LA算法解决N皇后问题
def n_queen_la(board_size: int, verbose: bool = False) -> int:
    solver = Solver()
    n = board_size

    # Each position of the board is represented by a 0-1 integer variable:
    #   ...    ...    ...    ...
    #   x_2_0  x_2_1  x_2_2  ...
    #   x_1_0  x_1_1  x_1_2  ...
    #   x_0_0  x_0_1  x_0_2  ...
    #
    board = [[Int(f"x_{row}_{col}") for col in range(n)] for row in range(n)]
    # only be 0 or 1 in board
    for row in board:
        for pos in row:
            solver.add(Or(pos == 0, pos == 1))
        # print(row)


    # @exercise 11: please fill in the missing code to add
    # the following constraint into the solver:
    #   each row has just 1 queen,
    #   each column has just 1 queen,
    #   each diagonal has at most 1 queen,
    #   each anti-diagonal has at most 1 queen.
   # raise Todo("exercise 11: please fill in the missing code.")
    for row in board:
        # print(row)
        solver.add(sum(row) == 1) # 约束1:一行只有一个皇后
    for col in board:
        # print(col)
        solver.add(sum(col) == 1) # 约束2: 一列只有一个皇后
    i = 0

    dia = []
    anti_dia = []
   # 对角线元素放到dia数组里面
    for row in board:
        j = 0
        for pos in row:
            if i == j:
                dia.append(pos)
            j = j + 1
        i = i + 1
    solver.add(sum(dia) <= 1)    # 约束3:对角线最多只有一个皇后
    # print(dia)
    # 反对角线元素放到anti_dia数组里面
    i = 0
    for row in board:
        j = 0
        for pos in row:
            if i + j == n-1 :
               anti_dia.append(pos)
            j = j + 1
        i = i + 1
    # print(anti_dia)
    solver.add(sum(anti_dia) <= 1)  # 约束4:反对角线最多只有一个皇后
    # count the number of solutions
    solution_count = 0

    start = time.time()
    while solver.check() == sat:
        solution_count += 1
        model = solver.model()

        if verbose:
            # print the solution
            print([(row_index, col_index) for row_index, row in enumerate(board)
                   for col_index, flag in enumerate(row) if model[flag] == 1])

        # generate constraints from solution
        solution_cons = [(flag == 1) for row in board for flag in row if model[flag] == 1]

        # add solution to the solver to get new solution
        solver.add(Not(And(solution_cons)))

    print(f"n_queen_la solve {board_size}-queens by {(time.time() - start):.6f}s")
    return solution_count

另一种解决N -queen问题的方法是使用回溯算法,但复杂度相对于棋盘大小N是指数级的。

Exercise 12:queen.py Python文件中的代码,在n_queen_bt()方法中有一个基于回溯的解决方案。尝试比较回溯算法和LA算法,通过改变棋盘大小N的值为其他值,哪一个更快?从结果中你能得出什么结论?

#回溯法解决N皇后问题
def n_queen_bt(board_size: int, verbose: bool = False) -> int:
    n = board_size
    solutions = [[]]

    def is_safe(col, solution):
        same_col = col in solution
        same_diag = any(abs(col - j) == (len(solution) - i) for i, j in enumerate(solution))

        return not (same_col or same_diag)

    start = time.time()
    for row in range(n):
        solutions = [solution + [col] for solution in solutions for col in range(n) if is_safe(col, solution)]
    print(f"n_queen_bt solve {board_size}-queens by {(time.time() - start):.6f}s")

    if verbose:
        # print the solutions
        for solution in solutions:
            print(list(enumerate(solution)))

    return len(solutions)

 

上述LA实现并不是求解n-queen问题的唯一算法。事实上,我们建立约束来描述问题的方式往往对算法的效率有很大的影响。

Exercise 13: 阅读queen.py Python文件中n_queen_la_opt()方法的代码。试着将此方法的效率与练习11中的实现进行比较。你的观察是什么?你能得出什么结论?

# LA优化算法解决N皇后问题
def n_queen_la_opt(board_size: int, verbose: bool = False) -> int:
    solver = Solver()
    n = board_size

    # We know each queen must be in a different row.
    # So, we represent each queen by a single integer: the column position
    # the q_i = j means queen in the row i and column j.
    queens = [Int(f"q_{i}") for i in range(n)]

    # each queen is in a column {0, ... 7 }
    solver.add([And(0 <= queens[i], queens[i] < n) for i in range(n)])

    # one queen per column
    solver.add([Distinct(queens)])

    # at most one for per anti-diagonal & diagonal
    solver.add([If(i == j, True, And(queens[i] - queens[j] != i - j, queens[i] - queens[j] != j - i))
                for i in range(n) for j in range(i)])

    # count the number of solutions
    solution_count = 0
    start = time.time()

    while solver.check() == sat:
        solution_count += 1
        model = solver.model()

        if verbose:
            # print the solutions
            print([(index, model[queen]) for index, queen in enumerate(queens)])

        # generate constraints from solution
        solution_cons = [(queen == model[queen]) for queen in queens]

        # add solution to the solver to get new solution
        solver.add(Not(And(solution_cons)))

    print(f"n_queen_la_opt solve {board_size}-queens by {(time.time() - start):.6f}s")

    return solution_count

 N = 4时,比较运行时间:

N = 5 时,比较运行时间:

结论:

三种算法解决N皇后问题效率的比较: 用回溯法最快、LA优化算法其次、LA算法最慢

 

#中科大软院-hbj形式化课程笔记-欢迎留言与私信交流

#随手点赞,我会更开心~~^_^

 

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值