n皇后2种解题思路与代码-Java与C++实现

 林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

           摘要:本文主要讲了n皇后问题的解题思路,并分别用java和c++实现了过程,最后,对于算法改进,使用了位运算。

一、问题抛出与初步解题思路

问题描述:八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。

《n皇后2种解题思路与代码-Java与C++实现》

转化规则:其实八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当 n = 1 或 n ≥ 4 时问题有解。令一个一位数组a[n]保存所得解,其中a[i] 表示把第i个皇后放在第i行的列数(注意i的值都是从0开始计算的),下面就八皇后问题做一个简单的从规则到问题提取过程。

 

 

(1)因为所有的皇后都不能放在同一列,因此数组的不能存在相同的两个值。
(2)所有的皇后都不能在对角线上,那么该如何检测两个皇后是否在同一个对角线上?我们将棋盘的方格成一个二维数组,如下:

 

 

《n皇后2种解题思路与代码-Java与C++实现》

假设有两个皇后被放置在(i,j)和(k,l)的位置上,明显,当且仅当|i-k|=|j-l| 时,两个皇后才在同一条对角线上。

 

二、代码与结果

 

(1)C++版本

运行平台:VS2013

操作系统:Windows7

 

[cpp] 
view plain
 copy  

  1. /** 
  2.  * n皇后问题解决 
  3.  * @author lin 
  4.  * 
  5.  */  
  6.   
  7. #include <iostream>  
  8. #include <cmath>  
  9. #include<time.h>  
  10.   
  11. using namespace std;  
  12. /**皇后的数目*/  
  13. static int num;  
  14. /**下标i表示第几行,x[i]表示第i行皇后的位置,注意此处0行不用*/  
  15. static int *x;  
  16. /**解的数目*/  
  17. static int sum = 0;  
  18.   
  19.   
  20. /** 
  21.  * 判断第k行皇后可以放置的位置 
  22.  * @param k k表示第k行,X[K]k表示第k行上皇后的位置 
  23.  * @return boolean false表示此处不能放置皇后 
  24.  */  
  25. bool place( int k )  
  26. {  
  27.     for ( int j = 1; j < k; j++ )  
  28.     {  
  29.         /* 如果当前传入的第K行上的皇后放置的位置和其它皇后一个对角线(abs(x[k]- x[j])==abs(k-j)或一个直线上(x[j] == x[k]) */  
  30.         if ( abs( x[k] – x[j] ) == abs( k – j ) || x[j] == x[k] )  
  31.         {  
  32.             return(false);  
  33.         }  
  34.     }  
  35.     return(true);  
  36. }  
  37.   
  38.   
  39. /** 
  40.  * 一行一行的确定该行的皇后位置 
  41.  * @param t 
  42.  */  
  43. void backtrack( int t )  
  44. {  
  45.     if ( t > num )                  /* 如果当前行大于皇后数目,表示找到解了 */  
  46.     {  
  47.         sum++;  
  48.         /* 依次打印本次解皇后的位置 */  
  49.         for ( int m = 1; m <= num; m++ )  
  50.         {  
  51.             //cout << x[m];   /* 这一行用输出当递归到叶节点的时候,一个可行解 */  
  52.             //这里只是为了好看才写成下面的  
  53.             for(int k =1; k <= num;k++){  
  54.                 if(k == x[m]){  
  55.                     cout << x[m] <<” “;   
  56.                 }else {  
  57.                     cout << “* “;//用*表示没有被用到的位置   
  58.                 }  
  59.             }  
  60.             cout << endl;  
  61.   
  62.         }  
  63.         cout << endl;  
  64.     } else {  
  65.         for ( int i = 1; i <= num; i++ )  
  66.         {  
  67.             x[t] = i;       /* 第t行上皇放在i列处 */  
  68.             if ( place( t ) )  
  69.             {  
  70.                 /* 此处的place函数用来进行我们上面所说的条件的判断,如果成立,进入下一级递归 */  
  71.                 backtrack( t + 1 );  
  72.             }  
  73.         }  
  74.     }  
  75. }  
  76.   
  77.   
  78. int main()  
  79. {  
  80.     cout<<“请输入皇后数目:”;  
  81.     cin>>num;   
  82.   
  83.     clock_t start,finish;  
  84.     double totaltime;//计算程序运行时间  
  85.     start=clock();//起始时间  
  86.   
  87.     x   = new int[num + 1];     /* 此处注意加1,这里0行不用,1-num分别对应1-num行 */  
  88.     for ( int i = 0; i <= num; i++ )  
  89.         x[i] = 0;  
  90.     backtrack( 1 );                 /*传入第一个皇后,开始递归 */  
  91.     cout << “方案共有” << sum;  
  92.     delete[]x;  
  93.   
  94.     finish=clock();//结束时间  
  95.     totaltime=(double)(finish-start)/CLOCKS_PER_SEC;  
  96.     cout<<“\n此程序的运行时间为”<<totaltime<<“秒!”<<endl;  
  97.   
  98.     while (1);  
  99.     return(0);  
  100. }  

