首页 > 其他分享 >回溯法、分支限界法解决N皇后问题

回溯法、分支限界法解决N皇后问题

时间:2024-11-12 22:45:38浏览次数:3  
标签:False 对角线 perm 放置 回溯 皇后 col 限界 分支

本期是针对解决 N 皇后问题的回溯法和分支限界法两块代码,分析了其解的形式(二维列表,元素取值 0 或 1 表示有无皇后)、解的空间结构(排列树,各层对应放置皇后决策,根节点初始态,树高为 N,路径对应解)及搜索条件(放置皇后合法性、回溯控制或分支选择条件、解完成判断条件等方面各自特点)。 

一、解的形式及各分量取值

解的形式

两种方法得到的 N 皇后问题的解的形式均为一个二维列表(矩阵),用于表示棋盘的状态。该二维列表的每一行对应棋盘的一行,每一列对应棋盘的一列。

各分量取值

在这个二维列表中,每个元素的取值只有两种情况:

  • 0:表示对应位置的棋盘方格上没有放置皇后,即该位置为空。
  • 1:表示在对应位置的棋盘方格上放置了皇后。

二、解的空间结构(排列树)

排列树的构成
  • 对于 N 皇后问题,解空间的排列树结构中:
    • 树的每一层对应着在棋盘上放置一个皇后的决策过程。
    • 根节点表示还未开始放置任何皇后的初始状态。此时,对于回溯法代码中的 backtrack 函数,传入的 perm 为空列表;对于分支限界法代码,初始压入优先队列的状态中皇后放置排列 perm 也是空列表。
    • 树的高度为 N,因为总共需要放置 N 个皇后,每放置一个皇后就向下一层延伸,所以到放置完 N 个皇后时到达叶节点,即完成了一个完整的解的探索。
    • 在每一层,节点的分支数取决于当前行可选择放置皇后的列位置数量以及之前放置皇后所限制的条件。例如,在第一层(放置第一个皇后),理论上有 N 种可能的列位置可供选择,所以根节点有 N 个分支;但随着皇后逐渐被放置,后续层可选择的列位置会因要满足皇后之间不能相互攻击的规则而减少,分支数也会相应减少。
节点与解的对应关系
  • 从根节点到叶节点的一条完整路径对应着一个完整的解。在这条路径上,每个节点所做的选择(即选择在哪一列放置皇后)组合起来就确定了整个棋盘上皇后的放置方案,也就是一个满足条件的解。具体来说:
    • 在回溯法中,backtrack 函数通过不断递归,每次选择一个合适的列添加到 perm 列表中,当 perm 列表的长度达到 N 时,就根据这个 perm 构建出完整的棋盘状态作为一个解。
    • 在分支限界法中,优先队列中的每个状态包含了当前的皇后放置排列 perm,当从优先队列中取出的状态其 perm 长度达到 N 时,同样根据这个 perm 构建出完整的棋盘状态作为一个解。

三、搜索条件

回溯法的搜索条件
放置皇后的合法性条件
  • 通过 is_safe 函数来判断在某个位置放置皇后是否合法。具体检查以下几个方面:
    • 列冲突检查:在 is_safe 函数中,对于回溯法的版本,会遍历整个棋盘的行(for i in range(n)),检查在同一列是否已经放置了皇后。若已经有皇后在同一列,那么在当前位置放置皇后就是不合法的,函数返回 False
    • 对角线冲突检查
      • 左上对角线冲突检查:从当前位置沿着左上对角线方向(行减 1,列减 1)进行遍历(while i >= 0 and j >= 0),检查是否有皇后已经放置在这条对角线上。若有,返回 False
      • 右上对角线冲突检查:从当前位置沿着右上对角线方向(行减 1,列加 1)进行遍历(while i >= 0 and j < n),检查是否有皇后已经有皇后放置在这条对角线上。若有,返回 False
    • 只有当这三个方面(列冲突、左上对角线冲突、右上对角线冲突)都没有冲突时,在当前位置放置皇后才被认为是合法的,is_safe 函数才会返回 True
