您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法[LeetCode]—N_Queen问题

2013-11-03 21:31 417 查看




N-Queen

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.



Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a distinct board configuration of the n-queens' placement, where
'Q'
and
'.'
both
indicate a queen and an empty space respectively.
For example,

There exist two distinct solutions to the 4-queens puzzle:
[
[".Q..",  // Solution 1
"...Q",
"Q...",
"..Q."],

["..Q.",  // Solution 2
"Q...",
"...Q",
".Q.."]
]


深度搜索的基本框架:

DFS(Node)

if(Node=目标节点

then //找到目标,结束

for each next∈d[Node]

do DFS(next);

end

下面贴出两个算法:
第一个简单,但是效率低一点。
用元组x[1:n]表示n后的问题的解。其中,x[i]表示皇后i放在棋盘的第i行的第x[i]列。一行只有一个后,则逐次考虑不同的行则可避免同行问题。由于不允许2个后放在同一列上,所以解向量中的x[i]互不相同。2个皇后不在同一个斜线上是问题的隐约束。假设,从左上角到右下角依次从1-n编号,设主对角线为从左上到右下,在主对角线及平行线上,2个下标值的差(行号-列号)值相等。同理,次对角线为右上角到左下角,2个下标的值的和(行号+列号)相等。因此,若2个皇后分别为(i,j)(k,l)且有i-j=k-l或者i+k=k+l,则说明2个皇后处于同一个斜线上。由此可知,只要|i-k|=|j-l|,就表明连个皇后位于同一条斜线上。
用回朔法解n后问题时,用完全n叉树表示解空间。可行性约束Place剪去不满足行、列和斜线约束的子树。
递归函数backtrack(i)实现对整个解空间的回朔搜索。backtrack(i)搜索解空间中第i层子树。在backtrack()算法中:
(1)当i>.n时,算法搜索至夜节点,得到一个新的n皇后互不攻击的方案,当前已找到的可行方案数sum增1;
(2)当i<=n时,由Place检查其可行性,并以深度优先的方式递归地对可行子树搜索。
本算法只计算了可行解的数目,并未打印所有解方案。
#include<iostream>
#include<cmath>
using namespace std;

class Queen{
friend int nQueen(int);
private:
bool Place(int k);
void Backtrack(int t);
int n,     //皇后的个数
*x;   //当前解x[i]
int sum;  //当前已找到的可行方案数
};

bool Queen::Place(int k)  //判断第K行的x[k]的可行性
{
for(int i=1;i<k;i++)
if((x[k]==x[i])||abs(i-k)==abs(x[i]-x[k]))return false;
return true;

}

void Queen::Backtrack(int t)
{
if(t>n)sum++;
else
for(int i=1;i<=n;i++){
x[t]=i;
if(Place(t))Backtrack(t+1);
}
}

int nQueen(int n)
{
Queen X;

//初始化
X.n=n;
X.sum=0;
int *p=new int [n+1];
for(int i=0;i<=n;i++)
p[i]=0;
X.x=p;
X.Backtrack(1);
delete [] p;
return X.sum;}


第二个方案相等效率较高。因为它记录了所有先前已经占据的列、占据的主对角线、次对角线。不用每次重新遍历进行绝对值相减计算。那么就要求对主次对角线的编号存储方案有一定的考虑。任何编号方案都可,查询和设置一致即可。本方案打印了出了所有方案,但并未计算总方案数目(容易实现)。本算法参考https://gitcafe.com/soulmachine/LeetCode。
#include<iostream>
#include<string>
#include<vector>
using namespace std;

class Solution {
public:
vector<vector<string> > solveNQueens(int n) {
// Note: The Solution object is instantiated only once and is reused by each test case.
vector<vector<string> > ret;
this->columns=vector<int>(n,0);
this->principal_diagonals=vector<int>(2*n,0);
this->counter_diagonals=vector<int>(2*n,0);

vector<int> C(n,0);  //C[i]表示第i行皇后所在列的编号
DFS(0,C,ret);
return ret;
}
private:
//三个变量用于剪枝
vector<int> columns;   //表示已经放置的皇后占据了哪些列
vector<int> principal_diagonals; //占据了哪些主对角线
vector<int> counter_diagonals; //占据了哪些副对角线
void DFS(int row,vector<int> &C,vector<vector<string> > &ret){
const int N =C.size();
if(row==N){
vector<string> solution;
for(int i=0;i<N;++i){
string s(N,'.');
for(int j=0;j<N;++j){
if(j==C[i])s[j]='Q';
}
solution.push_back(s);
}
ret.push_back(solution);
return;
}
for(int j=0;j<N;++j){    //一列一列的验证 坐标为(raw,j)
const bool ok=columns[j]==0 &&
principal_diagonals[j-row+N]==0 &&   //对角线怎么编号可以自己规划不同的方案但前后一致
counter_diagonals[row+j]==0;
if(ok) {  //合法,继续递归
C[row]=j;
columns[j]=1;
principal_diagonals[j-row+N]=1;
counter_diagonals[row+j]=1;
DFS(row+1,C,ret);
//撤销动作
//c[row]=0; //没有必要,总是会继续覆盖,最终会被成功的一组覆盖。
columns[j]=principal_diagonals[j-row+N]=counter_diagonals[row+j]=0;
}
}

}
};


当然,还有很多更有优异的方案需要思考。

N-Queens II



Follow up for N-Queens problem.

Now, instead outputting board configurations, return the total number of distinct solutions.



本题并不要求求出所有解的结果,只需要计算解的总个数。在上一题的基础上,简化了格式化输出的部分代码。只需要用一个全局计数器,当一种方案确定可行时,计数器+1。
class Solution {
public:
int  totalNQueens(int n) {
this->col=vector<int>(n,0);
this->principal=vector<int>(2*n,0);
this->counter=vector<int>(2*n,0);

int counts=0;
vector<int> C(n,0);  //C[i] 表示第i行皇后所在列编号
DFS(0,C,counts);
return counts;
}
private:
vector<int> col;  //已经放置的皇后,占据了哪些列
vector<int> principal;  //已经放置的皇后,占据哪些正对角线
vector<int> counter; //已经放置的皇后,占据了哪些负对角线
void  DFS(int row,vector<int> &C,int &counts){
const int N=C.size();
if(row==N){
counts++;
return;
}

for(int j=0;j<N;j++){ //一列列的验证,坐标为(row,j)
bool ok=col[j]==0 && principal[j-row+N]==0 && counter[j+row]==0;
if(ok){
C[row]=j;
col[j]=1;
principal[N+j-row]=1;
counter[j+row]=1;
DFS(row+1,C,counts);
//撤销动作
C[row]=0;
col[j]=principal[N+j-row]=counter[row+j]=0;
}
}
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: