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
- /**
- * n皇后问题解决
- * @author lin
- *
- */
- #include <iostream>
- #include <cmath>
- #include<time.h>
- using namespace std;
- /**皇后的数目*/
- static int num;
- /**下标i表示第几行,x[i]表示第i行皇后的位置,注意此处0行不用*/
- static int *x;
- /**解的数目*/
- static int sum = 0;
- /**
- * 判断第k行皇后可以放置的位置
- * @param k k表示第k行,X[K]k表示第k行上皇后的位置
- * @return boolean false表示此处不能放置皇后
- */
- bool place( int k )
- {
- for ( int j = 1; j < k; j++ )
- {
- /* 如果当前传入的第K行上的皇后放置的位置和其它皇后一个对角线(abs(x[k]- x[j])==abs(k-j)或一个直线上(x[j] == x[k]) */
- if ( abs( x[k] - x[j] ) == abs( k - j ) || x[j] == x[k] )
- {
- return(false);
- }
- }
- return(true);
- }
- /**
- * 一行一行的确定该行的皇后位置
- * @param t
- */
- void backtrack( int t )
- {
- if ( t > num ) /* 如果当前行大于皇后数目,表示找到解了 */
- {
- sum++;
- /* 依次打印本次解皇后的位置 */
- for ( int m = 1; m <= num; m++ )
- {
- //cout << x[m]; /* 这一行用输出当递归到叶节点的时候,一个可行解 */
- //这里只是为了好看才写成下面的
- for(int k =1; k <= num;k++){
- if(k == x[m]){
- cout << x[m] <<" ";
- }else {
- cout << "* ";//用*表示没有被用到的位置
- }
- }
- cout << endl;
- }
- cout << endl;
- } else {
- for ( int i = 1; i <= num; i++ )
- {
- x[t] = i; /* 第t行上皇放在i列处 */
- if ( place( t ) )
- {
- /* 此处的place函数用来进行我们上面所说的条件的判断,如果成立,进入下一级递归 */
- backtrack( t + 1 );
- }
- }
- }
- }
- int main()
- {
- cout<<"请输入皇后数目:";
- cin>>num;
- clock_t start,finish;
- double totaltime;//计算程序运行时间
- start=clock();//起始时间
- x = new int[num + 1]; /* 此处注意加1,这里0行不用,1-num分别对应1-num行 */
- for ( int i = 0; i <= num; i++ )
- x[i] = 0;
- backtrack( 1 ); /*传入第一个皇后,开始递归 */
- cout << "方案共有" << sum;
- delete[]x;
- finish=clock();//结束时间
- totaltime=(double)(finish-start)/CLOCKS_PER_SEC;
- cout<<"\n此程序的运行时间为"<<totaltime<<"秒!"<<endl;
- while (1);
- return(0);
- }
输出结果:
8皇后:
10皇后:
(2)java版本
运行平台:eclispse luna
操作系统:Windows7
- package com.lin;
- import java.lang.*;
- /**
- * n皇后问题解决
- * @author lin
- *
- */
- public class QueenTest {
- /**下标i表示第几行,x[i]表示第i行皇后的位置,注意此处0行不用*/
- public int[] x;
- /**皇后的数目*/
- public int queenNum;
- /**解的数目*/
- public int methodNum;
- QueenTest(int queenNum) {
- this.queenNum = queenNum;
- this.x = new int[queenNum+1];//注意,这里我们从第1行开始算起,第0行不用
- backtrack(1);//从第一个皇后开始递归
- }
- /**
- * 一行一行的确定该行的皇后位置
- * @param t
- */
- public void backtrack(int t)
- {
- if( t > queenNum) //如果当前行大于皇后数目,表示找到解了
- {
- methodNum++;//sum为所有的可行的解
- //依次打印本次解皇后的位置
- for(int m = 1; m <= queenNum; m++){
- //System.out.println(x[m]);//这一行用输出当递归到叶节点的时候,一个可行解
- //这里只是为了好看才写成下面的
- for(int k =1; k <= queenNum;k++){
- if(k == x[m]){
- System.out.print(x[m]+" ");
- }else {
- System.out.print("* ");//用*表示没有被用到的位置
- }
- }
- System.out.println();
- }
- System.out.println();
- }
- else{
- for(int i = 1;i <= queenNum;i++)
- {
- x[t] = i;//第t行上皇后的位置只能是1-queenNum
- if(place(t)) {//此处的place函数用来进行我们上面所说的条件的判断,如果成立,进入下一级递归,即放置下一个皇后
- backtrack(t+1);
- }
- }
- }
- }
- /**
- * 判断第k行皇后可以放置的位置
- * @param k k表示第k行,X[K]k表示第k行上皇后的位置
- * @return boolean false表示此处不能放置皇后
- */
- public boolean place(int k) {
- for (int j = 1; j < k; j++)
- // 如果当前传入的第K行上的皇后放置的位置和其它皇后一个对角线(abs(x[k]- x[j])==abs(k-j)或一个直线上(x[j] == x[k])
- if (Math.abs(x[k] - x[j]) == Math.abs(k - j) || (x[j] == x[k])){
- return false;
- }
- return true;
- }
- public static void main(String[] args) {
- QueenTest queenTest = new QueenTest(8);
- System.out.println("总共解数为:"+ queenTest.methodNum);
- }
- }
输出结果:
这是八皇后
三、更加高效的算法-位运算版本
上面的方法递归次数实在太多了,也浪费空间,下面介绍目前号称是最快的--位运算。原理就不介绍了,看这里吧http://blog.csdn.net/xadillax/article/details/6512318
(1)Java代码
- package com.lin;
- import java.util.Scanner;
- /**
- * n皇后问题解决
- * @author lin
- *
- */
- public class QueenTest3 {
- /**sum用来记录皇后放置成功的不同布局数*/
- public long sum = 0;
- /**upperlim用来标记所有列都已经放置好了皇后*/
- public long upperlim = 1;
- /**
- * 试探算法从最右边的列开始。
- * @param row 竖列
- * @param ld 左对角线
- * @param rd 右对角线
- */
- void queenPos(long row, long ld, long rd)
- {
- if (row != upperlim)
- {
- // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
- // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
- // 也就是求取当前哪些列可以放置皇后
- long pos = upperlim & ~(row | ld | rd);
- while (pos != 0) // 0 -- 皇后没有地方可放,回溯
- {
- // 拷贝pos最右边为1的bit,其余bit置0
- // 也就是取得可以放皇后的最右边的列
- long p = pos & -pos;
- // 将pos最右边为1的bit清零
- // 也就是为获取下一次的最右可用列使用做准备,
- // 程序将来会回溯到这个位置继续试探
- pos -= p;
- // row + p,将当前列置1,表示记录这次皇后放置的列。
- // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
- // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
- // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
- // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
- // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
- // 上产生的限制都被记录下来了
- queenPos(row + p, (ld + p) << 1, (rd + p) >> 1);
- }
- }
- else
- {
- // row的所有位都为1,即找到了一个成功的布局,回溯
- sum++;
- }
- }
- /**
- * 根据传入的皇后数目开始计算
- * @param n 皇后数据
- */
- void queen(int queenNum) {
- if ((queenNum < 1) || (queenNum > 32)) {
- System.out.println(" 只能计算1-32之间\n");
- return;
- }
- // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。
- upperlim = (upperlim << queenNum) - 1;
- queenPos(0, 0, 0);
- }
- public static void main(String[] args) {
- Scanner sc=new Scanner(System.in);
- System.out.print("请输入皇后数目:");
- int num=sc.nextInt();
- long starTime=System.currentTimeMillis();//程序开始时间
- QueenTest3 queenTest3 = new QueenTest3();
- queenTest3.queen(num);
- System.out.println("总共解数为:"+ queenTest3.sum);
- long endTime=System.currentTimeMillis();//程序结束时间
- double runTimes=(double)(endTime-starTime) / 1000.0;
- System.out.println("程序总共运行时间:"+ runTimes + "s");
- }
- }
运行结果:
八皇后的效果:(位运算版本)
把上面的代码中的输出结果的去掉:(非位运算版本)
- //依次打印本次解皇后的位置
- /* for(int m = 1; m <= queenNum; m++){
- //System.out.println(x[m]);//这一行用输出当递归到叶节点的时候,一个可行解
- //这里只是为了好看才写成下面的
- for(int k =1; k <= queenNum;k++){
- if(k == x[m]){
- System.out.print(x[m]+" ");
- }else {
- System.out.print("* ");//用*表示没有被用到的位置
- }
- System.out.println();
- }
- System.out.println();*/
然后输出如下:
经过两者对比,发现快了2ms
十皇后效果,没想到反而比八皇后的位运算版本还快(十皇后位运算版本)
12皇后
位运算
非位运算
(2)C++版本
- /*
- ** 目前最快的N皇后递归解决方法
- ** N Queens Problem
- ** 试探-回溯算法,递归实现
- */
- #include <iostream>
- using namespace std;
- #include <time.h>
- // sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。
- long sum = 0, upperlim = 1;
- // 试探算法从最右边的列开始。
- void test(long row, long ld, long rd)
- {
- if (row != upperlim)
- {
- // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
- // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
- // 也就是求取当前哪些列可以放置皇后
- long pos = upperlim & ~(row | ld | rd);
- while (pos) // 0 -- 皇后没有地方可放,回溯
- {
- // 拷贝pos最右边为1的bit,其余bit置0
- // 也就是取得可以放皇后的最右边的列
- long p = pos & -pos;
- // 将pos最右边为1的bit清零
- // 也就是为获取下一次的最右可用列使用做准备,
- // 程序将来会回溯到这个位置继续试探
- pos -= p;
- // row + p,将当前列置1,表示记录这次皇后放置的列。
- // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
- // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
- // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
- // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
- // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
- // 上产生的限制都被记录下来了
- test(row + p, (ld + p) << 1, (rd + p) >> 1);
- }
- }
- else
- {
- // row的所有位都为1,即找到了一个成功的布局,回溯
- sum++;
- }
- }
- int main()
- {
- int num;
- cout<<"请输入皇后数目:";
- cin>>num;
- clock_t start,finish;
- double totaltime;//计算程序运行时间
- start=clock();//起始时间
- // 因为整型数的限制,最大只能32位,
- // 如果想处理N大于32的皇后问题,需要
- // 用bitset数据结构进行存储
- if ((num < 1) || (num > 32))
- {
- cout << " 只能计算1-32之间\n";
- return 0;
- }
- // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。
- upperlim = (upperlim << num) - 1;
- test(0, 0, 0);
- cout << "方案共有" << sum;
- finish=clock();//结束时间
- totaltime=(double)(finish-start)/CLOCKS_PER_SEC;
- cout<<"\n此程序的运行时间为"<<totaltime<<"秒!"<<endl;
- while(1);
- return 0;
- }
输出结果:
下面来对比下java和C++运算的效果:
16皇后C++版本(位运算)
16皇后java版本(位运算)
发现又是java快了点。
from: http://blog.csdn.net/evankaka/article/details/48756951
n皇后2种解题思路与代码-Java与C++实现的更多相关文章
- 一道仅有7人通过的超5星微软比赛题目-------解题思路&优秀代码分享,邀你来“找茬儿”
6月23日英雄会平台发布了一道难度为超5星的微软比赛题目,截止活动结束共有300多名编程爱好者参与线上答题,而最终通过者仅有7人,通过率仅为2%.为什么成绩如此出人意料?是因为题目的英文描述难以理解? ...
- 编程题及解题思路(1,String)
题目描述 请实现一个算法,确定一个字符串的所有字符是否全都不同.这里我们要求不允许使用额外的存储结构. 给定一个string iniString,请返回一个bool值,True代表所有字符全都不同,F ...
- PTA 1014 Waiting in Line (30分) 解题思路及满分代码
题目 Suppose a bank has N windows open for service. There is a yellow line in front of the windows whi ...
- dp方法论——由矩阵相乘问题学习dp解题思路
前篇戳:dp入门——由分杆问题认识动态规划 导语 刷过一些算法题,就会十分珍惜“方法论”这种东西.Leetcode上只有题目.讨论和答案,没有方法论.往往答案看起来十分切中要害,但是从看题目到得到思路 ...
- leetcode array解题思路
Array *532. K-diff Pairs in an Array 方案一:暴力搜索, N平方的时间复杂度,空间复杂度N 数组长度为10000,使用O(N平方)的解法担心TLE,不建议使用,尽管 ...
- top(k,n)—db kernel队解题思路
0. 比赛 公司里的第三届XX中间件性能挑战赛 我和另外两个P5组队参加,队名为"db kernel".最后获得了第八,应该是P5里的最高排名. 以下简单扼要地介绍一下题目,以及我 ...
- 记一次CTF比赛过程与解题思路-MISC部分
前言 最近好久没更新博客和公众号了,有朋友问是不是在憋大招,但我不好意思说其实是因为最近一段时间太懒了,一直在当咸鱼- 意识到很久没更新这个问题,我是想写点什么的,但好像一直当咸鱼也没啥可分享的,最近 ...
- 阿里聚安全攻防挑战赛第三题Android PwnMe解题思路
阿里聚安全攻防挑战赛第三题Android PwnMe解题思路 大家在聚安全挑战赛正式赛第三题中,遇到android app 远程控制的题目.我们今天带你一探究竟,如何攻破这道题目. 一.题目 购物应用 ...
- [LeetCode] 3Sum 解题思路
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all un ...
随机推荐
- 【CodeVS】p1038 一元三次方程求解
题目描述 Description 有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程.给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100 ...
- 访问google,youtube
一.找到host文件 windows : C:\windows\system32\drivers\etc mac os: /private/etc linux : /etc 二.修改host文件 ht ...
- Spring整合Hibernate之AnnotationSessionFactoryBean与LocalSessionFactoryBean
spring集成hibernate由两种形式 1.继续使用Hibernate的映射文件*.hbm.xml 2.使用jpa形式的pojo对象, 去掉*.hbm.xml文件 一.继续使用Hibernate ...
- 初识WebSocket
众所周知,Http协议是无状态的,并且是基于Request/Response的方式与服务器进行交互,也就是我们常说的单工模式.但是随着互联 网的发展,浏览器与服务端进行双向通信需求的增加,长轮询向服务 ...
- node.js开发中使用Node Supervisor实现监测文件修改并自动重启应用提高nodejs调试效率
在开发或调试Node.js应用程序的时候,当你修改js文件后,总是要按下CTRL+C终止程序,然后再重新启动,即使是修改一点小小的参数,也 总是要不断地重复这几个很烦人的操作.这是因为Node.js ...
- 特殊集合 Stack Queue Hashtable
//Stack 干草堆集合 栈集合 先进后出 Stack st = new Stack(); //实例化 初始化 st.Push(2); //添加元素 st.Push(6); s ...
- Hadoop.2.x_WebUV示例
一.网站基本指标(即针对于网站用户行为而产生的日志中进行统计分析) 1. PV:网页浏览量(Page View页面浏览次数,只要进入该网页就产生一条记录,不限IP,统计点每天(较多)/每周/每月/.. ...
- Centos 安装了 Wkhtmltopdf 却依旧显示 无法打印pdf
Odoo里判断wkhtmlpdf是否安装的代码在 openerp/tools/misc.py 文件中: def find_in_path(name): path = os.environ.get('P ...
- 使用redis的五个注意事项
http://blog.nosqlfan.com/html/3705.html 下面内容来源于Quora上的一个提问,问题是使用Redis需要避免的五个问题.而回答中超出了五个问题的范畴,描述了五个使 ...
- Hibernate的一级二级缓存机制配置与测试
特别感谢http://www.cnblogs.com/xiaoluo501395377/p/3377604.html 在本篇随笔里将会分析一下hibernate的缓存机制,包括一级缓存(session ...