输出结果:

 

8皇后:

《n皇后2种解题思路与代码-Java与C++实现》

10皇后:

《n皇后2种解题思路与代码-Java与C++实现》

 

(2)java版本

 

运行平台:eclispse luna

操作系统:Windows7

 

[java] 
view plain
 copy  

  1. package com.lin;  
  2.   
  3. import java.lang.*;  
  4.   
  5. /** 
  6.  * n皇后问题解决 
  7.  * @author lin 
  8.  * 
  9.  */  
  10. public class QueenTest {  
  11.     /**下标i表示第几行,x[i]表示第i行皇后的位置,注意此处0行不用*/  
  12.     public int[] x;  
  13.     /**皇后的数目*/  
  14.     public int queenNum;  
  15.     /**解的数目*/  
  16.     public int methodNum;  
  17.       
  18.      QueenTest(int queenNum) {  
  19.         this.queenNum = queenNum;  
  20.         this.x = new int[queenNum+1];//注意,这里我们从第1行开始算起,第0行不用  
  21.         backtrack(1);//从第一个皇后开始递归  
  22.     }  
  23.       
  24.     /** 
  25.      * 一行一行的确定该行的皇后位置 
  26.      * @param t 
  27.      */  
  28.     public void backtrack(int t)  
  29.     {  
  30.         if( t > queenNum) //如果当前行大于皇后数目,表示找到解了  
  31.         {  
  32.             methodNum++;//sum为所有的可行的解  
  33.             //依次打印本次解皇后的位置  
  34.             for(int m = 1; m <= queenNum; m++){  
  35.                //System.out.println(x[m]);//这一行用输出当递归到叶节点的时候,一个可行解  
  36.                //这里只是为了好看才写成下面的  
  37.                for(int k =1; k <= queenNum;k++){  
  38.                    if(k == x[m]){  
  39.                      System.out.print(x[m]+” “);   
  40.                    }else {  
  41.                      System.out.print(“* “);//用*表示没有被用到的位置   
  42.                 }  
  43.                }  
  44.                 System.out.println();  
  45.             }  
  46.             System.out.println();  
  47.         }  
  48.         else{  
  49.             for(int i = 1;i <= queenNum;i++)  
  50.             {  
  51.                 x[t] = i;//第t行上皇后的位置只能是1-queenNum               
  52.                 if(place(t)) {//此处的place函数用来进行我们上面所说的条件的判断,如果成立,进入下一级递归,即放置下一个皇后  
  53.                     backtrack(t+1);  
  54.                 }  
  55.             }  
  56.         }  
  57.     }  
  58.       
  59.       
  60.       
  61.     /** 
  62.      * 判断第k行皇后可以放置的位置 
  63.      * @param k k表示第k行,X[K]k表示第k行上皇后的位置 
  64.      * @return boolean false表示此处不能放置皇后 
  65.      */  
  66.     public boolean place(int k) {  
  67.         for (int j = 1; j < k; j++)  
  68.             // 如果当前传入的第K行上的皇后放置的位置和其它皇后一个对角线(abs(x[k]- x[j])==abs(k-j)或一个直线上(x[j] == x[k])  
  69.             if (Math.abs(x[k] – x[j]) == Math.abs(k – j) || (x[j] == x[k])){                                                                  
  70.                 return false;  
  71.             }  
  72.         return true;  
  73.     }  
  74.   
  75.     public static void main(String[] args) {  
  76.         QueenTest queenTest = new QueenTest(8);  
  77.         System.out.println(“总共解数为:”+ queenTest.methodNum);  
  78.   
  79.     }  
  80. }  

输出结果:

这是八皇后

《n皇后2种解题思路与代码-Java与C++实现》

这是十皇后:
《n皇后2种解题思路与代码-Java与C++实现》
通过对比java和C++发现,反而java运行更加快?这是为什么呢?原因就是C++中使用了new操作,而java中基本数据都是在栈上来创建的,存取的速度比堆快多了。

