N 皇后问题是在N×N 的国际象棋棋盘上, 放置N 个皇后,使任何两个皇后都不能相互攻击。也就是说, 任何两个皇后, 都不在同一行、同一列或同一斜线上。N 皇后问题是由八皇后问题演化而来的。八皇后问题于1848 年由国际象棋棋手MaxBazzel 提出。八皇后问题自从提出之后, 吸引了许多著名数学家的关注, 其中包括Carl Gauss。近年来, N 皇后问题成为计算机应用领域内的一个经典问题, 不但成为测试算法的一个工具, 还成为检验并行计算系统效率的一个有效工具。
问题定义
八皇后问题是一个非常著名的问题,最初是由著名数学家高斯提出的。问题的描述是这样的:在一个8 ×8的棋盘上,摆放8个皇后,任意两个皇后不能处在同一行、同一列和同一斜线上。该问题也可以被扩展为在一个n ×n的棋盘上摆放n个皇后的问题。
在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
经典的回溯算法,该算法的思路如下:
• 依次在棋盘的每一行上摆放一个皇后。 • 每次摆放都要检查当前摆放是否可行。如果当前的摆放引发冲突,则把当前皇后摆放到当前行的下一列上,并重新检查冲突。
• 如果当前皇后在当前行的每一列上都不可摆放,则回溯到上一个皇后并且将其摆放到下一列上,并重新检查冲突。
• 如果所有皇后都被摆放成功,则表明成功找到一个解,记录下该解并且回溯到上一个皇后。
问题分析
解向量:(x1, x2, … , xn),x[i]表示皇后i放在棋盘的第i行的第x[i]列。
显约束:xi=1,2, … ,n
隐约束:
1)不同列:xi≠xj (i≠j)
2)不处于同一正、反对角线:|i-j|≠|xi-xj|
解空间:排列树
约束函数:xi≠xj (i≠j) and |i-j|≠|xi-xj|
算法描述
//place函数测试若将皇后k放在x[k]列是否与前面已放置的k-1个皇后都不在同一列,而且都不在同一斜线上。
bool Queen::Place(int k)
{
for (int j=1;j<k;j++)
if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
return true;
}
//递归函数backtrack(1)实现对整个解空间的回溯搜索。
void Queen::Backtrack(int t)
{
if (t>n) sum++;//sum记录当前已找到的可行方案数。
else
for (int i=1;i<=n;i++) {
x[t]=i;
if (Place(t)) Backtrack(t+1);
}
}
bool Queen::Place(int k)
{
for (int j=1;j<k;j++)
if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
return true;
}
//递归函数backtrack(1)实现对整个解空间的回溯搜索。
void Queen::Backtrack(int t)
{
if (t>n) sum++;//sum记录当前已找到的可行方案数。
else
for (int i=1;i<=n;i++) {
x[t]=i;
if (Place(t)) Backtrack(t+1);
}
}
当i<=n时,当前扩展结点是解空间的一个内部结点。该结点有x[i]=1,2,…n共n个儿子结点。对当前扩展结点的每一个儿子结点,由函数place检查其可行性,并以深度优先的方式递归地对可行子树进行搜索,或者剪去不可行子树。
下面是实现的四皇后的Java代码
public class Empress {
public static int num=0;
public List<Integer>list=new ArrayList<Integer>();
public boolean constraint(int []array,int k)
{
for(int i=1;i<k;i++)
{
if(Math.abs(array[i]-array[k])==Math.abs(i-k)||array[i]==array[k])//如果在同一个斜线或者在同一列
{
return false;
}
}
return true;
}
public void search(int n,int []array)
{
if(n>4)
{
num++;
for(int i=1;i<array.length;i++)
{
list.add(array[i]);
}
}
else
{
for (int i=1;i<array.length;i++) {
array[n]=i;
if (constraint(array,n))
search(n+1,array);
}
}
}
public void print()
{
Iterator iterator=list.iterator();
while(iterator.hasNext())
{
for(int i=0;i<4;i++)
{
System.out.print(iterator.next()+" ");
}
System.out.println();
}
}
public static void main(String args[])
{
Empress e=new Empress();
int []array=new int [5];
e.search(1, array);
e.print();
}
}
N后问题的时间复杂性
如果只要找出N后问题的一个解,那么这是一个求路径的问题。
求路径:只需要找到一条路径便可以得到解。设每个状态有k个后继,其搜索树为K叉树,其结点总数为k(n+1)–1【k的n+1次方】,遍历的时间为O(kn),这里n为找到解的路径长度。
N后问题的路径深度为n,可选择的后继有n个,所以时间复杂性为O(nn)。【n的n次方】