基于回溯法解决八皇后问题+以位运算方法优化n皇后问题(算法与数据结构期末设计)

news2024/12/15 21:22:20

文章目录

  • 基于回溯法解决八皇后问题+以位运算方法优化n皇后问题
    • 1. 八皇后问题问题描述
    • 2.回溯法求八皇后(n皇后)问题
      • ①由四皇后问题引入
      • ②皇后的占位问题
      • ③皇后的放置过程
      • ④放置过程中的问题
      • ⑤回溯算法核心
      • ⑥回溯算法的求解过程
      • ⑦验证算法和代码实现
        • LeetCode51.N皇后
        • LeetCode52.N皇后II
    • 3.位运算求八皇后(n皇后)问题(最优解)
      • 用位信息表示列限制
      • 用位信息表示对角线限制
        • 从右上往左下的限制 如何用位信息表示
        • 从左上往右下的限制 如何用位信息表示
      • 如何整合在一起
    • 4.数组方法存皇后限制(回溯)与位运算方法的时间对比
  • 总结


基于回溯法解决八皇后问题+以位运算方法优化n皇后问题


1. 八皇后问题问题描述

问题描述:八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。

要求:求解并输出八皇后问题的全部92个解。

2.回溯法求八皇后(n皇后)问题

(有多少种摆放方式)+(每种方式的具体摆法)
在这里插入图片描述
在这里插入图片描述
保证所有皇后互相不可互相攻击

①由四皇后问题引入

先使问题简化,让n=4,以四皇后问题进行引入。
在这里插入图片描述

对于四皇后,有这两种摆放形式。

②皇后的占位问题

如果在棋盘上放置一个皇后,它实际上占据了哪些位置呢?
在这里插入图片描述

上,下,左,右,左上,左下,右上,右下。八个方向的位置会被占据,此时,橙色区域就不能再放置其他的皇后了。

③皇后的放置过程

放置第一个皇后,用橙色标记它的攻击位置
在第二行白色格子处,放置第二个皇后 ,用蓝色标记攻击位置
在这里插入图片描述

在第三行白色格子处,放第三个皇后,用绿色标记攻击位置
在第四行白色格子处,放第四个皇后,用紫色标记攻击位置
在这里插入图片描述

④放置过程中的问题

在这里插入图片描述
如果在放置过程中,发现皇后没有空白位置放置了,这是就说明之前某个皇后的摆放方式有问题,我们需要回到之前的某一时刻,重新选择该皇后放置方法,从而使后面的皇后可以正常摆放。
这里的:回到之前的某一时刻重新放置,就是回溯。
在这里插入图片描述

⑤回溯算法核心

回溯算法核心,就是发现哪一步出现了问题,就回到之前的时刻,重新尝试。

⑥回溯算法的求解过程

(以四皇后为例)
在过程中,需要定义两个二维数组。
attack[][]:用于标记皇后的攻击位置,攻击位置标为1,其余为0。
queen[][]:用于保存放置结果,标记皇后所在的位置,放置皇后位置为Q,其余为.。
在这里插入图片描述
在这里插入图片描述

问题的核心在于,递归的放置皇后,从一个空白的棋盘开始,每一行都要放置一个皇后,然后递归到下一行。
完成皇后的放置后,需要更新attack和queen数组。然后再进行下一行的皇后设置。
如果当前行没有任何一列可以放置皇后=》需要回溯。

以上图片及解释的参考视频:
回溯法求八皇后问题

⑦验证算法和代码实现

LeetCode51.N皇后

N皇后

可直接提交的代码,附注释及错误改正(c++实现)

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        // 传入参数n,表示n皇后问题
        // 函数返回一个二维的vector
        // 内部的vector是string类型
        // 保存n皇后的全部解法
 
        vector<string> queen; 
            // 保存皇后位置
        vector<vector<int>> attack;
            // 保存皇后攻击位置
        vector<vector<string>> solve;
            // 保存最终结果

        // 初始化attack和queen数组
        for(int i=0;i<n;i++){
            attack.push_back((std::vector<int>()));
            // 1、向量(Vector)。
            // 2、它是一个动态数组,可以根据需要自动调整大小。
            // 3、向量提供了一些方便的方法来管理元素,比如添加、删除和访问元素。
            // 4、向量提供了一些内置功能,比如 push_back()(在末尾添加元素)、pop_back()(删除末尾元素)、size()(获取元素数量)等。
            // 5、std::vector<int> numbers;  创建一个空的整数向量。
            // 6、numbers.push_back(10); 在向量末尾添加元素10
            
            // 初始状态:
            // 刚创建时,numbers 是一个空的向量,不包含任何元素。
            // 它的初始大小为 0,但它可以动态增长。
            // 你可以使用 push_back() 方法向向量末尾添加元素:
            // numbers.push_back(10); // 向向量末尾添加元素 10
            // numbers.push_back(20); // 向向量末尾添加元素 20
            
            // 对于每一行,创建一个新的空列向量,并将其添加到 attack 向量中。
            // 这样,attack 成为一个 n x n 的二维向量,初始时所有元素都是空的。
            for(int j=0;j<n;j++){
                attack[i].push_back(0);
            }
            queen.push_back("");
            // queen.push_back(""):为 queen 向量添加一个新的空字符串,表示当前行的状态。
            // queen[i].append(n,".");
            queen[i] = std::string(n, '.'); // 使用 std::string 构造函数初始化
            
            // 主函数 solveNQueens 中 queen 数组初始化错误:
            // 在初始化 queen 数组时,你使用了 queen[i].push_back('.');,
            // 这会将每个 queen[i] 初始化为 ".",而不是 n 个 "."。
            // 就是会变成".""."".""."而不是"...."这样的。
            // 这会导致后续操作中 queen[k][i] 访问越界。
            // 修正方法:使用 queen[i].append(n, '.'); 来初始化每个 queen[i] 为 n 个 "."

            // queen[i].push_back('.'); // 添加一个字符
            // 将当前行的字符串扩展为 n 个 '.' 字符。'.' 通常表示该位置尚未放置皇后。
        }
        backtrack(0,n,queen,attack,solve);
        return solve;
    }




    //在(x,y)位置放置皇后,并更新攻击位置attack数组 
    void put_queen(int x,int y,vector<vector<int>> &attack){
        // 定义常量方向数组,用于对8个方向的更新
        static const int dx[]={-1,1,0,0,-1,-1,1,1};
        static const int dy[]={0,0,-1,1,-1,1,-1,1};
        attack[x][y]=1;        
        // 将皇后位置标为1
        // 通过两层循环,对该皇后可能攻击到的位置,进行标记
        // 外层for循环,表示从皇后位置向外延伸 
        // “从皇后位置向外延伸” 指的是从皇后所在的 (x, y) 位置出发,沿着八个方向逐步向外扩展,标记所有可能被攻击的位置。
        // 外层循环 for(int i = 1; i < attack.size(); i++) 控制延伸的步数,内层循环 for(int j = 0; j < 8; j++) 遍历八个方向。
        // 这种方法确保了皇后在所有方向上的攻击路径都被正确标记。
        
        // 外层循环 (for(int i = 1; i < attack.size(); i++)):
        // i 表示从皇后位置向外延伸的步数。
        // 当 i = 1 时,表示从皇后位置向外延伸1步,标记距离皇后1步的位置。
        // 当 i = 2 时,表示从皇后位置向外延伸2步,标记距离皇后2步的位置。
        // 依此类推,直到 i 达到棋盘的大小 attack.size()。

        for(int i=1;i<attack.size();i++){
            // 因为attack是动态数组,所有以这种形式获得,长度。
            // 对于八皇后问题,attack.size()就是8。
            // 内层便利8个方向,生成新的位置
            for(int j=0;j<8;j++){
                int nx=x+i*dx[j];
                int ny=y+i*dy[j];
                // 如果新位置在棋盘范围内
                if(nx>=0&&nx<attack.size()&&ny>=0&&ny<attack.size()){
                    attack[nx][ny]=1;
                    // 将他标记为1
                }
            }
        }
    }




    // 回溯法求解n皇后的递归函数
    // k表示当前正处理的行
    // n表示皇后数量
    // queen存储皇后的位置
    // attack标记皇后的攻击位置
    // solve存储n皇后的全部解法
    void backtrack(int k,int n,vector<string> &queen,vector<vector<int>> &attack,vector<vector<string>> &solve){
        // 在 backtrack 函数中,你传递了 attack 数组的副本给 put_queen,但 put_queen 函数是按值传递的,
        // 这意味着对 attack 的修改不会影响递归调用外部的 attack 数组。
        // 修正方法:将 put_queen 函数的参数改为引用传递,以便修改能够影响到外部的 attack 数组。
        if(k==n){
            // 找到了一组解
            solve.push_back(queen);
            // 将结果queen存储至solve
            return;
        }
        for(int i=0;i<n;i++){
            // 遍历每一列==>遍历第k行的第0列到n-1列
            // 回溯法试探皇后的可放置的位置
            if(attack[k][i]==0){
                // =0表示可以放置皇后
                vector<vector<int>> temp=attack;
                // 备份attack数组
                queen[k][i]='Q';
                put_queen(k,i,attack);
                // 更新attack数组
                backtrack(k+1,n,queen,attack,solve);
                // 继续探索k+1行皇后放置
                // 当递归完成后,将attack和queen数组恢复为递归前的状态
                attack=temp;
                queen[k][i]='.';
            }
            // 继续下一列的尝试
        }
        // for循环结束,就完成了k行所有可能的尝试。
    }

}; 

可在编译器里运行 并出图案的 代码版本(c++实现)

#include <iostream>
#include <vector>
#include <string>
using namespace std; 

