八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
问题阐述
N皇后问题:将以上问题改为,对于一个任意的N,求是否存在N皇后问题的摆法?如N=1,4,5,6,7,8,9时,存在摆法。
N皇后问题的解法有全排列和回溯两类,其中全排列可以递归与非递归,回溯也可以递归与非递归。于是共有4种解法,下面采用递归的回溯法解决。
算法思路
由于皇后们不能同行、同列和共对角线,每一行都必定放置了一个皇后。故可以采用回溯法从第0行开始放置,若合理,则继续放下一行,一直放到N-1行,若不合理,则回溯到上一状态,换一个位置继续放置。只要存在一种情况能够放到N-1行,并且该行合理,则表示问题有解。
①放置第一行的皇后,然后进行②
②放置第二行的皇后,先检测自身的方法是否合理。不合理则返回false,回溯到上一状态。合理则继续进行③
③…
一直到最后N-1行成功
程序语言描述
解决算法问题必须用恰当的数据结构将其描述出来,这里用一个一维数组int[] a表示。其中a[i] = j;表示第i行的皇后放在了第j列。(0<=i,j<=N-1)。
Java代码
/*
* Created by ping on 2015/8/16.
*/
public class QueensProblem {
int N;
int a[];
public QueensProblem(int N)
{
this.N = N;
a = new int[N];
for (int y=0;y<N;y++)
{
a[y] = -1;
}
}
public boolean hasSolution()
{
for (int y=0;y<N;y++)
{
if(place(0,y)) {
System.out.print(N+"皇后问题的一个解:");
for(int index = 0;index<N;index++)
System.out.print(a[index] + " ");
System.out.println("");
return true;
}
}
return false;
}
/*place(i,j)表示把i行的皇后放到第j列*/
public boolean place(int i,int j)
{
if(i<0||i>=N||j<0|j>=N)
return false;
a[i] = j; //把第i行的皇后放在第j列
for(int x=0;x<i;x++)//先检测自身是否合理
{
for(int y=x+1;y<=i;y++)
{
if(a[x]==a[y]||Math.abs(a[x]-a[y])==Math.abs(x-y)) {
a[i] = -1; //若不合理,则回退
return false;
}
}
}
if(i==N-1) return true; //之前漏了这一行!!!如果没有这一行,这违背了递归算法的原则
for (int y=0;y<N;y++)
{
if(place(i+1,y)) {
return true;
}
}
return false;
}
public static void main(String args[]) {
for(int x = 1;x<10;x++)
{
new QueensProblem(x).hasSolution();
}
}
}
代码中第50行讲到了递归算法的原则,
编写递归代码必须符合以下三点,违背其中任意一条都可能得到错误的结果或者低效的代码:
a.递归总有一个最简单的情况
b.递归总是尝试去解决一个规模更小的问题
c.父问题和子问题不应该有交集
我之前写这个代码的时候调试老是出错,用了1个小时,结果发现是由于违背了条件a,竟然漏掉了对最简单的一种情况的处理。