回溯法c++学习 解决八皇后问题

使用回溯法解决八皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8

的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。这个问题可以推广为更一般的n 皇后摆放问题,其中棋盘的大小变为n×n ,而皇后个数也变成n 。当且仅当n=1 或n≥4

时,问题有解

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
private:
    std::vector<std::vector<std::string>> results; // 存储所有有效的棋盘配置

public:
    std::vector<std::vector<std::string>> solveNQueens(int n) {
        std::vector<std::string> board(n, std::string(n, '.')); // 初始化棋盘,全部填充为'.'
        backtrack(board, 0); // 从第0行开始回溯
        return results;
    }

private:
    void backtrack(std::vector<std::string>& board, int row) {
        if (row == board.size()) { // 如果已经放置了n个皇后(到达最后一行之后),找到一个有效解
            results.push_back(board);
            return;
        }

        int n = board[row].size();
        for (int col = 0; col < n; col++) { // 尝试在当前行的每一列放置皇后
            if (isValid(board, row, col)) { // 检查在此位置放置皇后是否有效
                board[row][col] = 'Q'; // 放置皇后
                backtrack(board, row + 1); // 递归到下一行
                board[row][col] = '.'; // 回溯,撤销这个位置的皇后
            }
        }
    }

    bool isValid(const std::vector<std::string>& board, int row, int col) {
        int n = board.size();

        // 检查同一列
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') return false;
        }

        // 检查左上对角线
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') return false;
        }

        // 检查右上对角线
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') return false;
        }

        return true; // 如果通过所有检查,则此位置有效
    }
};

int main() {
    Solution solution;
    auto results = solution.solveNQueens(8); // 解决8皇后问题

    // 打印所有解
    for (int i = 0; i < results.size(); i++) {
        std::cout << "Solution " << i + 1 << ":\n";
        for (const auto& row : results[i]) {
            std::cout << row << "\n";
        }
        std::cout << "\n";
    }

    std::cout << "Total solutions: " << results.size() << std::endl;

    return 0;
}

这段代码的详细解释如下:

  1. 我们定义了一个Solution类来封装解决方案。
  2. results成员变量用于存储所有有效的棋盘配置。
  3. solveNQueens函数是主入口点,它初始化棋盘并开始回溯过程。
  4. backtrack函数实现了回溯算法:
    • 如果已经成功放置了n个皇后,我们就找到了一个有效解。
    • 否则,我们尝试在当前行的每一列放置皇后。
    • 如果某个位置有效,我们就放置皇后,然后递归到下一行。
    • 在回溯时,我们撤销这个位置的皇后。
  5. isValid函数检查在特定位置放置皇后是否有效:
    • 检查同一列是否已有皇后。
    • 检查左上对角线是否已有皇后。
    • 检查右上对角线是否已有皇后。
  6. 在main函数中,我们创建Solution对象,解决8皇后问题,并打印所有解。

这个算法的时间复杂度是O(N!),其中N是棋盘的大小(在这里是8)。这是因为在最坏的情况下,我们需要尝试所有可能的排列。空间复杂度是O(N),主要用于递归调用栈和存储棋盘状态。

这个解决方案使用了回溯法,它通过系统地尝试所有可能的配置来找到所有有效的解。每当发现当前路径不可行时,它就回溯并尝试下一个可能的选择。

但是八皇后问题的最有效的算法是位运算法

cpp 复制代码
#include <iostream>
using namespace std;

// 位运算解决八皇后问题
void solveNQueens(int n) {
    long upperlim = (1 << n) - 1; // 初始化,upperlim 表示 n 个皇后的所有列都已放置好
    long Ans = 0; // 记录解的个数

    // 递归函数,寻找可以放置皇后的位置
    void test(long row, long ld, long rd) {
        if (row != upperlim) {
            // pos 表示当前行可以放置皇后的位置
            long pos = upperlim & (~(row | ld | rd));
            while (pos) {
                // 取出最右边的可以放皇后的位置
                long p = pos & (-pos);
                pos -= p; // 移除该位置并递归调用 test 过程

                // 更新限制条件
                long new_ld = (ld | p) << 1;
                long new_rd = (rd | p) >> 1;
                test(row | p, new_ld, new_rd);
            }
        } else {
            ++Ans; // 找到一个解
        }
    }

    // 调用参数
    test(0, 0, 0);

    cout << "共有 " << Ans << " 种排列" << endl;
}

int main() {
    int n = 8; // 八皇后问题
    solveNQueens(n);
    return 0;
}

这段代码使用了位运算来高效地解决八皇后问题。核心思想是用一个整数变量表示每一行中哪些位置已经被占用,然后通过位运算判断某个位置是否可以放置皇后。具体解释如下:

  1. upperlim 初始化为2n−1,表示 n 个皇后的所有列都已放置好。
  2. test 函数是递归的,它寻找可以放置皇后的位置。参数 row、ld 和 rd 分别表示在纵列和两个对角线方向的限制条件下,这一行的哪些地方不能放。
  3. 位于该行上的冲突位置用 row、ld 和 rd 中的 1 来表示。将它们三个进行并操作,得到该行所有的禁位,取反后就得到所有可以放的位置(用 pos 表示)。
  4. p = pos & (-pos) 取出 pos 最右边的那个 1,表示该行的某个可以放子的位置。将它从 pos 中移除并递归调用 test 过程。
  5. 注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。
  6. 最后,如果递归到某个时候发现 row = upperlim,说明 n 个皇后全放进去了,找到的解的个数加一。
相关推荐
AI你一生一世1 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
布莱克6051 小时前
C/C++ 中变量的声明与定义:区别、联系与常见误区
c语言·开发语言·c++·声明·定义
云-缩影2 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯2 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
前进的李工3 小时前
Agent 学习笔记 05:系统提示词应该怎么写
笔记·学习·prompt·agent·个人开发·few-shot·ai应用开发
正在走向自律5 小时前
【3/5】人工智能训练师 三级/高级工 考证学习方向:从“独立执行”到“设计、归因与全链条负责”
人工智能·学习·人工智能训练师·注方案设计·数据质控负责人·模型评测与调优辅助·ai训练项目交付
沫璃染墨6 小时前
从零入门计算机网络系列(十二):HTTP长连接与会话管理详解——从Connection到Cookie与Session
linux·服务器·c++·tcp/ip·ubuntu·http·交互
海的透彻6 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat6 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
CCIE-Yasuo7 小时前
网络安全-学习数据库-Mysql-2
数据库·学习·mysql