// 在(x,y)位置放置皇后,并更新攻击位置attack数组 
void put_queen(int x, int y, vector<vector<int>> &attack){
    // 定义常量方向数组,用于对8个方向的更新
    static const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
    static const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
    attack[x][y] = 1;        
    // 将皇后位置标为1

    // 通过两层循环,对该皇后可能攻击到的位置,进行标记
    for(int i = 1; i < attack.size(); i++){
        for(int j = 0; j < 8; j++){
            int nx = x + i * dx[j];
            int ny = y + i * dy[j];
            // 如果新位置在棋盘范围内
            if(nx >= 0 && nx < attack.size() && ny >= 0 && ny < attack.size()){
                attack[nx][ny] = 1;
                // 将他标记为1
            }
        }
    }
}

// 回溯法求解n皇后的递归函数
// k表示当前正处理的行
// n表示皇后数量
// queen存储皇后的位置
// attack标记皇后的攻击位置
// solve存储n皇后的全部解法
void backtrack(int k, int n, vector<string> &queen, vector<vector<int>> &attack, vector<vector<string>> &solve){
    if(k == n){
        // 找到了一组解
        solve.push_back(queen);
        // 将结果queen存储至solve
        return;
    }
    for(int i = 0; i < n; i++){
        // 遍历每一列==>遍历第k行的第0列到n-1列
        // 回溯法试探皇后的可放置的位置
        if(attack[k][i] == 0){
            // =0表示可以放置皇后
            vector<vector<int>> temp = attack;
            // 备份attack数组
            queen[k][i] = 'Q';
            put_queen(k, i, attack);
            // 更新attack数组
            backtrack(k + 1, n, queen, attack, solve);
            // 继续探索k+1行皇后放置
            // 当递归完成后,将attack和queen数组恢复为递归前的状态
            attack = temp;
            queen[k][i] = '.';
        }
        // 继续下一列的尝试
    }
    // for循环结束,就完成了k行所有可能的尝试。
}

// 主函数
int main(){
    int n;
    cout << "请输入皇后的数量 n: ";
    cin >> n;
    // 传入参数n,表示n皇后问题
    // 函数返回一个二维的vector
    // 内部的vector是string类型
    // 保存n皇后的全部解法

    vector<string> queen; 
        // 保存皇后位置
    vector<vector<int>> attack;
        // 保存皇后攻击位置
    vector<vector<string>> solve;
        // 保存最终结果

    // 初始化attack和queen数组
    for(int i = 0; i < n; i++){
        attack.push_back(vector<int>()); // 使用 vector<int>() 初始化
        for(int j = 0; j < n; j++){
            attack[i].push_back(0); // 初始化所有位置为0
        }
        queen.push_back(string(n, '.')); // 初始化为n个'.' 
    }

    backtrack(0, n, queen, attack, solve);

    // 输出结果
    if(solve.size() == 0){
        cout << "没有方案。" << endl;
    }
    else{
        for(int i = 0; i < solve.size(); i++){
            cout << "第 " << i + 1 << " 个解:" << endl;
            for(int j = 0; j < solve[i].size(); j++){
                cout << solve[i][j] << endl;
            }
            cout << endl;
        }
        cout << "总共有 " << solve.size() << " 个解。" << endl;

    }
    return 0;
}
LeetCode52.N皇后II

n皇后II

可直接提交的代码,附注释(java实现)(与上面的方法不同)

(用数组表示路径实现的N皇后问题)

class Solution {
    // 用数组表示路径实现的N皇后问题
	public static int totalNQueens(int n) {
		if (n < 1) {
			return 0;
		}
		return f1(0, new int[n], n);
	}

	// i : 当前来到的行(当前的行)
	// path : 0...i-1行的皇后,都摆在了哪些列(之前的行)
	// n : 是几皇后问题
	// 返回 : 0...i-1行已经摆完了,i....n-1行可以去尝试的情况下还能找到几种有效的方法
	public static int f1(int i, int[] path, int n) {
		if (i == n) {
            // 如果能到最后一行这个终止位置,
            // 就找到了一种有效方法。
			return 1;
		}
		int ans = 0;
        // ans有多少种有效的方法
		//      0 1 2 3 .. n-1(列数)
		// i    j             (i表示来到第i行)(j从0到n-1列依次试)
		for (int j = 0; j < n; j++) {
			if (check(path, i, j)) {
				path[i] = j;
				ans += f1(i + 1, path, n);
			}
		}
		return ans;
	}

	// 当前在i行、j列的位置,摆了一个皇后
	// 0...i-1行的皇后状况,path[0...i-1]====》// path : 0...i-1行的皇后,都摆在了哪些列(之前的行)
	// 返回会不会冲突,不会冲突,有效!true
	// 会冲突,无效,返回false
	public static boolean check(int[] path, int i, int j) {
		// 当前 i
		// 当列 j
		for (int k = 0; k < i; k++) {
			// 0...i-1
			// 之前行 : k
			// 之前列 : path[k]
			if (j == path[k] || Math.abs(i - k) == Math.abs(j - path[k])) {
                // 当前的列不能和之前的列一样
                // Math.abs(i - k) == Math.abs(j - path[k])这句话可以表示在对角线方向
                // 之前的行-当前的行 的绝对值=之前的列-当前的列 的绝对值
                // 则两个数在对角线方向。
				return false;
			}
		}
		return true;
	}

}

