N皇后问题的两种求解方法:回溯法、约束规划

(一)回溯法

回溯法是求解 N 皇后问题的经典算法,详细可见 Leetcode 第 51 题,及其简化版第 52 题。

本文提供一种最经典的回溯法的 Python 代码。

from datetime import datetime


class NQueensBacktrack(object):
    """
    N queens problem, backtracking
    """

    def __init__(self, size: int):
        """
        backtracking, initialisation

        :param size: size of the chessboard, 'N'
        """

        self.size = size

        """ result """

        # final result
        self.list_result = []

        # row state: columns index of queens in each row
        self.list_state = [-1 for _ in range(self.size)]

    def run(self):
        """
        backtracking, run

        :return: nothing
        """

        dt_start = datetime.now()

        self._run_recursion(row=0)

        # print all the solutions
        for s in self.list_result:
            print(s)
        print()
        print("Number of solutions found:  {}".format(len(self.list_result)), '\n')

        dt_end = datetime.now()
        tm_run = round((dt_end - dt_start).seconds + (dt_end - dt_start).microseconds / (10 ** 6), 3)
        print("backtrack total time:  {} s".format(tm_run), '\n')

    def _run_recursion(self, row: int):
        """
        backtracking, recursion

        :param row:  current row index

        :return: nothing
        """

        # end condition
        if row == self.size:
            list_solution = []

            for i in range(self.size):
                list_row = []
                for j in range(self.size):
                    if j == self.list_state[i]:
                        list_row.append(1)
                    else:
                        list_row.append(0)
                list_solution.append(list_row)

            self.list_result.append(list_solution)
            return

        # recursion body
        for column in range(self.size):
            if self._is_valid(row=row, column=column):
                self.list_state[row] = column
                self._run_recursion(row=row + 1)
                self.list_state[row] = -1

    def _is_valid(self, row: int, column: int) -> bool:
        """
        judge valid for a cell

        :param row:  row index of current cell
        :param column:  column index of current cell

        :return:  if current cell is valid
        """

        for i in range(row):
            if (self.list_state[i] == column) or (abs(row - i) == abs(column - self.list_state[i])):
                return False

        return True


if __name__ == '__main__':
    N = 8
    print('\n')

    n_queens_backtrack = NQueensBacktrack(size=N)
    n_queens_backtrack.run()

上述代码采用的是递归的实现方式,对应的也可以通过非递归的方式实现,此处不赘述。

此外,还有一种位运算的算法较为高效,但笔者在答题时发现,位运算算法似乎比上述算法稍微慢一些,不确定经验是否准确,有关位运算算法的代码可见 Leetcode 上的答案。

(二)约束规划

N皇后问题可以看作一个整数规划问题,但如果没有优化目标,只是为了获取所有可行解,则称之为约束规划。

OR-Tools CP-SAT 求解器提供了约束规划的求解方法。本文提供调用该求解器,获取N皇后问题的所有可行解的 Python 代码。

from datetime import datetime
from typing import List

from ortools.sat.python import cp_model


class VarMatrixSolutionPrinter(cp_model.CpSolverSolutionCallback):
    """
    print intermediate solutions, and get total solution count
    """

    def __init__(self, variables: List[List[cp_model.IntVar]], size: int):
        """
        initialisation

        :param variables:  variable matrix
        :param size:  size of the matrix
        """

        cp_model.CpSolverSolutionCallback.__init__(self)

        self.variables = variables
        self.size = size

        # total solution count
        self.solution_count = 0

    def on_solution_callback(self):
        """
        print intermediate solutions, and update total solution count

        :return: nothing
        """

        self.solution_count += 1

        # print intermediate solutions
        for i in range(self.size):
            for j in range(self.size):
                print('%s=%i' % (self.variables[i][j], self.Value(self.variables[i][j])), end=' ')
        print()


class NQueensCPModel(object):
    """
    N queens problem, solving by CP model
    """

    def __init__(self, size: int):
        """
        CP model, initialisation

        :param size:  size of the chessboard, 'N'
        """

        self.size = size

    def run(self):
        """
        CP model, run

        :return: nothing
        """

        dt_start = datetime.now()
        model = cp_model.CpModel()

        x = [[model.NewBoolVar(name='x_[{},{}]'.format(i, j)) for j in range(self.size)] for i in range(self.size)]

        """ constraints """

        # cons 1: number of queens
        model.Add(sum(x[i][j] for j in range(self.size) for i in range(self.size)) == self.size)

        # cons 2: no row conflicts
        for i in range(self.size):
            model.Add(sum(x[i][j] for j in range(self.size)) <= 1)

        # cons 3: no column conflicts
        for j in range(self.size):
            model.Add(sum(x[i][j] for i in range(self.size)) <= 1)

        # cons 4: no diagonal conflicts, left bottom to right top diagonals
        for s in range(0, 2 * (self.size - 1) + 1):
            model.Add(sum(x[i][j] if i + j == s else 0 for i in range(self.size) for j in range(self.size)) <= 1)

        # cons 5: no diagonal conflicts, left top to right bottom diagonals
        for d in range(self.size - 1, - (self.size - 1) - 1, -1):
            model.Add(sum(x[i][j] if i - j == d else 0 for i in range(self.size) for j in range(self.size)) <= 1)

        """ solutions """

        solver = cp_model.CpSolver()
        solution_printer = VarMatrixSolutionPrinter(variables=x, size=self.size)
        status = solver.SearchForAllSolutions(model, solution_printer)
        print()
        print('Status = %s' % solver.StatusName(status))
        print()
        print('Number of solutions found: %i' % solution_printer.solution_count, '\n')

        dt_end = datetime.now()
        tm_model = round((dt_end - dt_start).seconds + (dt_end - dt_start).microseconds / (10 ** 6), 3)
        print("CP model total time:  {} s".format(tm_model), '\n')


if __name__ == '__main__':
    N = 8
    print('\n')

    n_queens_cp_model = NQueensCPModel(size=N)
    n_queens_cp_model.run()

最后简述一下求解效率,笔者在 i5-6200 2.30GHz 2.40GHz CPU 和 8GB 内存的笔记本电脑上运行上述两个程序,回溯法的运行时间大概是 0.03s,而约束规划模型的运行时间大概是 0.3s,慢了约十倍,不过约束规划的思想还是蛮有意义的。

相关资料:

OR-Tools Constraint Optimization:

https://developers.google.cn/optimization/cp

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值