当前位置: 首页 > news >正文

N皇后问题(位运算版本)

问题描述

在一个  的国际象棋棋盘上放置  个皇后,使得任意两个皇后都不能处于同一行、同一列或同一斜线上。目标是找出所有满足该条件的皇后放置方案。

时间复杂度是O(n!),对于n 乘 n的格子,每行都有n种选择,选择可能依次递减。同时要便随着大量剪枝。

一,经典解法

调用递归,使用类的成员变量或全局变量,引用传参等方法收集可能。

里面关于如何判断两个皇后是否在同一条斜线上,利用共斜线的话是斜率等于1或-1,利用斜率,即两个的坐标:行的变化量等于列的变化量。

要有封装和简化的思想。将原来代码量大的函数抽离一部分封装成函数,既使代码简洁,又方便后面修改。

思路

依次检验每个坐标符不符合条件,如果符合就收集进temp临时向量中,知道遇到终止条件后将temp添加进ans里,return返回。

代码

class NQueen
{
public:
	NQueen(int N);
	void printAns();
	~NQueen() = default;
private:
	int N;
	std::vector<std::vector<std::pair<int,int>>> ans;
	std::vector<std::pair<int,int>> temp;
	void recursion(int row, int col);
	bool isValid(int row, int col, std::vector<std::pair<int,int>>& temp);
};
NQueen::NQueen(int N) : N(N)
{
}

void NQueen::printAns()
{
	recursion(0, 0);
	std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
	for (const auto& it : ans)
	{
		for (const auto& jt : it)
		{
			board[jt.first][jt.second] = 1;
		}

		for (int i = 0; i < N; i++)
		{
			for (int j = 0; j < N; j++)
			{
				if (board[i][j] == 1) cout << "Q ";
				else cout << "* ";
			}
			cout << endl;
		}

		for (int i = 0; i < 2 * N - 1; i++)
		{
			cout << "=";
		}
		cout << endl;
		board.clear();
		board.resize(N, std::vector<int>(N, 0));
	}

}

void NQueen::recursion(int row, int col)
{
	if (row == N)
	{
		ans.push_back(temp);
		return;
	}
	
	for (int i = 0; i < N; i++)
	{
		if (isValid(row, i, temp))
		{
			temp.push_back(std::make_pair(row, i));
			recursion(row + 1, col);
			temp.pop_back();
		}
	}
}

bool NQueen::isValid(int row, int col, std::vector<std::pair<int, int>>& temp)
{
	for (const auto& it : temp)
	{
		if (it.first == row || it.second == col || abs(it.first - row) == abs(it.second - col))
		{
			return false;
		}
	}
	return true;
}

二,位运算版本

前置知识

可以看下我的博客有关于位运算的那几节。

位运算实现加减乘除 

位图的学习

位运算的骚操作

首先我应该明白int类型数字底层是二进制数字0和1的组合。

int类型4字节,32bit。

思路

根据要求可以知道每行最多一个皇后,而又是递归,递归时行数row要更新加一。所以行上面没有限制,再后面temp.push_back()时只需要考虑列和斜对角的限制。

利用数字bit位上1和0来表示该位置能否放置皇后,跟我之前字符串的递归子序列中标记数组一样的功能。

同时利用位运算来更新限制。

列和斜对角线的影响在本层递归放置皇后后,进行更新限制。利用&和|逻辑运算合并这些限制。

通过位移来实现斜对线的更新

直接上代码

优势

为什么更快,虽然递归是通过值传递来传递影响但是位运算太快,弥补这上面的缺陷。

代码         

注释版

