/*
Name: n皇后问题
Copyright:
Author: 巧若拙
Date: 07-03-17 09:09
Description:
一、问题描述:
在n×n格的棋盘上放置彼此不受攻击的n个皇后。
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n后问题等价于再n×n的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
输入:
给定棋盘的大小n (n ≤ 13)
输出:
输出有多少种放置方法。
二、解题思路:
要解决N皇后问题,其实就是要解决好怎么放置这n个皇后,每一个皇后与前面的所有皇后不能在同一行、同一列、同一对角线,
在这里我们可以以行优先,就是说皇后的行号按顺序递增,只考虑第i个皇后放置在第i行的哪一列,
所以在放置第i个皇后的时候,可以从第1列判断起,如果可以放置在第1个位置,则跳到下一行放置下一个皇后。
如果不能,则跳到下一列...直到最后一列,如果最后一列也不能放置,则说明此时放置方法出错,
则回到上一个皇后向之前放置的下一列重新放置。此即是回溯法的精髓所在。
当第n个皇后放置成功后,即得到一个可行解,此时再回到上一个皇后重新放置寻找下一个可行解...
如此后,即可找出一个n皇后问题的所有可行解。
三、复杂度分析:
每一行都有n个位置可以放置,一共有n^n中可能性。因而最坏情况下时间复杂度为O(n^n)。
似乎比穷举法要高,但是由于回溯法不测试死结点的分支,它的平均时间复杂度要低于穷举法。
*/
#include<iostream>
#include<cmath>
using namespace std;
const int N = 4; //皇后的个数
int c[N];//记录n个皇后的列坐标
int sum = 0;//保存可以放置的方案数
bool OK(int t);//检查当前皇后的列坐标是否合法
void Backtrace(int t); //递归回溯
void Backtrace_2(); //非递归回溯
int main()
{
Backtrace(0);
// Backtrace_2();
system("pause");
return 0;
}
bool OK(int t)//检查当前皇后的列坐标是否合法
{
for(int i=0; i<t; i++)
{
if(c[i] == c[t] || t-i == abs(c[t]-c[i]))
return false;
}
return true;
}
void Backtrace(int t) //递归回溯
{
if(t == N)
{
sum++;
cout << "方案" << sum << ": ";
for(int i=0; i<N; i++)
{
cout << c[i] << " ";
}
cout << endl;
}
else
{
for(int i=1; i<=N; i++)
{
c[t] = i;
if(OK(t))
Backtrace(t+1);
}
// c[t] = 0; //列坐标还原并回溯(由于c[t]每次都从0开始取值,故列坐标无需还原)
}
}
void Backtrace_2() //非递归回溯
{
int t = 0; //第一个顶点入栈
while(t >= 0)
{
while(c[t]++ < N)//c[t]默认初始化为0
{
if (OK(t)) //c[t]可行才进入下一步
{
if(t == N-1)
{
sum++;
cout << "方案" << sum << ": ";
for(int i=0; i<N; i++)
{
cout << c[i] << " ";
}
cout << endl;
}
else
{
t++;
}
}
}
c[t--] = 0; //列坐标还原并回溯
}
}
/*
Name: n皇后问题
Copyright:
Author: 巧若拙
Date: 07-03-17 09:09
Description:
一、问题描述:
在n×n格的棋盘上放置彼此不受攻击的n个皇后。
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n后问题等价于再n×n的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
输入:
给定棋盘的大小n (n ≤ 13)
输出:
输出有多少种放置方法。
二、解题思路:
要解决N皇后问题,其实就是要解决好怎么放置这n个皇后,每一个皇后与前面的所有皇后不能在同一行或同一列或同一斜线上,
在这里我们可以以行优先,就是说皇后的行号按顺序递增,只考虑第i个皇后放置在第i行的哪一列,
所以在放置第i个皇后的时候,可以从第1列判断起,如果可以放置在第1个位置,则跳到下一行放置下一个皇后。
如果不能,则跳到下一列...直到最后一列,如果最后一列也不能放置,则说明之前的皇后放置有问题,
则回溯到上一个皇后,把其换到下一个可能位置。此即是回溯法的精髓所在。
当第n个皇后放置成功后,即得到一个可行解,此时再回到上一个皇后重新放置寻找下一个可行解...
如此后,即可找出一个n皇后问题的所有可行解。
本算法的精妙之处在于分别使用三个数组 b[N],c[N+N],d[N+N]用来存储该列和两条斜线是否可用,
这样就无需使用函数来判断该坐标是否可用了。 尤其为了避免数组c的下标越界,使用了c[i-j+N]表示坐标的处理,甚为高妙。
*/
#include<iostream>
#include<cmath>
using namespace std;
const int N = 8; //皇后的个数
int cel[N];//记录n个皇后的列坐标
bool b[N]; //b[j]==0表示列j可用
bool c[N+N]; //c[j]==0表示左上-右下斜线可用, i - cel[i] == j - cel[j]
bool d[N+N]; //d[j]==0表示右上-左下斜线可用 i + cel[i] == j + cel[j]
int sum = 0;//保存可以放置的方案数
void Backtrace(int r); //递归回溯,r表示第r行
void Backtrace_2(int r);
int main()
{
// Backtrace(0);
Backtrace_2(0);
return 0;
}
void Backtrace(int r) //递归回溯,r表示第r行
{
if(r == N)
{
sum++;
cout << "方案" << sum << ": ";
for(int i=0; i<N; i++)
{
cout << cel[i]+1 << " ";
}
cout << endl;
}
else
{
for(int j=0; j<N; j++)//可能的列号
{
if((!b[j])&& (!c[r-j+N]) && (!d[r+j])) //c[i-j+N]是为了确保下标不越界
{
cel[r] = j;
b[j] = 1; //宣布占领列j
c[r-j+N] = 1; //宣布占领两条斜线
d[r+j] = 1;
Backtrace(r+1); //继续递归放置下一个皇后
//还原,以便回溯
b[j] = 0;
c[r-j+N] = 0;
d[r+j] = 0;
}
}
}
}
void Backtrace_2(int r) //递归回溯,r表示第r行
{
for(int j=0; j<N; j++)//可能的列号
{
if((!b[j])&& (!c[r-j+N]) && (!d[r+j])) //c[i-j+N]是为了确保下标不越界
{
cel[r] = j;
b[j] = 1; //宣布占领列j
c[r-j+N] = 1; //宣布占领两条斜线
d[r+j] = 1;
if(r == N-1)
{
sum++;
cout << "方案" << sum << ": ";
for(int i=0; i<N; i++)
{
cout << cel[i]+1 << " ";
}
cout << endl;
}
else
Backtrace_2(r+1); //继续递归放置下一个皇后
//还原,以便回溯
b[j] = 0;
c[r-j+N] = 0;
d[r+j] = 0;
}
}
}