以上代码参考视频如下

参考视频

可在编译器里面运行 出图案的 代码(java实现)


import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    // 存储所有解法
    public static List<int[]> allSolutions = new ArrayList<>();

    // 用数组表示路径实现的N皇后问题,不推荐
    public static int totalNQueens(int n) {
        if (n < 1) {
            return 0;
        }
        backtrack(0, new int[n], n);
        return allSolutions.size();
    }

    // 回溯法求解N皇后问题
    public static void backtrack(int i, int[] path, int n) {
        if (i == n) {
            // 找到一种有效解,将其添加到allSolutions中
            allSolutions.add(path.clone());
            return;
        }

        for (int j = 0; j < n; j++) {
            if (check(path, i, j)) {
                path[i] = j;
                backtrack(i + 1, path, n);
            }
        }
    }

    // 检查当前放置是否有效
    public static boolean check(int[] path, int i, int j) {
        for (int k = 0; k < i; k++) {
            if (j == path[k] || Math.abs(i - k) == Math.abs(j - path[k])) {
                return false;
            }
        }
        return true;
    }

    // 辅助方法:根据path数组生成棋盘的图形表示
    public static void printBoard(int[] path) {
        int n = path.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (path[i] == j) {
                    System.out.print("Q ");
                } else {
                    System.out.print(". ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入皇后的数量n: ");
        int n = scanner.nextInt();

        int total = totalNQueens(n);
        if (total == 0) {
            System.out.println("没有找到有效的解法。");
        } else {
            for (int i = 0; i < allSolutions.size(); i++) {
                System.out.println("解法 " + (i + 1) + ":");
                printBoard(allSolutions.get(i));
            }
            System.out.println("总共有 " + total + " 种解法。");
        }
        scanner.close();
    }
}

输出结果如图所示
在这里插入图片描述
在这里插入图片描述

可在编译器里面运行 出图案的 代码(c++实现)

#include <iostream>
#include <vector>
#include <string>
#include <cmath>

using namespace std;

// 存储所有解法
vector<vector<int>> allSolutions;

// 检查当前放置是否有效
bool check(const vector<int>& path, int i, int j, int n) {
    for (int k = 0; k < i; k++) {
        if (j == path[k] || abs(i - k) == abs(j - path[k])) {
            return false;
        }
    }
    return true;
}

// 回溯法求解N皇后问题
void backtrack(int i, vector<int>& path, int n) {
    if (i == n) {
        // 找到一种有效解,将其添加到allSolutions中
        allSolutions.push_back(path);
        return;
    }

    for (int j = 0; j < n; j++) {
        if (check(path, i, j, n)) {
            path[i] = j;
            backtrack(i + 1, path, n);
        }
    }
}


// 辅助方法:根据path数组生成棋盘的图形表示
void printBoard(const vector<int>& path) {
    int n = path.size();
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (path[i] == j) {
                cout << "Q ";
            } else {
                cout << ". ";
            }
        }
        cout << endl;
    }
    cout << endl;
}

int main() {
    int n;
    cout << "请输入皇后的数量n: ";
    cin >> n;

    if (n < 1) {
        cout << "没有找到有效的解法。" << endl;
        return 0;
    }

    vector<int> path(n, -1); // 初始化路径数组

    backtrack(0, path, n);

    if (allSolutions.empty()) {
        cout << "没有找到有效的解法。" << endl;
    } else {
        for (size_t i = 0; i < allSolutions.size(); i++) {
            cout << "解法 " << (i + 1) << ":" << endl;
            printBoard(allSolutions[i]);
        }
        cout << "总共有 " << allSolutions.size() << " 种解法。" << endl;
    }

    return 0;
}

3.位运算求八皇后(n皇后)问题(最优解)

参考视频
(以下拿五皇后问题为例)

用位信息表示列限制

用位信息来表示皇后放置的位置。
一个整型int变量有32位,表示现在哪些列已经摆放了皇后,不是用整型的值,而是用整数的位信息状态来表示哪些列已经摆放了皇后。
在这里插入图片描述
如图,如果是要摆放5*5的棋盘,5皇后问题。
在0到4行,0到4列的格子种,皇后摆放到了0行1列,那位信息就是这样的。第一位的位信息为1,其余为0,表示第一列有皇后放置。
在这里插入图片描述
如果在第1行,第3列,又摆放了一个皇后。则位信息变为上图。
在这里插入图片描述
整体过程如上图。

用位信息表示对角线限制

从右上往左下的限制 如何用位信息表示