回溯搜索的控制条件
  • 在 solve_n_queens 函数中的 backtrack 函数里:
    • 当 len(perm) 等于 N 时,说明已经成功在棋盘上放置了 N 个皇后,此时找到了一个完整的解,会将根据当前 perm 构建出的棋盘状态添加到 solutions 列表中,然后返回,不再继续向下一层递归。
    • 在每一层递归调用 backtrack 时,会遍历所有可能的列位置(for col in range(n)),但只有当该列位置 col 不在当前的排列 perm 中(即还没有在这一列放置过皇后)并且在该位置放置皇后是安全的(is_safe(perm, len(perm), col))时,才会继续向下一层递归调用 backtrack,继续放置下一个皇后。否则,会跳过该列位置,尝试其他可能的列位置。
分支限界法的搜索条件
放置皇后的合法性条件
  • 同样是通过 is_safe 函数来判断在某个位置放置皇后是否合法,其检查内容与回溯法中的 is_safe 函数基本一致,包括:
    • 列冲突检查:在 is_safe 函数中,对于分支限界法的版本,会遍历当前行之前的所有行(for i in range(row)),检查在同一列是否已经放置了皇后。若已经有皇后在同一列,那么在当前位置放置皇后就是不合法的,函数返回 False
    • 对角线冲突检查
      • 左上对角线冲突检查:从当前位置沿着左上对角线方向(行减 1,将 i 减 1,列减 1)进行遍历(while i >= 0 and j >= 0),检查是否有皇后已经放置在这条对角线上。若有,返回 False
      • 右上对角线冲突检查:从当前位置沿着右上对角线方向(行减 1,列加 1)进行遍历(while i >= 0 and j < n),检查是否有皇后已经放置在这条对角线上。若有,此句应改为若有皇后,返回 False。同样也是只有当这三个方面(列冲突、左上对角线冲突、右上对角线冲突)都没有冲突时,在当前位置放置皇后才被认为是合法的,is_safe 函数才会返回 True
分支选择条件(基于启发式值)
  • 在分支限界法中,通过 calculate_heuristic 函数来计算每个可能分支的启发式值。该函数根据当前的皇后放置排列 perm,先构建出一个完整的棋盘状态,然后计算出后续放置皇后可能产生的冲突数量作为启发式值。
  • 启发式值越小,说明在当前排列基础上继续放置皇后产生冲突的可能性越小,也就越有可能找到一个可行解。
  • 在搜索过程中,通过优先队列(heapq)来管理各个分支状态。每次从优先队列中取出具有最小启发式值的状态进行进一步探索。这意味着优先选择那些看起来更有希望找到可行解的分支进行处理,而不是盲目地遍历所有可能的分支。
解的完成判断条件
  • 在 solve_n_queens_branch_and_bound 函数中,当取出的排列长度等于 N 时,说明已经成功在棋盘上放置了 N 个皇后,此时找到了一个完整的解,便可以根据该排列构建出完整的棋盘状态并返回。

综上所述,两种方法虽然在搜索策略上有所不同(回溯法是按深度优先搜索的方式递归探索解空间,分支限界法是基于启发式值通过优先队列选择更有希望的分支进行探索),但都是围绕着如何在满足皇后放置规则的前提下,通过合理的搜索条件在排列树结构的解空间中寻找 N 皇后问题的所有可行解。

代码案例 

# 以下是使用回溯法解决N皇后问题的代码

# 判断在给定棋盘状态下,在指定位置放置皇后是否安全
def is_safe(board, row, col):
    n = len(board)
    # 检查列是否有冲突
    for i in range(n):
        # 如果在当前列的其他行已经有皇后(值为1),则返回False,表示不安全
        if board[i][col] == 1:
            return False
    # 检查左上对角线是否有冲突
    i = row - 1
    j = col - 1
    while i >= 0 and j >= 0:
        # 如果在左上对角线上已经有皇后(值为1),则返回False,表示不安全
        if board[i][j] == 1:
            return False
        i -= 1
        j -= 1
    # 检查右上对角线是否有冲突
    i = row - 1
    j = col + 1
    while i >= 0 and j < n:
        # 如果在右上对角线上已经有皇后(值为1),则返回False,表示不安全
        if board[i][j] == 1:
            return False
        i -= 1
        j += 1
    return True

