首页 > 编程语言 >算法分析-回溯算法-求解N皇后问题

算法分析-回溯算法-求解N皇后问题

时间:2023-12-28 15:45:53浏览次数:46  
标签:求解 self range 算法 board 回溯 皇后 col row

一.题目需求

n皇后问题是一道比较经典的算法题。它研究的是将n个皇后放置在一个n×n的棋盘上,使皇后彼此之间不相互攻击。

即任意两个皇后都不能处于同一行、同一列或同一斜线上。

二.算法思想

1.构建棋盘

可以用一个n×n列表来表示棋盘,设皇后所在的位置为board[i],i代表行,board[i]代表列,因此皇后所处的位置就是第i行、第board [i]列。

如下,第一个皇后就处于[0,0]位置(以0为起点,[0,0]意为第一行第一列),第二个皇后就处于[2,3]位置(意为第三行第四列):

 

2.不攻击检查

即需要判断:
1)是否处于同一列中
2)是否在左斜线上:(行 + 列)的值不可相等
3)是否在右斜线上:(列 - 行)的值不可相等
这里,每行肯定只有1个皇后,是很显然的,因此不必特别判断,
左右斜线的判断可以用一个绝对值公式abs(board[i] - col) == abs(i - row)判断,这样就不需要写两个公式。

    # 校验是否有效
    def is_valid(board, row, col):
        for i in range(row):
            if board[i] == col or abs(board[i] - col) == abs(i - row):
                return False
        return True

3.DFS搜索,回溯算法
1)结束条件:当前行数 = 皇后总数,即最后一行已经成功放入皇后
2)循环一行中的每一个位置,若不发生攻击事件,可将皇后放入该位置
3)继续下一行的搜索,即传入的参数为当前行数 + 1

    # DFS搜索,回溯算法
    def backtrack(board, row):
        # 探索行号等于n时结束
        if row == n:
            result.append(board[:])
            return
        # 根据当前行号,再遍历每一列位置
        for col in range(n):
            # 检测当前行号,列号是否有效
            if is_valid(board, row, col):
                # 有效则设置该位置为皇后
                board[row] = col
                # 探索下一行,每次探索一行,放置1个皇后
                backtrack(board, row + 1)

4.算法分析

这个算法的时间复杂度是O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。

.编程实现

根据网上搜集学到的实现代码,多数都采用一维数组方式实现,每次探索每行的每一列,代码更简洁。

实现方法一:

class SolutionNQueens(object):
    '''
    回溯算法-一维数组解决N皇后问题。
    该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
    '''

    def __init__(self, num):
        self.count = 0
        self.num = num

    # 校验当前行号,列号是否有效
    def is_valid(self, board, row, col):
        # 遍历行号
        for i in range(row):
            if board[i] == col or abs(board[i] - col) == abs(i - row):
                return False
        return True

    def backtrack(self, board, row, result):
        if row == self.num:
            result.append(board[:])
            self.count += 1
            return
        for col in range(self.num):
            if self.is_valid(board, row, col):
                board[row] = col
                self.backtrack(board, row + 1, result)
                board[row] = 0

    def backtrack_result(self):
        result = []
        # 最终皇后的位置 (下标:第几行 数值:第几列)
        board = [0] * self.num
        # 从第一行开始
        row = 0
        self.backtrack(board, row, result)
        return result

同样采用一维数组方式实现,优化减少部分无效列号的遍历,每次探索部分列即可,耗时减少很多。

实现方法二:

class SolutionNQueensNew(object):
    '''
    回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历。
    该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
    '''

    def __init__(self, num):
        self.count = 0
        self.num = num

    # 校验当前行号,列号是否有效
    def is_valid(self, board, row, col):
        # 遍历行号
        for i in range(row):
            if board[i] == col or abs(board[i] - col) == abs(i - row):
                return False
        return True

    def backtrack(self, board, row, range_col, result):
        if row == self.num:
            result.append(board[:])
            self.count += 1
            return
        # 根据当前行号,再遍历列号表中的列号
        for col in range_col:
            if self.is_valid(board, row, col):
                # 有效则设置该位为 皇后
                board[row] = col
                # 列号表中删除该皇后位的列号,减少无效遍历次数
                range_col.remove(col)
                # 探索下一行,每次探索一行,放置1个皇后
                self.backtrack(board, row + 1, range_col, result)
                # 探索失败,回溯,还原该位置为 0-空位
                board[row] = 0
                # 还原列号表,列表尾部添加元素
                range_col.append(col)
                # sort 增序排序
                range_col.sort()

    def backtrack_result(self):
        result = []
        # 最终皇后的位置 (下标:第几行 数值:第几列)
        board = [0] * self.num
        # 从第一行开始
        row = 0
        # 列号表初始化,每一列都探索
        range_col = [i for i in range(self.num)]
        self.backtrack(board, row, range_col, result)
        return result

采用二维数组方式实现,每次探索每行每列,代码稍微复杂点,检测是否有效方法也不同。

实现方法三:

