BeMyself1

 

迭代法解n皇后问题

今天要写的是算法是源于八皇后问题,但在这里为了说明普遍性,直接介绍N皇后问题,与八皇后问题思路一样。
一、问题描述:
    在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于再n×n的棋盘上放置n个皇后,任何2个皇后不妨在同一行或同一列或同一斜线上。
输入:
    给定棋盘的大小n (n ≤ 13)
输出:
    输出有多少种放置方法。
二、解题思路:
    要解决N皇后问题,其实就是要解决好怎么放置这n个皇后,每一个皇后与前面的所有皇后不能在同一行、同一列、同一对角线,在这里我们可以以行优先,就是说皇后的行号按顺序递增,只考虑第i个皇后放置在第i行的哪一列,所以在放置第i个皇后的时候,可以从第1列判断起,如果可以放置在第1个位置,则跳到下一行放置下一个皇后。如果不能,则跳到下一列...直到最后一列,如果最后一列也不能放置,则说明此时放置方法出错,则回到上一个皇后向之前放置的下一列重新放置。此即是回溯法的精髓所在。当第n个皇后放置成功后,即得到一个可行解,此时再回到上一个皇后重新放置寻找下一个可行解...如此后,即可找出一个n皇后问题的所有可行解。
三、复杂度分析:
    关于N皇后问题的复杂度问题可以说是众说纷纭了,自己也改变过好几次,刚开始以为棋盘是n行n列,所以理所当然应该是n^2,后来发现在每列选择可否放置的比较上又做了一次循环,所以应该是n^3,但想了很久,发现判断可否放置的时候不是每次都循环到n,它是根据皇后i的取值而变化的,所以复杂度应该是1/3 n^3左右,即是小于n^3的。
四、测试代码:
    在这里我写了两个实现方法,一个是递归回溯,一个是迭代回溯,思路都一样,只是形式不同罢了。
递归回溯:

// N皇后问题.cpp : Defines the entry point for the console application.

//回溯法解n皇后问题1

 

#include "stdafx.h"

#include <iostream>

#include <math.h>

using namespace std;

 

 

class Queen

{

       friend int nQueen(int);

private:

       bool Place(int k);

       void Backtrack(int t);

       int n,       //皇后个数

              *x;   //当前解

       long sum;       //当前已找到的可行方案数

};

//判断函数,判断第k个皇后是否可以放在某一个位置k

//如果与之前的皇后出现在同一列或同一对角线则放置失败,返回false,否则返回true

bool Queen::Place(int k)

{

       for(int j=1;j<k;j++)

       {

              if((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))

                     return false;

       }

       return true;

}

void Queen::Backtrack(int t)

{

       if(t>n && n>0)      //当放置的皇后超过n时,可行解个数加1

       {

              sum++;

              cout<<""<<sum<<"种放法为:"<<endl;

              for(int i=1;i<=n;i++)

              {

                     cout<<x[i]<<" ";

              }

              cout<<endl;

       }

       else

       {

              for(int i=1;i<=n;i++)

              {

                     x[t]=i;                   //表明第t个皇后放在第i

                     if(Place(t))     //如果可以放在某一位置,则继续放下一皇后

                     {

                            Backtrack(t+1);

                     }

              }

       }

}

 

int nQueen(int n)

{

 

       Queen X;

       //初始化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;

}

 

void main()

{

       cout<<"***回溯法解n皇后问题1***"<<endl;

       int n;

       int *x;

       int i;

       while(i)

       {

              cout<<"请输入皇后的个数:"<<endl;

              cin>>n;

              x=new int[n];

              int b=nQueen(n);

              cout<<"问题的解有总共有:"<<b<<""<<endl;

              cout<<"Press<1> to run again "<<endl;

              cout<<"Press<0> to exit"<<endl;

              cin>>i;

       }

 


迭代回溯:

// n皇后问题2.cpp : Defines the entry point for the console application.

//回溯法解n皇后问题2(迭代回溯)

 

#include "stdafx.h"

#include <iostream>

#include <math.h>

using namespace std;

 

class Queen

{

       friend int nQueen(int);

       private:

              bool Place(int k);

              void Backtrack(void);

              int n,       //皇后的个数

                     *x;   //当前解

              long sum;

 

};

 

bool Queen::Place(int k)

{

       for(int j=1;j<k;j++)

       {

              if((abs(j-k)==abs(x[j]-x[k]))||(x[j]==x[k]))

                     return false;

       }

       return true;

}

 

void Queen::Backtrack(void)

{

       x[1]=0;

       int k=1;

       while(k>0)

       {

              x[k]+=1;

              while((x[k]<=n) && !(Place(k)))

              {

                     x[k]+=1;

              }

              if(x[k]<=n)

              {

                     if(k==n)

                     {

                            sum++;

                            cout<<""<<sum<<"种放法为:"<<endl;

                            for(int i=1;i<=n;i++)

                            {

                                   cout<<x[i]<<" ";

                            }

                            cout<<endl;

                     }

                     else

                     {

                            k++;

                            x[k]=0;

                     }

              }

              else

                     k--; 

       }

}

 

int nQueen(int n)

{

       Queen X;

       //初始化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();

       delete []p;

       return X.sum;

 

}

 

void main()

{

       cout<<"***回溯法解n皇后问题2(迭代回溯)***"<<endl;

       int n;

       int *x;

       int i;

       while(i)

       {

              cout<<"请输入皇后的个数:"<<endl;

              cin>>n;

              x=new int[n];

              int b=nQueen(n);

              cout<<"问题的解有总共有:"<<b<<""<<endl;

              cout<<"Press<1> to run again "<<endl;

              cout<<"Press<0> to exit"<<endl;

              cin>>i;

       }

}

 迭代回溯的注释因为和递归回溯差不多,所以就不再附注了。在这里我们可以看到,递归回溯非常简单,结构很清晰,但它有一个潜在的问题存在,即当随着变量n的增大,递归法的复杂度也将成几何级增长,也有可能会出现重复的情况,所以我们在解决问题时,如果能用迭代法解决,最好还是不要用递归法,除非你已经对这个递归了如指掌了。
    通过这个N皇后问题,我想大概已经把回溯法讲得很清楚了吧,回溯法得到的解展开就是一个树,很多方法都是可以通过回溯法来解决的,效率很高,但如果基数过大的话,回溯法就显得不是那么适用了,这也是回溯法的弱势吧。比如说这个N皇后问题,好像当n>60的时候,回溯法就不能完全地解决问题了,这时可以考虑用概率算法来解决,它可以解决很大的基数,只不过结果不是很精确而已。所以我们在面对一个问题时,具体是使用什么算法还是要结合实际情况来考虑的,目的都是更方便、更准确地解决问题。

posted on 2011-10-11 17:50  BeMyself1  阅读(2193)  评论(0编辑  收藏  举报

导航