在这里插入图片描述
如果此时的摆放如图,那么下一行是在第一列的位置无法摆放皇后的。
用left表示限制
此时的数组left为
在这里插入图片描述
下一行的left数组为
在这里插入图片描述
也就是把第一个状态的位信息右移,就可以得到第二个状态的位信息。
如果此时,在第四列再放置一个皇后,那此时的位信息变为
在这里插入图片描述
那如何表达此时的整体状态对下面的影响:将整体的位信息右移
在这里插入图片描述
如果位信息在右移过程中出现了,溢出现象,也没有关系,说明“影响”已经出格子了,没有限制了。

从左上往右下的限制 如何用位信息表示

以right表示
左移
同理
过程如图:
在这里插入图片描述

如何整合在一起

列限制:col:1不可以放,0可以放皇后
右上到左下限制:left:1不可以放,0可以放皇后
左上到右下限制:right:1不可以放,0可以放皇后
三个限制都是int类型的整数
共同限制:一个或在一起
col || left || right 1不可以放,0可以放皇后
~(共同的限制):共同的限制取反:0不可以放,1可以放皇后
所以放皇后的时候尝试所有1的位置就可以了。并且注意不能超出0~4的范围(对于5皇后问题)

n皇后II

可直接提交的代码(java实现)(位信息方法)

class Solution {
    // 用位信息表示路径实现的N皇后问题,推荐
	public static int totalNQueens(int n) {
		if (n < 1) {
			return 0;
		}
		// n = 5(如果是五皇后问题)
		// 1 << 5 = 0...100000 - 1(1向左移动5个状态得到limit)
		// limit  = 0...011111; (limit是规定范围用的)
		// n = 7
		// limit  = 0...01111111; 
		int limit = (1 << n) - 1;//用状态来限制几皇后的问题
		return f2(limit, 0, 0, 0);
	}

	// limit : 当前是几皇后问题(低位上有几个1就是几皇后问题)
	// 之前皇后的列影响:col
	// 之前皇后的右上 -> 左下对角线影响:left
	// 之前皇后的左上 -> 右下对角线影响:right
	public static int f2(int limit, int col, int left, int right) {
		if (col == limit) {
			// 所有皇后放完了!
			return 1;
		}
		// 总限制
		int ban = col | left | right;
		// ~ban :(总限制取反后代表) 1可放皇后,0不能放
		int candidate = limit & (~ban);//并且希望在有效的范围内表示出来
		// 放置皇后的尝试!
		int place = 0;
		// 一共有多少有效的方法
		int ans = 0;
		while (candidate != 0) {
			// (注释后的第一句的意思就是)提取出最右侧的1
			// 0 0 1 1 1 0
			// 5 4 3 2 1 0
			// place : 
			// 0 0 0 0 1 0(这个状态就是提取出来的最右侧的1)
			// candidate : 
			// 0 0 1 1 0 0
			// 5 4 3 2 1 0
			// place : 
			// 0 0 0 1 0 0
			// candidate : 
			// 0 0 1 0 0 0
			// 5 4 3 2 1 0
			// place : 
			// 0 0 1 0 0 0
			// candidate : 
			// 0 0 0 0 0 0
			// 5 4 3 2 1 0
			place = candidate & (-candidate);
			candidate ^= place;//异或
			ans += f2(limit, col | place, (left | place) >> 1, (right | place) << 1);
		}
		return ans;
	}

}

可在编译器运行 出图案的代码 (Java实现)

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    // 存储所有解法
    public static List<int[]> allSolutions = new ArrayList<>();

    // 用位信息表示路径实现的N皇后问题,推荐
    public static int totalNQueens(int n) {
        if (n < 1) {
            return 0;
        }
        // n = 5(如果是五皇后问题)
        // 1 << 5 = 0...100000 - 1(1向左移动5个状态得到limit)
        // limit  = 0...011111; (limit是规定范围用的)
        // n = 7
        // limit  = 0...01111111;
        int limit = (1 << n) - 1;//用状态来限制几皇后的问题
        backtrack(limit, 0, 0, 0, new int[n]);
        return allSolutions.size();
    }

    // 回溯法求解N皇后问题
    public static void backtrack(int limit, int col, int left, int right, int[] path) {
        if (col == limit) {
            // 找到一种有效解,将其添加到allSolutions中
            allSolutions.add(path.clone());
            return;
        }

        // 总限制
        int ban = col | left | right;
        // ~ban :(总限制取反后代表) 1可放皇后,0不能放
        int candidate = limit & (~ban);//并且希望在有效的范围内表示出来

        // 放置皇后的尝试!
        while (candidate != 0) {
            // 提取出最右侧的1
            int place = candidate & (-candidate);
            candidate ^= place;//异或

            // 计算新的路径
            int row = Integer.bitCount(col);
            path[row] = Integer.numberOfTrailingZeros(place);

            // 递归调用
            backtrack(limit, col | place, (left | place) >> 1, (right | place) << 1, path);
        }
    }

    // 辅助方法:根据path数组生成棋盘的图形表示
    public static void printBoard(int[] path) {
        int n = path.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (path[i] == j) {
                    System.out.print("Q ");
                } else {
                    System.out.print(". ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入皇后的数量n: ");
        int n = scanner.nextInt();

        int total = totalNQueens(n);
        if (total == 0) {
            System.out.println("没有找到有效的解法。");
        } else {
            System.out.println("总共有 " + total + " 种解法。");
            for (int i = 0; i < allSolutions.size(); i++) {
                System.out.println("解法 " + (i + 1) + ":");
                printBoard(allSolutions.get(i));
            }
        }
        scanner.close();
    }
}