def solve_n_queens(n):
    '''
    回溯算法-二维数组解决N皇后问题
    该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
    '''

    def is_valid(board, row, col):
        '''
        board(一个二维列表,表示棋盘),
        row(一个整数,表示要检查的行索引),
        col(一个整数,表示要检查的列索引)。
        函数的目的是检查在给定的行和列上放置一个皇后是否有效。
        '''

        '''
        函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
        如果有,函数返回False,表示放置皇后无效。
        '''
        for i in range(row):
            if board[i][col] == 1:
                return False
        '''
        zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
        '''
        for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
            if board[i][j] == 1:
                return False
        '''
        zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
        '''
        for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
            if board[i][j] == 1:
                return False
        return True

    def backtrack(board, row):
        # 探索行号等于N时结束
        if row == n:
            # 将棋盘可行方案数据添加到结果列表中
            result.append([[board[i][j] for j in range(n)] for i in range(n)])return
        # 根据当前行号,再遍历列号
        for col in range(n):# 检测当前行号,列号是否有效
            if is_valid(board, row, col):
                # 有效则设置该方格为 1-皇后
                board[row][col] = 1
                # 探索下一行,每次探索一行,放置1个皇后
                backtrack(board, row + 1)
                # 探索失败,回溯,还原该方格为 0-空位
                board[row][col] = 0

    # 返回结果列表
    result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
    board = [[0] * n for _ in range(n)]
    # 回溯算法,从第1行开始探索
    backtrack(board, 0)
    return result

采用二维数组方式实现,优化减少部分无效列号的遍历,每次探索部分列即可,耗时减少很多。

实现方法四:

def solve_n_queens_new(n):
    '''
    回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历。
    该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
    '''

    def is_valid(board, row, col):
        '''
        board(一个二维列表,表示棋盘),
        row(一个整数,表示要检查的行索引),
        col(一个整数,表示要检查的列索引)。
        函数的目的是检查在给定的行和列上放置一个皇后是否有效。
        '''

        '''
        zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
        '''
        for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
            if board[i][j] == 1:
                return False
        '''
        zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
        '''
        for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
            if board[i][j] == 1:
                return False

        '''
        函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
        如果有,函数返回False,表示放置皇后无效。
        '''
        for i in range(row):
            if board[i][col] == 1:
                return False
        return True

    def backtrack(board, row, range_col):
        # 探索行号等于N时结束
        if row == n:
            # 将棋盘可行方案数据添加到结果列表中
            result.append([[board[i][j] for j in range(n)] for i in range(n)])return
        # 根据当前行号,再遍历列号表中的列号
        for col in range_col:# 检测当前行号,列号是否有效
            if is_valid(board, row, col):
                # 有效则设置该方格为 1-皇后
                board[row][col] = 1
                # 列号表中删除该皇后位的列号,减少无效遍历次数
                range_col.remove(col)
                # 探索下一行,每次探索一行,放置1个皇后
                backtrack(board, row + 1, range_col)
                # 探索失败,回溯,还原该方格为 0-空位
                board[row][col] = 0
                # 还原列号表,列表尾部添加元素
                range_col.append(col)
                # sort 增序排序
                range_col.sort()

    # 返回结果列表
    result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
    board = [[0] * n for _ in range(n)]
    # 列号表初始化,每一列都探索
    range_col = [i for i in range(n)]
    # 回溯算法,从第1行开始探索
    backtrack(board, 0, range_col)
    return result

.运行结果

1,4种方法测试对比下耗时。

经过部分优化,减少已排放皇后位对应列号探测,明显可以减少整体耗时。

 

if __name__ == '__main__':

    nums = 10
    all_dis_time = 0.0
    # 循环10次,求平均值
    for i in range(nums):
        start_time = time.time()
        ###############################
        # num: 皇后的数量
        n = 10
        '''
            回溯算法-一维数组解决N皇后问题
            皇后的数量 = 10
            可行方案数: 724
            平均时间:180.8545毫秒
        '''
        # s = SolutionNQueens(n)
        '''
            回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历.
            皇后的数量 = 10
            可行方案数: 724
            平均时间:78.5564毫秒
        '''
        s = SolutionNQueensNew(n)
        # 参数:皇后总数  位置结果  当前放置第几行
        solutions = s.backtrack_result()
        print('可行方案数:', s.count)
        # 打印皇后在棋盘位置
        # for solution in solutions:
        #     print('======================')
        #     for row in solution:
        #         print(" ▢ " * row + " Q " + " ▢ " * (n - row - 1))
        # print('======================')
        '''
        回溯算法-二维数组解决N皇后问题
        皇后的数量 = 10
        可行方案数: 724
        平均时间:199.6063毫秒
        '''
        # grid_board = solve_n_queens(n)
        '''
        回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历.
        皇后的数量 = 10
        可行方案数: 724
        平均时间:117.3587毫秒
        '''
        # grid_board = solve_n_queens_new(n)
        # rst_nums = len(grid_board)
        # print("可行方案数:", rst_nums)

        # for i in range(rst_nums):
        #     print("方案:", (i + 1))
        #     # 打印网格地图
        #     grid_print(grid_board[i])
        ###############################
        # 识别时间
        end_time = time.time()
        # 计算耗时差,单位毫秒
        dis_time = (end_time - start_time) * 1000
        # 保留2位小数
        dis_time = round(dis_time, 4)
        all_dis_time += dis_time
        print('时间:' + str(dis_time) + '毫秒')
        print('=============================')
    pre_dis_time = all_dis_time / nums
    # 保留4位小数
    pre_dis_time = round(pre_dis_time, 4)
    print('平均时间:' + str(pre_dis_time) + '毫秒')

 