# 解决N皇后问题的主函数
def solve_n_queens(n):
    # 定义回溯函数,用于递归地尝试放置皇后
    def backtrack(perm):
        # 如果已经放置了n个皇后,说明找到了一个解
        if len(perm) == n:
            # 创建一个初始值全为0的n x n棋盘
            solution = [[0] * n for _ in range(n)]
            # 根据当前皇后的放置位置(perm),在棋盘对应位置设置为1
            for i, col in enumerate(perm):
                solution[i][col] = 1
            # 将找到的解添加到解列表solutions中
            solutions.append(solution)
            return

        # 遍历每一列,尝试放置皇后
        for col in range(n):
            # 如果当前列不在已经放置皇后的列列表(perm)中,并且在该位置放置皇后是安全的
            if col not in perm and is_safe(perm, len(perm), col):
                # 将当前列添加到已放置皇后的列列表中,继续递归放置下一个皇后
                backtrack(perm + [col])

    # 用于存储所有解的列表
    solutions = []
    # 从空的皇后放置列表开始回溯
    backtrack([])
    return solutions

n = 4
# 调用solve_n_queens函数解决4皇后问题,并获取解列表
solutions = solve_n_queens(n)
for solution in solutions:
    for row in solution:
        # 将每行中的0替换为'.',1替换为'Q',并以空格连接后打印
        print(' '.join(['Q' if cell == 1 else '.' for cell in row]))
    print()
# 以下是使用分支限界法解决N皇后问题的代码

# 判断在给定棋盘状态下,在指定位置放置皇后是否安全
import heapq


def is_safe(board, row, col):
    n = len(board)
    # 检查列是否有冲突
    for i in range(row):
        # 如果在当前列的前面行已经有皇后(值为1),则返回False,表示不安全
        if board[i][col] == 1:
            return False
    # 检查左上对角线是否有冲突
    i = row - 1
    j = col - 1
    while i >= 0 and j >= 0:
        # 如果在左上对角线上已经有皇后(值为1),则返回False,表示不安全
        if board[i][j] == 1:
            return False
        i -= 1
        j -= 1
    # 检查右上对角线是否有冲突
    i = row - 1
    j = col + 1
    while i >= 0 and j < len(board):
        # 如果在右上对角线上已经有皇后(值为1),则返回False,表示不安全
        if board[i][j] == 1:
            return False
        i -= 1
        j += 1
    return True


# 计算给定皇后放置排列(perm)对应的启发式值(即后续放置皇后可能产生的冲突数量)
def calculate_heuristic(perm, n):
    # 根据当前皇后放置排列创建一个初始值全为0的n x n棋盘
    board = [[0] * n for _ in range(n)]
    for i, col in enumerate(perm):
        board[i][col] = 1

    conflicts = 0
    for i in range(len(perm), n):
        for j in range(n):
            # 如果在后续行的某个位置放置皇后不安全(通过is_safe判断),则增加冲突计数
            if not is_safe(board, i, j):
                conflicts += 1
    return conflicts


# 使用分支限界法解决N皇后问题的主函数
def solve_n_queens_branch_and_bound(n):
    # 创建一个优先队列,用于存储待处理的状态
    priority_queue = []
    # 将初始状态(启发式值为0,空的皇后放置排列,皇后数量n)压入优先队列
    heapq.heappush(priority_queue, (0, [], n))
    while priority_queue:
        # 从优先队列中取出具有最小启发式值的状态
        _, perm, remaining_n = heapq.heappop(priority_queue)
        # 如果已经放置了n个皇后,说明找到了一个解
        if len(perm) == n:
            # 创建一个初始值全为0的n x n棋盘
            solution = [[0] * n for _ in range(n)]
            for i, col in enumerate(perm):
                solution[i][col] = 1
            return solution

        # 遍历每一列,尝试放置皇后
        for col in range(n):
            # 如果当前列不在已经放置皇后的列列表(perm)中,并且在该位置放置皇后是安全的
            if col not在perm and is_safe(perm, len(perm), com):
                # 创建一个新的皇后放置排列,将当前列添加进去
                new_perm = perm + [col]
                # 计算新排列对应的启发式值
                heuristic = calculate_heuristic(new_perm, n)
                # 将新状态(启发式值、新排列、剩余皇后数量减1)压入优先队列
                heapq.heappush(priority_queue, (heuristic, new_perm, remaining_n - 1))
    return None


