目录
问题分析
八皇后问题:高斯于1850年提出的问题。
在8*8的棋盘上放置八个皇后,任意两个皇后都不能处于同一行、列或者同一斜线上。
分析:
首先要解决的两个问题:
1. 如何表示棋盘?
2. 如何获取皇后的位置信息,进而判断是否相互攻击?
解决思想:
解题思想就是问题回溯法。
过程模拟
当某个皇后探测到最后一列,而其后还有皇后没有摆放完全的时候,就需要回溯;
将本次探测皇后的上一个皇后向后再探测一个位置,继续下面的皇后的从0开始到最大列的探测;
如果回溯的过程中,直到第一个皇后到最大列位置,还不满足条件,也就是下次的回溯是-1(不存在)的时候,表示皇后的位置探测完毕;
如果在这个探测的过程中,没有输出满足条件的皇后位置序列,表示当前的棋盘和皇后序列不存在。
问题回答
Q1:
表示皇后,可以采用两种方式。
第一种是使用二维数组,用值来标识当前皇后在本位置存在。 如:int chess[N][N];
第二种是使用一位数组,用下标表示皇后的编号,用存放的值表示该皇后在棋盘中列的位置。如 int chess[N];
Q2:
假设皇后i和皇后j在棋盘上的位置是(i, Xi),(j, Xj);
不在同行:i≠j
不在同列:Xi≠Xj
不在同一条斜线上:|i-j|≠|Xi-Xj| (也即是斜率的绝对值不等于1)
八皇后问题非递归实现
#include<iostream>
#include<cmath>
using namespace std;
const int N = 100;//假定最多能求的皇后问题
int x[N] = {-1};//这种方式下标表示皇后,值表示存放的列位置
//判断第k个皇后放置是否发生冲突
bool Success(int k){
//这里判断和前面的皇后是否发生冲突,所以前面皇后的下标是0-(k-1)
for(int i=0;i<k;i++){
//这里皇后的行for循环控制了的,所以可能行发生冲突
if(x[i]==x[k] || abs(k-i) == abs(x[i]-x[k]))
return false;//发生冲突
}
return true;//不发生冲突
}
//打印n皇后问题的一个解
void Print(int n){
for(int i=0;i<n;i++){//行
for(int j=0;j<n;j++){//列
if(j==x[i]){ //x[i]中存放的就是列下标
cout<<"Q ";
}
else{
cout<<"0 ";
}
}
cout<<endl;
}
cout<<endl;
}
void Queue(int n){
int k=0, num=0; //num存放问题的解的个数
while(k>=0){//前面分析过,回溯到-1的时候,表示探测完毕
x[k]++; //下一列摆放皇后k , 初始k=-1,故而开始探测的起始位置列是0
//方式皇后后,就需要判断是否发生冲突
while(x[k]<n and !Success(k)){//发生冲突,就探测下一列
x[k]++;
}
//发生冲突会执行上面的,不发生冲突才执行下面
if(x[k]<n and k==n-1){ //k=n-1表示 最后一个皇后,x[k]<n表示位置有效
cout<<"第"<<++num<<"个解是:"<<endl;
Print(n);
}
else if(x[k]<n and k<n-1){//表示还有皇后没有找到位置
//不发生冲突,还有皇后没放置完,故而需要放置下一个皇后
k++;
}else{
//探测越界 , 大于n-1, 表示本行没有可以放置的位置,所以回溯上一个元素
x[k--] = -1; //回溯,并重新初始化上一个皇后所在的列下标
}
}
}
int main(void){
Queue(4);
return 0;
}
---------------------------------------------------分割线-------------------------------------------------
结果截图:
八皇后问题递归方式
#include<iostream>
#include<cmath>
using namespace std;
const int N = 100;//假定最多能求的皇后问题
int chess[N][N]={0};
int n = 4;
bool Success(int k, int w){
for(int i=0;i<k;i++){//检查判断的始终都是前面的放置好位置的皇后
for(int j=0;j<n;j++){
if(chess[i][j]==1){
if(k==i or j==w or abs(i-k)==abs(j-w))
return false;
}
}
}
return true;
}
void Print(){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cout<<chess[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
}
int num = 0;
void Queue(int row){
if(row==n){//假设n=8,八皇后row取值是0-7,故而在递归到row=8时就需要输出,
//然后退栈,再次row在调用的过程中row++,到row=8时输出,退栈,直到栈空
cout<<"第"<<++num<<"个八皇后序列输出:"<<endl;
Print();
}else{
for(int j=0;j<n;j++){//模拟列
chess[row][j] = 1;
if(Success(row, j)){//如果成功,进行下一行的探测放置
Queue(row+1);
}
chess[row][j]=0;//撤回棋子
//递归调用,撤回棋子在最后一步,则如果探测下一个位置满足
//条件,直接进入下一个皇后的位置探测,不满足的时候,才会撤回
//棋子,同样的,在退栈的过程中,如果不满足,会回退到j++
//也就是上一个皇后进行下一个位置的探测,如此反复
}
}
}
int main(void){
Queue(0);
return 0;
}
-------------------------------------------------------分割线-------------------------------------------------
结果截图:
附Java版本:
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<List<String>>();
boolean[][] matrix = new boolean[n][n];
backtrack(matrix, 0, result);
return result;
}
private void backtrack(boolean[][] matrix, int row, List<List<String>> result) {
int size = matrix.length;
if(row == size){
result.add(getResult(matrix));
}else{
for (int i = 0; i < size; i++) {
if(!isValid(matrix, row, i)){
continue;
}
matrix[row][i] = true;
backtrack(matrix, row + 1, result);
matrix[row][i] = false;
}
}
}
private List<String> getResult(boolean[][] matrix) {
List<String> res = new ArrayList<String>();
for (boolean[] booleans : matrix) {
StringBuilder sb = new StringBuilder();
for (boolean aBoolean : booleans) {
sb.append(aBoolean ? 'Q' : '.');
}
res.add(sb.toString());
}
return res;
}
private boolean isValid(boolean[][] matrix, int row, int k) {
for(int i=0;i<row;i++){//检查判断的始终都是前面的放置好位置的皇后
for(int j=0;j<matrix.length;j++){
if(matrix[i][j]){
if(j==k || Math.abs(i - row)== Math.abs(j - k))
return false;
}
}
}
return true;
}
作者:无涯明月
发文时间:2018-11-17