今天刷leetcode时,碰到了n皇后问题,上网发现了一个非常给力的算法。
算法的核心:使用bit数组来代替以前由int或者bool数组来存储当前格子被占用或者说可用信息,从这
可以看出N个皇后对应需要N位表示。
巧妙之处在于:以前我们需要在一个N*N正方形的网格中挪动皇后来进行试探回溯,每走一步都要观察
和记录一个格子前后左右对角线上格子的信息;采用bit位进行信息存储的话,就可以只在一行格子也
就是(1行×N列)个格子中进行试探回溯即可,对角线上的限制被化归为列上的限制。
程序中主要需要下面三个bit数组,每位对应网格的一列,在C中就是取一个整形数的某部分连续位即可
。
row用来记录当前哪些列上的位置不可用,也就是哪些列被皇后占用,对应为1。
ld,rd同样也是记录当前哪些列位置不可用,但是不表示被皇后占用,而是表示会被已有皇后在对角线
上吃掉的位置。这三个位数组进行“或”操作后就是表示当前还有哪些位置可以放置新的皇后,对应0
的位置可放新的皇后。如下图所示的8皇后问题求解得第一步:
row: [ ][ ][ ][ ][ ][ ][ ][*]
ld: [ ][ ][ ][ ][ ][ ][*][ ]
rd: [ ][ ][ ][ ][ ][ ][ ][ ]
------------------------------------
row|ld|rd: [ ][ ][ ][ ][ ][ ][*][*]
所有下一个位置的试探过程都是通过位操作来实现的,这是借用了C语言的好处,详见代码注释。
我的leetcode代码:
class Solution {
public:
int maxLimit; //代表排序完毕的最大值,每位对应每列,如n=2,则 maxLimit = (1<<n)-1为3,即二进制11代表两列
/*DFS求解,ans为最后vector结果,temp为每一组可行的解,row代表已占据的列情况,leftCross代表左对角线占用情况,rightCross代表右对角线占用情况 */
void DFS(vector<vector<string>> &ans,vector<string> &temp,int row,int leftCross,int rightCross)
{
if(row==maxLimit) //当每一列都有皇后即每一位都为1时,则将这组解存入结果vector
{
ans.push_back(temp);
return ;
}
int cur,p,pos;
string str;
//获得可以将皇后放置的列,例如1110代表前三列可以放置,最后一列不可以放置
cur = maxLimit&(~(row|leftCross|rightCross));
while (cur) {
//获得右数第一位不是0,即1的位置,此次在该位置放置一个皇后(-cur为负数,在内存里是以补码形式存储的,补码=原码(除符号位)取反+1)
2者相与刚好取得最后为1的bit,其他为0
p = cur&(-cur);
//将该位置置0,用于下一次迭代
cur = cur - p;
//保存str作为输出,例如p=0010,maxLimt=111,则pos=1010用于输出str
pos=p+maxLimit+1;
str = "";
while(pos>1)
{
str += ((pos&1)?'Q':'.');
pos>>=1;
}
//将str存入temp
temp.push_back(str);
//更改row,leftCross,rightCross信息,进行下次深度遍历迭代
DFS(ans,temp,row+p,(leftCross+p)<<1,(rightCross+p)>>1);
//弹出temp中的第一个str,使得下次循环更新新的str用于迭代
temp.pop_back();
}
}
vector<vector<string> > solveNQueens(int n)
{
vector<vector<string>> ans;
vector<string> temp;
maxLimit = (1<<n)-1;
DFS(ans,temp,0,0,0);
return ans;
}
};
附上经典的解法用于对比:
#include <iostream>
using namespace std;
long sum=0;
bool Place(int k,int *p){//判断是否有冲突发生。k是当前摆放行
for(int j=0;j<k;j++)//p[j]==p[k]说明第j行与第k行放在同一列
if(abs(k-j)==abs(p[j]-p[k])||p[j]==p[k])
//abs(k-j)==abs(p[j]-p[k])说明斜率为+-1,说明在同一列
return false;
return true;
}
void Backtrack(int t,int *p,int n){
if(t>=n)//棋盘共有n行,用0~n-1表示,故如果t到达n,递归返回
sum++;//增加一次成功次数
else
for(int i=0;i<n;i++){//对第t行进行摆放尝试,可能情况是0~n-1
p[t]=i;
if(Place(t,p))Backtrack(t+1, p,n);//如果无冲突,摆放t+1行
}
}
int nQueen(int n){
int *p=new int[n];//数组p[i]表示棋盘第i行皇后的位置
for(int i=0; i<n;i++)
p[i]=0;//将棋盘数组初始化为0;
Backtrack(0, p, n);//回溯求解,从第0行开始摆放
delete [] p;
return sum;
}
int main(){
cout<<nQueen(8);
return 0;
}