2,动态演示求解4皇后问题完整过程。

 =====================end =====================

 

标签:求解,self,range,算法,board,回溯,皇后,col,row
From: https://www.cnblogs.com/xh2023/p/17932844.html

相关文章

  • 《算法笔记》学习记录
    算法笔记散列字符串散列//把字符串当成26进制数,转换成10进制,建立映射关系inthash(charS[],intlen){intres=0;for(inti=0;i<len;++i){res=res*26+(S[i]-'A');}returnres;}/**给出n个字符串,每个字符串由三位大......
  • 《生物信息学算法导论》是2007年化学工业出版社出版的图书,作者是(美)N.C.琼斯 ,(美)P.A
    目前,可供本科学生使用的生物信息学著作为数不多,本书恰恰是其中的一本。国内生物信息学,计算生物学、计算数学等领域的本科生、研究生和其他研究人员,会从书中汲取基本的算法原理、解决实际问题的方法和技巧,进而更好地从事相关研究工作。目录 播报编辑1绪论2算法与复杂性......
  • 大模型 RLHF 实战!【OpenAI独家绝技RLHF!RLHF的替代算法DPO!Claude 暗黑科技 RAIHF!】
    大模型RLHF实战大模型RLHF实战RLHF:OpenAI独家绝技RLHF的问题DPO直接偏好优化算法:RLHF的替代算法公式1-4:KL散度下奖励的最大化目标使用DPO微调Llama2RAIHF 大模型RLHF实战RLHF(基于人类反馈的强化学习)分为3个阶段:预训练:为了生成内容,需要一个生成式的预训练语言模......
  • 文心一言 VS 讯飞星火 VS chatgpt (158)-- 算法导论12.3 5题
    五、用go语言,假设为每个结点换一种设计,属性x.p指向x的双亲,属性x.succ指向x的后继。试给出使用这种表示法的二叉搜索树T上SEARCH、INSERT和DELETE操作的伪代码。这些伪代码应在O(h)时间内执行完,其中h为树T的高度。(提示:应该设计一个返回某个结点的双亲的子过程。......
  • day01 代码随想录算法训练营 704. 二分查找
    题目:leetcode704.二分查找 感悟:困扰我多年的二分查找对于边界的判断,我终于理解了。难点:难点1:定边界rightright=len(nums)还是len(nums)-1 难点2:while循环whileleft<right还是left<=right 难点3:mid取值mid=right-1还是mid=right  结论:1.自己确定......
  • class084 数位dp-上【算法】
    class084数位dp-上【算法】算法讲解084【必备】数位dp-上code1357.统计各位数字都不同的数字个数//统计各位数字都不同的数字个数//给你一个整数n,代表十进制数字最多有n位//如果某个数字,每一位都不同,那么这个数字叫做有效数字//返回有效数字的个数,不统计负数范围//测试......
  • 笔尖笔帽检测2:Pytorch实现笔尖笔帽检测算法(含训练代码和数据集)
    笔尖笔帽检测2:Pytorch实现笔尖笔帽检测算法(含训练代码和数据集)目录笔尖笔帽检测2:Pytorch实现笔尖笔帽检测算法(含训练代码和数据集)1.前言2.笔尖笔帽检测方法(1)Top-Down(自上而下)方法(2)Bottom-Up(自下而上)方法:3.笔尖笔帽检测数据集4.手笔检测模型训练5.笔尖笔帽检测模型训练......
  • TF-IDF算法是什么呢?
    TF-IDF(TermFrequency-InverseDocumentFrequency)是一种常用于信息检索和文本挖掘的统计方法,用于评估一个词在文档集或一个语料库中的重要程度。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数......
  • 文心一言 VS 讯飞星火 VS chatgpt (166)-- 算法导论13.1 6题
    六、用go语言,在一棵黑高为k的红黑树中,内部结点最多可能有多少个?最少可能有多少个?文心一言:红黑树是一种自平衡的二叉查找树,其中每个节点都有一个颜色属性,可以是红色或黑色。红黑树满足以下性质:每个节点或者是红色,或者是黑色。根节点是黑色。每个叶节点(NIL或空节点)是黑色。......
  • 限速器算法
    限速器限速器类型LeakyBucket:漏桶算法(和令牌桶(tokenbucket)非常相似)是一种非常简单,使用队列来进行限流的算法。当接收到一个请求时,会将其追加到队列的末尾,系统会按照先进先出的顺序处理请求,一旦队列满,则会丢弃额外的请求。队列中的请求数目受限于队列的大小。这种方式......