N皇后问题 各种优化
0.问题引入
N皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击),问有多少种摆法。
题目链接:https://www.luogu.org/problemnew/show/P1219
1、普通回溯
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
算法思想:
1. 在第k(1≤k≤N)行选择一个位置,判断这个位置是否可以摆,可以摆就进入第 k+1 行,不可以就试下一个位置;
2. 如果一直试到本行最后一个都不行,说明前面k-1行有位置选得不恰当,回到第 k-1 行,试 k-1 行的下一个位置。
3. 反复执行1,2,到最后一行摆上棋子时,说明找到了一个解。
一个问题能用回溯法求解,它的解具有$N$元组的形式,我们使用用$N$元组$(x_1,x_2,...,x_n)$表示问题的解,其中$x_i$表示第$i$行的皇后所处的列号。
核心代码:
//row,col表示当前尝试摆放皇后的行号与列好
bool check(int row, int col) {
for (int i = ; i < row; i++) {
if (x[i] == col)//列冲突
return false;
if (abs(row - i) == abs(col - x[i]))//对角线冲突
return false;
}
return true;
}
void DFS(int k) {
if (k == N + ) {
//获得了一个解
cnt++;
return;
}
for (int i = ; i <= N; i++) {
if (check(k, i)) {
x[k] = i;//标注第k行上第i个位置摆上了皇后
DFS(k + );
}
}
}
1.1 递归实现:
N=11,12的时就顶不住了,嗝屁了。
#include <iostream>
#include <math.h>
using namespace std; int x[];
int N, cnt; bool check(int row, int col) {
//回溯,不会受到后面行的影响
for (int i = ; i < row; i++) {
if (x[i] == col)return false;
if (abs(row - i) == abs(col - x[i]))return false;
}
return true;
}
void DFS(int k) {
if (k == N + ) {
cnt++;
if (cnt <= ) {
for (int i = ; i <= N; i++) {
cout << x[i] << " ";
}
cout << endl;
}
return;
}
for (int i = ; i <= N; i++) {
if (check(k, i)) {
x[k] = i;
DFS(k + );
}
}
} int main() {
cin >> N;
DFS();
cout << cnt << endl;
return ;
}
1.2 非递归实现:
算法优化一般不从这里考虑,因为非递归虽然是会快一点,但也只是那么一点而已,数据量小几乎没有区别,两个都跑不过去。
#include <iostream>
#include <math.h>
using namespace std; int x[];
int N, cnt; bool check(int row, int col) {
//回溯,不会受到后面行的影响
for (int i = ; i < row; i++) {
if (x[i] == col)return false;
if (abs(row - i) == abs(col - x[i]))return false;
}
return true;
} void queen(){
//i表示第几册,j表示在第i层搜索位置
int i = , j = ;
while (i <= N){
while (j <= N){
//如果当前位置合法
if (check(i, j)) {
//把x[i]暂时赋值成j
x[i] = j;
j = ;
break;
}
else
j++;
}
//第i行没有找到可以放置皇后的位置
if (x[i] == ){
//如果回溯到了第0行,说明完成了
if (i == )
break;
//回溯
else{
i--;
j = x[i] + ;//j为上一行的皇后位置+1
x[i] = ;//上一行清零
continue;
}
}
//如果找到了第N层,输出出来
if (i == N){
cnt++;
if (cnt <= ) {
for (int i = ; i <= N; i++) {
cout << x[i] << " ";
}
cout << endl;
}
j = x[i] + ;
x[i] = ;
continue;
}
i++;
}
}
int main() {
cin >> N;
//DFS(1);
queen();
cout << cnt << endl;
return ;
}
2、减半优化
其实仔细看解就不难发现,每个结果总有另一个与之对称。我们可以利用棋盘的对称, 只用回溯一半 。效率能提升50%。
对于第一层,只下该行的前一半的位置即可。但是对于奇数的N,计算出来的结果会将第一行下在中间位置的解算了两遍。所以要单独处理一下。
效率提升不到50%(奇数的情况),并不算多,题目的测试数据只到13,勉强跑过了,但优化还没结束。
#include <iostream>
#include <vector>
#include <math.h>
using namespace std; int x[];
vector<int> v[];
int N, cnt;
int flag, oddCnt; bool check(int row, int col) {
//回溯,不会受到后面行的影响
for (int i = ; i < row; i++) {
if (x[i] == col)return false;
if (abs(row - i) == abs(col - x[i]))return false;
}
return true;
}
void DFS(int k) {
if (k == N + ) {
if (flag&&x[] == (N + ) / ) {
oddCnt++;
if (oddCnt % == )cnt++;
}
else
cnt++;
if (cnt <= ) {
for (int i = ; i <= N; i++) {
cout << x[i] << " ";
v[cnt - ].push_back(x[i]);
}
cout << endl;
}
return;
}
int len = (k == ) ? (N + flag) / : N;
for (int i = ; i <= len; i++) {
if (check(k, i)) {
x[k] = i;
DFS(k + );
}
}
} int main() {
cin >> N;
if (N & )flag = ;
DFS();
for (int i = cnt, j = cnt - ; i < && j >= ; i++, j--) {
for (int k = N - ; k >= ; k--) {
cout << v[j][k] << " ";
}
cout << endl;
} cout << cnt* << endl;
return ;
}
3、优化判断
以本图为例:
每条橙色对角线的行列之差是相同的。
每条蓝色对角线的行列之和是相同的。
用两个bool数组用来记录行列之和为 i 的正斜线、行列之差为 i 的反斜线是否已经被占据。考虑到行列之差可能为负数,棋盘坐标 [x,y] 对应下标 [ x - y + n ]。
再用一个数组记录第 i 列是否有元素。
#include <iostream>
using namespace std; int N, cnt,a[];
//正对角线、副对角线、行
bool x1[], x2[], y[]; void DFS(int k) {
if (k == N + ) {
cnt++;
if (cnt <= ) {
for (int i = ; i <= N; i++) {
cout << a[i] << " ";
}
cout << endl;
}
return;
}
for (int i = ; i <= N; i++) {
//这里x2下标不能用abs,那样是不对的
if (!x1[i + k] && !x2[k - i + N] && !y[i]) {
a[k] = i;
x1[i + k] = ;
x2[k - i + N] = ;
y[i] = ;
DFS(k + );
x1[i + k] = ;
x2[k - i + N] = ;
y[i] = ;
}
}
} int main() {
cin >> N;
DFS();
cout << cnt << endl;
return ;
}
当N较大时,算法会耗费大量的次数在无用的回溯上,时间还是没有显著提高。
4、位运算优化
警告:以下代码可能引起不适,请60岁以下用户在家长陪同下阅读。
位运算是计算机最快的操作,我们可以用数的二进制位表示各纵列、对角线是否可以放置皇后。
看讲解的:https://blog.csdn.net/Hackbuteer1/article/details/6657109 博主讲的很清楚了。
#include <iostream>
#include <queue>
using namespace std; int n, limit, cnt;
int x[], k = ;
//行,左对角线,右对角线
void DFS(int row,int left,int right) {
if (row != limit) {
//row|left|right表示这一行的所有禁止位置,取反再和limit按位与,得到的是该行可以放的几个位置
int pos = limit & ~(row | left | right);
//每一个可以摆的位置,都要做一次
while (pos) {
//找到的可以放皇后的位置(pos二进制最右边的一个1)
int p = pos & -pos;// pos & (~pos+1);
//把这一位置0,表示不为空
pos &= pos - ;//pos=pos-p;
//把p所在row,left,right的位都置1。
//(left | p)<< 1 是因为这一行由左对角线造成的禁止位在下一行要右移一下;right同理
DFS(row | p, (left | p) << , (right | p) >> );
}
}
else {
cnt++;
}
} int main() {
cin >> n;
limit = ( << n) - ;
DFS(, , );
cout << cnt << endl;
return ;
}
#include <iostream>
#include <queue>
using namespace std; int n, limit, cnt;
int x[], k = ;
//行,左对角线,右对角线
void DFS(int row,int left,int right) {
if (row != limit) {
int pos = limit & ~(row | left | right);
while (pos) {
//找到的可以放皇后的位置
int p = pos & -pos;// pos & (~pos+1);
pos &= pos - ;
if (cnt < ) {
int t = p, num = ;
while (t != ) {
num++;
t >>= ;
}
x[k++] = num;
}
DFS(row | p, (left | p) << , (right | p) >> );
if (cnt < ) k--;
}
}
else {
if (cnt < ) {
for (int i = ; i <= n; i++) {
cout << x[i] << " ";
}
cout << endl;
}
cnt++;
}
} int main() {
cin >> n;
limit = ( << n) - ;
DFS(, , );
cout << cnt << endl;
return ;
}
果然名不虚传~
N皇后问题 各种优化的更多相关文章
- N皇后-位运算优化
N皇后问题 时间限制: 5 Sec 内存限制: 128 MB 题目描述 魔法世界历史上曾经出现过一个伟大的罗马共和时期,出于权力平衡的目的,当时的政治理论家波利比奥斯指出:“事涉每个人的权利,绝不应 ...
- 回溯算法-C#语言解决八皇后问题的写法与优化
结合问题说方案,首先先说问题: 八皇后问题:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 嗯,这个问题已经被使用各种语言解 ...
- PAT (Advanced Level) 1128~1131:1128N皇后 1129 模拟推荐系统(set<Node>优化) 1130 中缀表达式
1128 N Queens Puzzle(20 分) 题意:N皇后问题.按列依次给定N个皇后的行号,问N个皇后是否能同时不存在行冲突.列冲突和主副对角线冲突. 分析: 1.根据题意一定不存在列冲突,所 ...
- N皇后解法以及位运算优化
N皇后解法以及位运算优化 观察棋盘,要求皇后之间不能处在同行同列同一条斜线,求使得每行都有一个皇后的放置方法共有多少种. 每尝试放置一个皇后,都可以把该位置所在的行.列标号用一个数组标记,含义表示该行 ...
- 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,循环控制及其优化
上两篇博客 8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,数据结构“栈”实现 研究了递归方法实现回溯,解决N皇后问题,下面我们来 ...
- P1219 八皇后 含优化 1/5
题目描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序列2 4 6 1 3 ...
- N皇后问题的二进制优化详细思路
题目啊常规解法(DFS)在此就不赘述了... 直接进入正题. 众所周知,N皇后是NP完全类问题,n稍微大了点求解过程就会变得很长. 算法方面很难再有质的效率突破,但这不妨在其他细节上下下功夫. 揆诸常 ...
- 【位运算经典应用】 N皇后问题
说到位运算的经典应用,不得不说N皇后问题. 学过程序设计的都知道N皇后问题,没听过也没关系.很简单,最传统的的N皇后问题是这个样子的,给你一个n * n大小的board,让你放n个皇后(国际象棋),要 ...
- 八皇后,回溯与递归(Python实现)
八皇后问题是十九世纪著名的数学家高斯1850年提出 .以下为python语句的八皇后代码,摘自<Python基础教程>,代码相对于其他语言,来得短小且一次性可以打印出92种结果.同时可以扩 ...
随机推荐
- C# 往线程里传参数的方法总结
Thread (ParameterizedThreadStart) 初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托. Thread (ThreadStart) 初始化 ...
- 基于Spring Security2与 Ext 的权限管理设计与兑现
基于Spring Security2与 Ext 的权限管理设计与实现 一.Spring Security介绍 Spring Security的前身Acegi,其配置及使用相对来说复杂一些,因为要配置的 ...
- Median(vector+二分)
Median Time Limit: 5 Seconds Memory Limit: 65536 KB The median of m numbers is after sorting them in ...
- Java虚拟机 - Javac编译与JIT编译
[深入Java虚拟机]之七:Javac编译与JIT编译 编译过程 不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤 ...
- python基础技巧综合训练题1
1,大小写翻转 >>> str='hello,GhostWU' >>> str.swapcase() 'HELLO,gHOSTwu' 2,从一串字符串中,提取纯数字 ...
- Vue: 生命周期, VueRouter
Vue实例的生命周期: beforeCreate: 实例创建之前除标签外,所有的vue实例需要的数据,事件都不存在 created: 实例被创建之后,data和事件已经被解析到,el还没有找到 ...
- 第三十天- 进程 Process模块 空间隔离
1.进程: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体: ...
- BZOJ2208: [Jsoi2010]连通数(tarjan bitset floyd)
题意 题目链接 Sol 数据水的一批,\(O(n^3)\)暴力可过 实际上只要bitset优化一下floyd复杂度就是对的了(\(O(\frac{n^3}{32})\)) 还可以缩点之后bitset维 ...
- 【读书笔记】iOS-网络-异步请求与运行循环
异步请求需要运行循环.当数据传递到服务器或是被客户端接收时,运行循环用于实现事件与委托对象之间的通信.异步请求在发出时,会在当前线程的运行循环上操作,这个实现细节是很重要的,因为在GCD块中或者是通过 ...
- Android Studio 关联 JDK Java 源码
Android Studio 关联 Android 源码比较方便,一般下载后可自动关联,但是 Android Studio 默认使用的 JDK 是内嵌的,是不带源码的.所以在查看 JDK 源码时,看到 ...