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


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;
             }
          }
    }
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值