可在编译器运行 出图案的代码 (c++实现)

#include <iostream>
#include <vector>
#include <string>

using namespace std;

// 存储所有解法
vector<vector<int>> allSolutions;

// 回溯法求解N皇后问题
void backtrack(int limit, int col, int left, int right, vector<int>& path, int n) {
    if (col == limit) {
        // 找到一种有效解,将其添加到allSolutions中
        allSolutions.push_back(path);
        return;
    }

    // 总限制:col | left | right
    int ban = col | left | right;
    // 可选位置:limit & (~ban),1表示可以放置皇后
    int candidate = limit & (~ban);

    // 尝试放置皇后
    while (candidate != 0) {
        // 提取最右侧的1
        int place = candidate & (-candidate);
        candidate ^= place; // 移除已放置的位置

        // 计算当前行的索引
        int row = __builtin_popcount(col);
        path[row] = __builtin_ctz(place);

        // 递归调用,更新限制
        backtrack(limit, col | place, (left | place) << 1, (right | place) >> 1, path, n);
    }
}

// 辅助方法:根据path数组生成棋盘的图形表示
void printBoard(const vector<int>& path) {
    int n = path.size();
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (path[i] == j) {
                cout << "Q ";
            } else {
                cout << ". ";
            }
        }
        cout << endl;
    }
    cout << endl;
}

int main() {
    int n;
    cout << "请输入皇后的数量n: ";
    cin >> n;

    if (n < 1) {
        cout << "没有找到有效的解法。" << endl;
        return 0;
    }

    // 初始化limit为(1 << n) - 1,例如n=4时,limit=0b1111
    int limit = (1 << n) - 1;
    vector<int> path(n, -1); // 初始化路径数组

    backtrack(limit, 0, 0, 0, path, n);

    if (allSolutions.empty()) {
        cout << "没有找到有效的解法。" << endl;
    } else {
        for (size_t i = 0; i < allSolutions.size(); ++i) {
            cout << "解法 " << (i + 1) << ":" << endl;
            printBoard(allSolutions[i]);
        }
        cout << "总共有 " << allSolutions.size() << " 种解法。" << endl;

    }

    return 0;
}

4.数组方法存皇后限制(回溯)与位运算方法的时间对比

public class Main {

    // 用数组表示路径实现的N皇后问题,不推荐
    public static int totalNQueens1(int n) {
        if (n < 1) {
            return 0;
        }
        return f1(0, new int[n], n);
    }

    // i : 当前来到的行
    // path : 0...i-1行的皇后,都摆在了哪些列
    // n : 是几皇后问题
    // 返回 : 0...i-1行已经摆完了,i....n-1行可以去尝试的情况下还能找到几种有效的方法
    public static int f1(int i, int[] path, int n) {
        if (i == n) {
            return 1;
        }
        int ans = 0;
        // 0 1 2 3 .. n-1
        // i j
        for (int j = 0; j < n; j++) {
            if (check(path, i, j)) {
                path[i] = j;
                ans += f1(i + 1, path, n);
            }
        }
        return ans;
    }

    // 当前在i行、j列的位置,摆了一个皇后
    // 0...i-1行的皇后状况,path[0...i-1]
    // 返回会不会冲突,不会冲突,有效!true
    // 会冲突,无效,返回false
    public static boolean check(int[] path, int i, int j) {
        // 当前 i
        // 当列 j
        for (int k = 0; k < i; k++) {
            // 0...i-1
            // 之前行 : k
            // 之前列 : path[k]
            if (j == path[k] || Math.abs(i - k) == Math.abs(j - path[k])) {
                return false;
            }
        }
        return true;
    }

    // 用位信息表示路径实现的N皇后问题,推荐
    public static int totalNQueens2(int n) {
        if (n < 1) {
            return 0;
        }
        // n = 5
        // 1 << 5 = 0...100000 - 1
        // limit  = 0...011111;
        // n = 7
        // limit  = 0...01111111;
        int limit = (1 << n) - 1;
        return f2(limit, 0, 0, 0);
    }