三、更加高效的算法-位运算版本

    上面的方法递归次数实在太多了,也浪费空间,下面介绍目前号称是最快的–位运算。原理就不介绍了,看这里吧http://blog.csdn.net/xadillax/article/details/6512318

(1)Java代码

 

[java] 
view plain
 copy  

  1. package com.lin;  
  2.   
  3. import java.util.Scanner;  
  4.   
  5. /** 
  6.  * n皇后问题解决 
  7.  * @author lin 
  8.  * 
  9.  */  
  10. public class QueenTest3 {  
  11.       
  12.     /**sum用来记录皇后放置成功的不同布局数*/  
  13.     public long sum = 0;  
  14.       
  15.     /**upperlim用来标记所有列都已经放置好了皇后*/  
  16.     public long upperlim = 1;        
  17.         
  18.   
  19.     /** 
  20.      * 试探算法从最右边的列开始。   
  21.      * @param row 竖列 
  22.      * @param ld  左对角线 
  23.      * @param rd  右对角线 
  24.      */  
  25.     void queenPos(long row, long ld, long rd)    
  26.     {    
  27.         if (row != upperlim)    
  28.         {    
  29.             // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,    
  30.             // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1    
  31.             // 也就是求取当前哪些列可以放置皇后    
  32.             long pos = upperlim & ~(row | ld | rd);     
  33.             while (pos != 0)    // 0 — 皇后没有地方可放,回溯    
  34.             {    
  35.                 // 拷贝pos最右边为1的bit,其余bit置0    
  36.                 // 也就是取得可以放皇后的最右边的列    
  37.                 long p = pos & -pos;                                                  
  38.         
  39.                 // 将pos最右边为1的bit清零    
  40.                 // 也就是为获取下一次的最右可用列使用做准备,    
  41.                 // 程序将来会回溯到这个位置继续试探    
  42.                 pos -= p;                               
  43.         
  44.                 // row + p,将当前列置1,表示记录这次皇后放置的列。    
  45.                 // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。    
  46.                 // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。    
  47.                 // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归    
  48.                 // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位    
  49.                 // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线    
  50.                 // 上产生的限制都被记录下来了    
  51.                 queenPos(row + p, (ld + p) << 1, (rd + p) >> 1);                                  
  52.             }    
  53.         }    
  54.         else       
  55.         {    
  56.             // row的所有位都为1,即找到了一个成功的布局,回溯    
  57.             sum++;    
  58.         }    
  59.     }  
  60.       
  61.     /** 
  62.      * 根据传入的皇后数目开始计算 
  63.      * @param n 皇后数据 
  64.      */  
  65.     void queen(int queenNum) {  
  66.         if ((queenNum < 1) || (queenNum > 32)) {  
  67.             System.out.println(” 只能计算1-32之间\n”);  
  68.             return;  
  69.         }  
  70.         // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。  
  71.         upperlim = (upperlim << queenNum) – 1;  
  72.         queenPos(0, 0, 0);  
  73.     }  
  74.   
  75.   
  76.     public static void main(String[] args) {  
  77.         Scanner sc=new Scanner(System.in);    
  78.         System.out.print(“请输入皇后数目:”);    
  79.         int num=sc.nextInt();    
  80.         long starTime=System.currentTimeMillis();//程序开始时间  
  81.         QueenTest3 queenTest3 = new QueenTest3();  
  82.         queenTest3.queen(num);  
  83.         System.out.println(“总共解数为:”+ queenTest3.sum);  
  84.           
  85.         long endTime=System.currentTimeMillis();//程序结束时间  
  86.         double runTimes=(double)(endTime-starTime) / 1000.0;  
  87.         System.out.println(“程序总共运行时间:”+ runTimes + “s”);  
  88.           
  89.   
  90.     }  
  91. }  

运行结果:

 

八皇后的效果:(位运算版本)

《n皇后2种解题思路与代码-Java与C++实现》

把上面的代码中的输出结果的去掉:(非位运算版本)

 

[java] 
view plain
 copy  

  1. //依次打印本次解皇后的位置  
  2.  /*  for(int m = 1; m <= queenNum; m++){ 
  3.       //System.out.println(x[m]);//这一行用输出当递归到叶节点的时候,一个可行解 
  4.       //这里只是为了好看才写成下面的 
  5.       for(int k =1; k <= queenNum;k++){ 
  6.        if(k == x[m]){ 
  7.          System.out.print(x[m]+” “);  
  8.        }else { 
  9.          System.out.print(“* “);//用*表示没有被用到的位置  
  10.  
  11.       } 
  12.     System.out.println(); 
  13.    } 
  14.    System.out.println();*/  