// 递归求解 N 皇后问题的函数
// row: 当前正在处理的行号
// left: 表示从左上到右下对角线方向上的冲突信息,使用位运算存储
// right: 表示从右上到左下对角线方向上的冲突信息,使用位运算存储
// col: 表示列方向上的冲突信息,使用位运算存储
void NQueen::recursion2(int row, int left, int right, int col)
{
    // 当 row 等于 N 时,说明已经成功放置了 N 个皇后,找到了一个解
    if (row == N)
    {
        // 将当前的皇后放置方案添加到结果集合 ans 中
        ans.push_back(temp);
        // 回溯,结束当前递归调用
        return;
    }

    // 计算当前行所有不能放置皇后的位置
    // left | right | col 是将三个方向的冲突信息进行按位或运算
    // 得到的结果中,为 1 的位表示该位置不能放置皇后
    int limit = left | right | col;

    // 遍历当前行的每一列
    for (int i = 0; i < N; i++)
    {
        // 检查当前位置 (row, i) 是否可以放置皇后
        // isValid 函数用于判断该位置是否与已放置的皇后冲突
        if (isValid(limit, i))
        {
            // 如果该位置可以放置皇后,将其添加到临时方案 temp 中
            temp.push_back(std::make_pair(row, i));

            // 计算下一行在各个方向上的冲突信息
            // (left | (1 << i)) << 1: 将当前列的左对角线冲突信息更新
            // 1 << i 表示将 1 左移 i 位,得到当前列的位置信息
            // 与 left 按位或运算后,再左移一位,表示下一行的左对角线冲突
            int _left = (left | (1 << i)) << 1;

            // (right | (1 << i)) >> 1: 将当前列的右对角线冲突信息更新
            // 与 left 类似,只不过是右移一位,表示下一行的右对角线冲突
            int _right = (right | (1 << i)) >> 1;

            // (col | (1 << i)): 将当前列的列冲突信息更新
            // 表示该列已经被占用
            int _col = (col | (1 << i));

            // 递归调用 recursion2 函数,处理下一行
            recursion2(row + 1, _left, _right, _col);

            // 回溯操作,撤销当前选择
            // 将该位置从临时方案 temp 中移除,尝试其他可能的位置
            temp.pop_back();
        }
    }
}
class NQueen
{
public:
	NQueen(int N);
	void printAns();
	void printAnsByBit();
	~NQueen() = default;
private:
	int N;
	std::vector<std::vector<std::pair<int,int>>> ans;
	std::vector<std::pair<int,int>> temp;
	void recursion(int row, int col);
	void recursion2(int row, int left, int right, int col);
	bool isValid(int row, int col, std::vector<std::pair<int,int>>& temp);
	void moveBit(int&, int);
	bool isValid(int, int);
};
void NQueen::printAnsByBit()
{
	recursion2(0, 0, 0, 0);
	std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
	for (const auto& it : ans)
	{
		for (const auto& jt : it)
		{
			board[jt.first][jt.second] = 1;
		}

		for (int i = 0; i < N; i++)
		{
			for (int j = 0; j < N; j++)
			{
				if (board[i][j] == 1) cout << "Q ";
				else cout << "* ";
			}
			cout << endl;
		}

		for (int i = 0; i < 2 * N - 1; i++)
		{
			cout << "=";
		}
		cout << endl;
		board.clear();
		board.resize(N, std::vector<int>(N, 0));
	}

}


void NQueen::printAnsByBit()
{
	recursion2(0, 0, 0, 0);
	std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
	for (const auto& it : ans)
	{
		for (const auto& jt : it)
		{
			board[jt.first][jt.second] = 1;
		}

		for (int i = 0; i < N; i++)
		{
			for (int j = 0; j < N; j++)
			{
				if (board[i][j] == 1) cout << "Q ";
				else cout << "* ";
			}
			cout << endl;
		}

		for (int i = 0; i < 2 * N - 1; i++)
		{
			cout << "=";
		}
		cout << endl;
		board.clear();
		board.resize(N, std::vector<int>(N, 0));
	}

}

相关文章:

  • Tips :仿真竞争条件 指的是什么?
  • FFmpeg+vvenc实现H.266的视频编解码教程
  • SAP-ABAP:使用ST05(SQL Trace)追踪结构字段来源的步骤
  • JavaScript系列(88)--Babel 编译原理详解
  • 5秒修改文件默认打开方式-windows版
  • 蓝桥杯 Java B 组之回溯剪枝优化(N皇后、数独)
  • Milvus x DeepSeek 搭建低成本高精度 RAG 实战
  • 【部署优化篇十三】深度解析《DeepSeek API网关:Kong+Nginx配置指南》——从原理到实战的超详细手册
  • androidstudio 运行项目加载很慢,优化方法
  • Golang适配达梦数据库连接指定模式
  • 第十章:服务器消费者管理模块
  • 基于Springboot的游戏分享网站【附源码】
  • Java如何解决彻底解决,大数据量excel导出内存溢出问题
  • 【前端】页面结构管理
  • C# 打印Word文档 – 4种打印方法
  • 知识管理接入DeepSeek大模型,能够带来什么新体验?
  • 人工智能的无声基石:被低估的数据革命
  • ubuntu 安全策略(等保)
  • 最新Java面试题,常见面试题及答案汇总
  • 蓝桥杯 Java B 组之记忆化搜索(滑雪问题、斐波那契数列)
  • 国家卫生健康委通报关于肖某引发舆情事件调查处置进展情况
  • 1至4月全国铁路发送旅客14.6亿人次,创同期历史新高
  • 端午假期购票日历发布,今日可购买5月29日火车票
  • 文化润疆|为新疆青少年提供科普大餐,“小小博物家(喀什版)”启动
  • 商人运作亿元“茅台酒庞氏骗局”,俩客户自认受害人不服“从犯”判决提申诉
  • 金砖国家召开经贸联络组司局级特别会议,呼吁共同抵制单边主义和贸易保护主义