    // limit : 当前是几皇后问题
    // 之前皇后的列影响:col
    // 之前皇后的右上 -> 左下对角线影响:left
    // 之前皇后的左上 -> 右下对角线影响:right
    public static int f2(int limit, int col, int left, int right) {
        if (col == limit) {
            // 所有皇后放完了!
            return 1;
        }
        // 总限制
        int ban = col | left | right;
        // ~ban : 1可放皇后,0不能放
        int candidate = limit & (~ban);
        // 放置皇后的尝试!
        int place = 0;
        // 一共有多少有效的方法
        int ans = 0;
        while (candidate != 0) {
            // 提取出最右侧的1
            // 0 0 1 1 1 0
            // 5 4 3 2 1 0
            // place :
            // 0 0 0 0 1 0
            // candidate :
            // 0 0 1 1 0 0
            // 5 4 3 2 1 0
            // place :
            // 0 0 0 1 0 0
            // candidate :
            // 0 0 1 0 0 0
            // 5 4 3 2 1 0
            // place :
            // 0 0 1 0 0 0
            // candidate :
            // 0 0 0 0 0 0
            // 5 4 3 2 1 0
            place = candidate & (-candidate);
            candidate ^= place;
            ans += f2(limit, col | place, (left | place) >> 1, (right | place) << 1);
        }
        return ans;
    }

    public static void main(String[] args) {
        int n = 14;
        long start, end;
        System.out.println("测试开始");
        System.out.println("解决" + n + "皇后问题");
        start = System.currentTimeMillis();
        System.out.println("方法1答案 : " + totalNQueens1(n));
        end = System.currentTimeMillis();
        System.out.println("方法1运行时间 : " + (end - start) + " 毫秒");

        start = System.currentTimeMillis();
        System.out.println("方法2答案 : " + totalNQueens2(n));
        end = System.currentTimeMillis();
        System.out.println("方法2运行时间 : " + (end - start) + " 毫秒");
        System.out.println("测试结束");

        System.out.println("=======");
        System.out.println("只有位运算的版本,才能10秒内跑完16皇后问题的求解过程");
        start = System.currentTimeMillis();
        int ans = totalNQueens2(16);
        end = System.currentTimeMillis();
        System.out.println("16皇后问题的答案 : " + ans);
        System.out.println("运行时间 : " + (end - start) + " 毫秒");
    }

}

在这里插入图片描述

总结

基于回溯法解决n皇后问题+以位运算方法优化n皇后问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2260142.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python学opencv|读取图像(九)用numpy创建黑白相间灰度图

【1】引言 前述学习过程中&#xff0c;掌握了用numpy创建矩阵数据&#xff0c;把所有像素点的BGR取值设置为0&#xff0c;然后创建纯黑灰度图的方法&#xff0c;具体链接为&#xff1a; python学opencv|读取图像&#xff08;八&#xff09;用numpy创建纯黑灰度图-CSDN博客 在…

若依实现图片上传时自动添加水印

文章目录 总体思路1. 修改通用上传方法2. 去除文件路径前两级目录3. 添加水印方法运行效果总结 为了解决图盗用&#xff0c;并有效保护图片版权&#xff0c;若依项目需要实现一个功能&#xff1a;上传图片时&#xff0c;自动在图片上添加水印。这不仅可以有效防止盗用&#xff…

[C#与C++交互] 跨进程通信NamedPipes

目录 1、前言 2、什么是命名管道&#xff1f; 3、实现步骤 4、示例代码 4.1 C 服务器代码 4.2 C# 客户端代码 5、运行步骤 6、注意事项 7、应用场景 8、优缺点 9、总结 1、前言 在 C# 和 C 应用程序之间进行数据交换时&#xff0c;命名管道&#xff08;Named Pipes…

ubuntu+ros新手笔记(三)

系统ubuntu22.04 ros2 humble 1. 设置ubuntu终端字体大小 点击Terminal右上角的三条横线—>Preferences—>Unnamed—>Text—>勾选Custom font—>点击右侧的字号&#xff08;我的显示的是12&#xff09;—>最下方Size处设置字号大小—>Select—>设置完…

使用echarts实现3d柱状图+折线图

以下代码有问题请直接问国内直连GPT/Claude HTML 需要注意threeDchart一定要设置宽度高度&#xff0c;不然图不显示,然后echarts版本不要太低&#xff0c;不然也不显示 <div id"threeDchart" class"threeDchart"></div>js set3DBarChart2(dat…

蓝桥杯新年题解 | 第15届蓝桥杯迎新篇

蓝桥杯新年题解 | 第15届蓝桥杯迎新篇 2024年的蓝桥杯即将拉开序幕&#xff01;对于许多编程爱好者来说&#xff0c;这不仅是一次展示自我能力的舞台&#xff0c;更是一次学习和成长的机会。作为一名大一新生的小蓝&#xff0c;对蓝桥杯充满了期待&#xff0c;但面对初次参赛的…

计算机毕业设计PySpark+PyFlink+Hive地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Hadoop 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【Python】使用Selenium的find_element模块获取网页上的大段文字和表格的方法(建议收藏!)

发现了一个使用Selenium的find_element模块&#xff0c;快速获取文字和表格的方法&#xff0c;很实在&#xff0c;以后爬网的时候&#xff0c;就不用beautifulSoup 和 pandas的read_html 混起来用了&#xff01; 文字部分&#xff1a;实现网络节点下&#xff0c;某个节点下的其…

Pytest-Bdd-Playwright 系列教程(16):标准化JSON报告Gherkin格式命令行报告

Pytest-Bdd-Playwright 系列教程&#xff08;16&#xff09;&#xff1a;标准化JSON报告&Gherkin格式命令行报告 前言一、创建Feature文件二、创建步骤定义文件三、生成Cucumber格式的JSON报告四、使用Gherkin格式的命令行报告五、将BDD报告集成到Jenkins中总结 前言 在自动…

HDR视频技术之七:逆色调映射

HDR 技术近年来发展迅猛&#xff0c;在未来将会成为图像与视频领域的主流。当前 HDR 内容非常短缺&#xff0c;限制了 HDR 视听节目的广泛应用。逆色调映射(Inverse Tone Mapping)应运而生&#xff0c;它是一种用来将 SDR 源信号转换为 HDR 源信号的技术&#xff0c;可以应用于…

迎接全新的 Kotlin 支持 – K2 模式:基本信息

K2 模式有什么作用&#xff1f; K2 模式是 IntelliJ IDEA 中 Kotlin 支持的新实现&#xff0c;它可以提高 IDE 的稳定性&#xff0c;同时也会为支持未来 Kotlin 语言功能奠定基础。 K2 模式与 Kotlin K2 编译器有什么区别&#xff1f; K2 编译器负责编译 Kotlin 语言 2.0 或…

黑马程序员Java项目实战《苍穹外卖》Day12

苍穹外卖-day12 课程内容 工作台Apache POI导出运营数据Excel报表 功能实现&#xff1a;工作台、数据导出 工作台效果图&#xff1a; 数据导出效果图&#xff1a; 在数据统计页面点击数据导出&#xff1a;生成Excel报表 1. 工作台 1.1 需求分析和设计 1.1.1 产品原…

活动报名:Voice Agent 开发者分享会丨RTE Meetup

引入 voice agent 的口语学习应用 Speak 估值已达 10 亿美元 Voice Agent 开发者分享会 一同探索语音驱动的下一代人机交互界面&#xff0c;一场 voice agent builder 的小规模深度交流会。 RTE Meetup 迎来第六期&#xff01;12 月 15 日&#xff08;周日&#xff09;上午&…

优化你的 3D Tiles:性能与质量的平衡

优化你的 3D Tiles&#xff1a;性能与质量的平衡 在现代的三维场景渲染中&#xff0c;3D Tiles 是一种强大的技术&#xff0c;它能以高效、分级加载的方式呈现海量的三维数据。然而&#xff0c;优化 3D Tiles 以实现性能与质量的平衡&#xff0c;却是一个复杂且关键的任务。本…

【K8S系列】在 Kubernetes 中使用 Prometheus 进行监控的详细指南

Prometheus 是一个开源的监控和报警工具&#xff0c;广泛用于 Kubernetes 环境中。本文将深入探讨如何通过 Kubernetes 中的注解配置 Prometheus 的抓取设置&#xff0c;以高效监控服务&#xff0c;并确保系统的可靠性和可维护性。 一、Prometheus 简介 Prometheus 是一个功能…

SEC_ASA 第一天作业

拓扑&#xff1a; 实验需求&#xff1a; 注意&#xff1a;在开始作业之前必须先读“前言”&#xff0c;以免踩坑&#xff01;&#xff01;&#xff01;&#xff08;☞敢点我试试&#xff09; 按照拓扑图配置VLAN连接。 注意&#xff1a;ASA防火墙的 Gi0/1口需要起子接口&#x…

基于STM32设计的工地扬尘与噪音实时监测系统(网页)

一、前言 当前项目使用的相关软件工具、传感器源代码工程已经上传到网盘&#xff08;实时更新项目内容&#xff09;&#xff1a;https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?fromfrom_copylink 1.1 项目开发背景 近年来&#xff0c;随着城市化进程的…

Vue项目打包部署到服务器

1. Vue项目打包部署到服务器 1.1. 配置 &#xff08;1&#xff09;修改package.json文件同级目录下的vue.config.js文件。 // vue.config.js module.exports {publicPath: ./, }&#xff08;2&#xff09;检查router下的index.js文件下配置的mode模式。   检查如果模式改…

KMP 字符串匹配详解

一、KMP 的作用 KMP 用于解决字符串匹配问题&#xff0c;当出现字符串不匹配时&#xff0c;可以知道一部分之前已经匹配的文本内容&#xff0c;可以利用这些信息避免从头再去做匹配了。 二、题目 链接&#xff1a;28. 找出字符串中第一个匹配项的下标 - 力扣&#xff08;Lee…

ElasticSearch01-概述

零、文章目录 ElasticSearch01-概述 1、Elastic Stack &#xff08;1&#xff09;简介 官网地址&#xff1a;https://www.elastic.co/cn/ELK是一个免费开源的日志分析架构技术栈总称&#xff0c;包含三大基础组件&#xff0c;分别是Elasticsearch、Logstash、Kibana。但实际…