然后输出如下:

 

《n皇后2种解题思路与代码-Java与C++实现》

经过两者对比,发现快了2ms

十皇后效果,没想到反而比八皇后的位运算版本还快(十皇后位运算版本)

《n皇后2种解题思路与代码-Java与C++实现》

十皇后非位运算版本
《n皇后2种解题思路与代码-Java与C++实现》
快了10倍啊!!!!!!!!!!!!!!!!!!!
 

12皇后

位运算

《n皇后2种解题思路与代码-Java与C++实现》

非位运算

《n皇后2种解题思路与代码-Java与C++实现》

(2)C++版本

 

[cpp] 
view plain
 copy  

  1. /*  
  2. ** 目前最快的N皇后递归解决方法  
  3. ** N Queens Problem  
  4. ** 试探-回溯算法,递归实现  
  5. */    
  6. #include <iostream>  
  7. using namespace std;    
  8. #include <time.h>  
  9.   
  10. // sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。    
  11. long sum = 0, upperlim = 1;         
  12.   
  13. // 试探算法从最右边的列开始。    
  14. void test(long row, long ld, long rd)    
  15. {    
  16.     if (row != upperlim)    
  17.     {    
  18.         // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,    
  19.         // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1    
  20.         // 也就是求取当前哪些列可以放置皇后    
  21.         long pos = upperlim & ~(row | ld | rd);     
  22.         while (pos)    // 0 — 皇后没有地方可放,回溯    
  23.         {    
  24.             // 拷贝pos最右边为1的bit,其余bit置0    
  25.             // 也就是取得可以放皇后的最右边的列    
  26.             long p = pos & -pos;                                                  
  27.   
  28.             // 将pos最右边为1的bit清零    
  29.             // 也就是为获取下一次的最右可用列使用做准备,    
  30.             // 程序将来会回溯到这个位置继续试探    
  31.             pos -= p;                               
  32.   
  33.             // row + p,将当前列置1,表示记录这次皇后放置的列。    
  34.             // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。    
  35.             // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。    
  36.             // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归    
  37.             // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位    
  38.             // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线    
  39.             // 上产生的限制都被记录下来了    
  40.             test(row + p, (ld + p) << 1, (rd + p) >> 1);                                  
  41.         }    
  42.     }    
  43.     else       
  44.     {    
  45.         // row的所有位都为1,即找到了一个成功的布局,回溯    
  46.         sum++;    
  47.     }    
  48. }    
  49.   
  50. int main()    
  51. {    
  52.     int num;  
  53.     cout<<“请输入皇后数目:”;  
  54.     cin>>num;   
  55.   
  56.     clock_t start,finish;  
  57.     double totaltime;//计算程序运行时间  
  58.     start=clock();//起始时间  
  59.   
  60.     // 因为整型数的限制,最大只能32位,    
  61.     // 如果想处理N大于32的皇后问题,需要    
  62.     // 用bitset数据结构进行存储    
  63.     if ((num < 1) || (num > 32))                     
  64.     {    
  65.         cout << ” 只能计算1-32之间\n”;    
  66.         return 0;  
  67.     }    
  68.   
  69.     // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。    
  70.     upperlim = (upperlim << num) – 1;             
  71.   
  72.     test(0, 0, 0);    
  73.     cout << “方案共有” << sum;  
  74.   
  75.     finish=clock();//结束时间  
  76.     totaltime=(double)(finish-start)/CLOCKS_PER_SEC;  
  77.     cout<<“\n此程序的运行时间为”<<totaltime<<“秒!”<<endl;  
  78.     while(1);  
  79.     return 0;    
  80. }  

输出结果:

 

《n皇后2种解题思路与代码-Java与C++实现》

《n皇后2种解题思路与代码-Java与C++实现》

《n皇后2种解题思路与代码-Java与C++实现》

下面来对比下java和C++运算的效果:

16皇后C++版本(位运算)

《n皇后2种解题思路与代码-Java与C++实现》

16皇后java版本(位运算)

《n皇后2种解题思路与代码-Java与C++实现》

发现又是java快了点。

 

from: http://blog.csdn.net/evankaka/article/details/48756951

    原文作者:八皇后问题
    原文地址: https://www.cnblogs.com/GarfieldEr007/p/5746270.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