zoukankan      html  css  js  c++  java
  • n皇后2种解题思路与代码-Java与C++实现

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

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

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

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

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

     

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

    假设有两个皇后被放置在(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<<" 此程序的运行时间为"<<totaltime<<"秒!"<<endl;  
    97.   
    98.     while (1);  
    99.     return(0);  
    100. }  


    输出结果:

    8皇后:

    10皇后:

    (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. }  

    输出结果:

    这是八皇后

    这是十皇后:
    通过对比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之间 ");  
    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. }  

    运行结果:

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

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

    [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();*/  


    然后输出如下:

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

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

    十皇后非位运算版本
    快了10倍啊!!!!!!!!!!!!!!!!!!!
     

    12皇后

    位运算

    非位运算

    (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之间 ";    
    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<<" 此程序的运行时间为"<<totaltime<<"秒!"<<endl;  
    78.     while(1);  
    79.     return 0;    
    80. }  


    输出结果:

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

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

    16皇后java版本(位运算)

    发现又是java快了点。

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

  • 相关阅读:
    2.Android之按钮Button和编辑框EditText学习
    《DSP using MATLAB》Problem 3.8
    《DSP using MATLAB》Problem 3.7
    《DSP using MATLAB》Problem 3.6
    《DSP using MATLAB》Problem 3.5
    《DSP using MATLAB》Problem 3.4
    《DSP using MATLAB》Problem 3.3
    《DSP using MATLAB》Problem 3.2
    《DSP using MATLAB》Problem 3.1
    《DSP using MATLAB》Problem 2.20
  • 原文地址:https://www.cnblogs.com/GarfieldEr007/p/5746270.html
Copyright © 2011-2022 走看看