【回溯算法篇】N皇后问题

简介: 【回溯算法篇】N皇后问题

👉N皇后II👈


n 皇后问题研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。注:共行、共列或者共斜线的皇后都会相互攻击。


给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。

ad70217da0794123b0ae1bbae52a314d.png


皇后摆放的位置是否同行或者同列是很好判断的,关键是如何判断任意两个皇后是否共斜线。

666a25d0d55b45ffb01551aea2d21713.png

皇后放在不同行,这个是很容易就能够保证的。那么我们就需要保证皇后放在不同行且不共斜线。那么放入皇后的过程就是一个搜索的过程,搜索皇后之间不会相互攻击的位置。那我们需要将已经放入的皇后的所在位置,保存皇后的位置只需要一个整型数组record就行了,record[i] 的值表示第 i 行的皇后放在了第几列。


class Solution 
{
private:
    int total = 0;  // tatol为N皇后的摆法
    bool isValid(const vector<int>& record, int row, int col)
    {
        // 第i行的皇后
        // 判断是否和0行到row-1行的皇后共行、共列或者共斜线
        // 如果是,返回false;如果不是,返回true
        // 条件col == record[i]表示当前row行和i行的皇后共列
        // 两个皇后共斜线时,行标差的绝对值等于列标差的绝对值
        // 条件abs(record[i] - col) == abs(row - i)表示两个皇后共斜线
        for(int i = 0; i < row; ++i)
        {
            if(col == record[i] || abs(record[i] - col) == abs(row - i))
                return false;
        }
        return true;
    }
    void BackTracking(int row, vector<int>& record, int n)
    {
        // 当row等于n时,表示找到了一种摆法
        if(row == n)
        {
            ++total;
            return;
        }
        // 当前在第row行,需要遍历当前行的所有列看是否能放入皇后
        for(int col = 0; col < n; ++col)
        {
            // isValid函数是检查第row行的皇后放在第col列,会不会
            // 和之前0到row-1行的皇后共行、共列或者共斜线
            // 如果是,认为第row的皇后不能放在第col列,继续判断后面的列能不能放皇后
            // 如果不是,认为第row的皇后能放在第col列,递归去放第row+1行的皇后
            if(isValid(record, row, col))
            {
                // record[row]表示第row行的皇后放在了第col列
                record[row] = col;  
                BackTracking(row + 1, record, n);
            }
        }
    }
public:
    int totalNQueens(int n) 
    {
      total = 0; // 防止同一个对象调用多次调用该函数
        vector<int> record(n);
        BackTracking(0, record, n);
        return total;
    }
};

fcacf7470edf4aa19872f818d0a59b7a.png


N 皇后的时间复杂度为 O(N^N)。


上面的解法还是可以进行优化,就是通过位信息来表示皇后的位置。这种优化是比较抽象的,但确实可以提高效率。


八皇后问题为例,来了解通过位信息来判断皇后之间是否会相互攻击。


e031d65e32be468387c178bf997498fd.png

class Solution 
{
private:
    // colLim 列的限制,1的位置不能放皇后,0的位置可以放皇后
    // leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以放皇后
    // rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以放皇后
    int BackTracking(int limit, int colLim, int leftDiaLim, int rightDiaLim)
    {
        // limit的低n位比特位全为1,如果colLim等于limit,
        // 也就说明n个皇后已经摆放好了,且不会相互攻击
        if (colLim == limit)
        {
            return 1;
        }
        // colLim | leftDiaLim | rightDiaLim为总限制,1表示不可以放皇后,0表示可以放皇后
        // ~(colLim | leftDiaLim | rightDiaLim):1表示可以放皇后,0表示不可以放皇后
        // pos中的低n位比特位:1表示该位置可以放皇后,0表示该位置不可以放皇后
        int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
        int mostRightOne = 0;
        int ret = 0;
        while (pos != 0)
        {
            // pos & (~pos + 1)可以将pos最右侧的1提取出来
            mostRightOne = pos & (~pos + 1);
            pos = pos - mostRightOne;   // 减去最右侧的1,尝试在次右侧的1的位置上放皇后
            // colLim | mostRightOne 默认在pos最右侧的1的位置放皇后
            // (leftDiaLim | mostRightOne) << 1:放皇后之后的左斜线的限制
            // 因为要去下一行放皇后了,相当于向左下方移动了,那么(leftDiaLim | mostRightOne) << 1就是下一行放皇后左斜线的限制
            // (rightDiaLim | mostRightOne) >> 1:放皇后之后的右斜线的限制
            // 因为要去下一行放皇后了,相当于向右下方移动了,那么(rightDiaLim | mostRightOne) >> 1就是下一行放皇后右斜线的限制
            ret += BackTracking(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >> 1);
        }
        return ret;
    }
public:
// totalNQueens只能解决1到32个皇后的问题
// 如果想要解决1到64个皇后的问题,可以将limit的类型改成long long
    int totalNQueens(int n)
    {
        if (n < 1 || n > 32)
        {
            return 0;
        }
        // 保证limit的低n位比特位全为1
        int limit = n == 32 ? -1 : (1 << n) - 1;    
        return BackTracking(limit, 0, 0, 0);
    }
};

85f0a98f29e949538086606a80b58841.png


324017407b1245ca9e4bcc13323d916a.png


👉N皇后👈

a39ec28f59e0437fbc33a2f9717a44c4.png


N 皇后问题的关键还是判断皇后之间是否会相互攻击。N 皇后这道题和 N皇后II 的思路都是一样的,只是返回的结果不同而已。


class Solution 
{
private:
    vector<vector<string>> ret;
    bool isValid(const vector<int>& record, int row, int col)
    {
        // 第i行的皇后
        // 判断是否和0行到row-1行的皇后共行、共列或者共斜线
        // 如果是,返回false;如果不是,返回true
        // 条件col == record[i]表示当前row行和i行的皇后共列
        // 两个皇后共斜线时,行标差的绝对值等于列标差的绝对值
        // 条件abs(record[i] - col) == abs(row - i)表示两个皇后共斜线
        for(int i = 0; i < row; ++i)
        {
            if(col == record[i] || abs(record[i] - col) == abs(row - i))
                return false;
        }
        return true;
    }
    void BackTracking(int n, int row, vector<string>& chessBoard, vector<int>& record)
    {
        if(row == n)
        {
            ret.push_back(chessBoard);
            return;
        }
        for(int col = 0; col < n; ++col)
        {
            if(isValid(record, row, col))
            {
                // record[row]表示第row行的皇后放在了第col列
                record[row] = col;  
                chessBoard[row][col] = 'Q'; // 放置皇后
                BackTracking(n, row + 1, chessBoard, record);
                chessBoard[row][col] = '.'; // 回溯,撤销皇后
            }
        }
    }
public:
    vector<vector<string>> solveNQueens(int n) 
    {
        ret.clear();    // 防止用同一个对象多次调用该函数
        vector<int> record(n);
        std::vector<std::string> chessBoard(n, std::string(n, '.'));
        BackTracking(n, 0, chessBoard, record);
        return ret;
    }
};

7870083cecb94b5bb7bdac4f9788241a.png


class Solution 
{
private:
    vector<vector<string>> result;
    // n 为输入的棋盘大小
    // row 是当前递归到棋盘的第几行了
    void backtracking(int n, int row, vector<string>& chessboard) 
    {
        if (row == n) 
        {
            result.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++) 
        {
            if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
                chessboard[row][col] = 'Q'; // 放置皇后
                backtracking(n, row + 1, chessboard);
                chessboard[row][col] = '.'; // 回溯,撤销皇后
            }
        }
    }
    bool isValid(int row, int col, vector<string>& chessboard, int n) 
    {
        // 检查列
        for (int i = 0; i < row; i++) 
        { // 这是一个剪枝
            if (chessboard[i][col] == 'Q') 
            {
                return false;
            }
        }
        // 检查 45度角是否有皇后
        for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) 
        {
            if (chessboard[i][j] == 'Q') 
            {
                return false;
            }
        }
        // 检查 135度角是否有皇后
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) 
        {
            if (chessboard[i][j] == 'Q') 
            {
                return false;
            }
        }
        return true;
    }
public:
    vector<vector<string>> solveNQueens(int n) 
    {
        result.clear();
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }
};

2e1bd56a56ab407dac2f4583388db47e.png


👉总结👈


本篇博客主要讲解了 N 皇后问题,N 皇后问题算是回溯算法中比较难的题目了,解决 N 皇后问题的关键就是判断皇后之间是否会相互攻击。除此之外,还讲解了用位信息来判断放置皇后的位置限制。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

相关文章
|
1月前
|
机器学习/深度学习 算法 C++
【DFS/回溯算法】2016年蓝桥杯真题之路径之谜详解
题目要求根据城堡北墙和西墙箭靶上的箭数,推断骑士从西北角到东南角的唯一路径。每步移动时向正北和正西各射一箭,同一格不重复经过。通过DFS回溯模拟“拔箭”过程,验证路径合法性。已知箭数约束路径唯一,最终按编号输出行走顺序。
|
3月前
|
算法
回溯算法的基本思想
本节介绍回溯算法,通过图1中从A到K的路径查找示例,说明其与穷举法的异同。回溯算法通过“回退”机制高效试探各种路径,适用于决策、优化和枚举问题。
95 0
|
4月前
|
存储 缓存 算法
什么是回溯算法
回溯算法是一种通过尝试所有可能路径寻找问题解的策略,采用深度优先搜索与状态重置机制。它适用于组合、排列、棋盘等需枚举所有可能解的问题,核心思想包括DFS遍历、剪枝优化与状态恢复。尽管时间复杂度较高,但通过合理剪枝可显著提升效率,是解决复杂搜索问题的重要方法。
204 0
|
8月前
|
算法 Java
算法系列之回溯算法求解数独及所有可能解
数独求解的核心算法是回溯算法。回溯算法是一种通过逐步构建解决方案并在遇到冲突时回退的算法。具体来说,我们尝试在空格中填入一个数字,然后递归地继续填充下一个空格。如果在某个步骤中发现无法继续填充,则回退到上一步并尝试其他数字。
302 11
算法系列之回溯算法求解数独及所有可能解
|
9月前
|
算法 Java
算法系列之回溯算法
回溯算法(Backtracking Algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,遇到正确解将其记录,直到找到了所有的解或者尝试了所有的可能为止。
277 4
算法系列之回溯算法
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法

热门文章

最新文章