n = 4
# 调用solve_n_queens_branch_and_bound函数解决4皇后问题,并获取解
solution = solve_n_queens_branch_and_bound(n)
if solution:
    for row in solution:
        # 将每行中的0替换为'.',1替换为'Q',并以空格连接后打印
        print(' '.join(['Q' if cell == 1 else '.' for cell in row]))
else:
    print("No solution found.")

 

 

标签:False,对角线,perm,放置,回溯,皇后,col,限界,分支
From: https://blog.csdn.net/2301_80050457/article/details/143725457

相关文章

  • 分支语句(if ,switch)
    分支语句ifswitch1.什么是控制语句?控制语句:用于控制程序的执行流程,以实现程序的各种结构方式,它们由特定的语句定义符组成,c语言有九种控制语句。可以分成三类:1.条件判断语句也叫分支语句:if语句、switch语句;2.循环执行语句:dowhile语句、while语句、for语句;3.转向语句:bre......
  • Python:条件分支 if 语句全讲解
    如果我拿出下面的代码,阁下该做何应对?ifnotreset_excutedand(terminatedortruncated):...else:...前言:消化论文代码的时候看到这个东西直接大脑冻结,没想过会在这么基础的东西上犯难运算符优先级在Python中,布尔运算符的优先级从高到低的顺序如下:括号():最高优先级,......
  • 一文搞定回溯算法
    回溯算法基础入门“尝试”与“回退”剪枝全排列问题 子集和问题 n皇后问题 相关题解leetcode104.二叉树的最大深度 法一:深度优先遍历法二:广度优先遍历leetcode113.路径总和Ⅱ法一:深度优先遍历leetcode46.全排列法一:回溯 leetcode47.全排列Ⅱ法一:回溯 l......
  • c++ 回溯算法
    概念回溯算法(Backtracking)是一种用于寻找所有可能解的算法。它通过递归构建解,并在发现当前解不符合条件时进行“回溯”撤销部分选择,直到找到有效的解或没有更多可能性时停止。回溯算法常用于求解组合、排列、子集、图的遍历等问题。基本思想选择:在某个阶段做出一个选择。......
  • 开发分支管理策略
    GitFlow是一种基于Git版本控制系统的分支管理模型,定义了一套严格的分支命名和操作规范主要包括以下几种分支类型:主干分支(master):始终保持稳定,只包含经过充分测试和可发布的代码开发分支(develop):团队成员在该分支上进行日常的开发工作,所有的新功能和特性都先在这个分支上进行......
  • 4.分支和循环(上)
    文章目录引言一、if语句1.1if1.2else1.3分支中包含多条语句1.4if语句的嵌套1.5悬空else问题二、关系操作符三、条件操作符四、逻辑操作符:&&,||,!4.1逻辑取反运算符!4.2逻辑与运算符&&4.3逻辑或运算符4.4练习:闰年的判断4.5短路五、switch语句5.1if语句和switc......
  • 分支语句【if】 pk 【switch】
    一、if语句有三种形式:①if(表达式)   语句inta=5;if(a>0)puts("大于0");② if(表达式)    语句   else    语句  ​ inta=5; if(a>0) puts("大于0"); else puts("小于等于0");​③ if(表达式)        ......
  • GIT我们为什么需要分支
    分支的作用和重要性分支是在一个仓库的不同版本中同时开发的秘诀;使用合适的分支管理策略,能加速您和团队的研发效率;仓库通常有一个默认的master分支,我们从master拉出特性分支来开发新功能,然后再合入master分支。分支基本操作以下命令行需要您在Git客户端执行,不知如何安装Git客户......
  • day03 运算符-分支语句
    今日内容运算符分支语句教学目标能够知道哪些运算中发生了隐式转换能够知道如何对数据进行强转能够使用自增自减运算符并知道在前在后的区别能够使用关系运算符完成数据的比较能够掌握不同逻辑运算符的运算规则能够掌握三元运算符的格式和执行流程能够运用小扩号......
  • 86分支汇编语言-0基础可选择
    在86汇编语言中,分支和循环是常见的控制流结构,主要用于根据条件执行不同的代码段,或者重复执行某段代码。下面我将详细讲解如何在86汇编语言中实现分支和循环。1.分支指令分支指令用于根据条件选择是否跳转到程序的其他部分。常见的分支指令有:1.JMP:无条件跳转。2.JE/JZ:......