//一个很有研究意义的算法,借鉴贺老师的程序与讲解,珍惜这个资源,在此感谢老师 ^-^
/*在8×8的棋盘上摆放8个皇后,使其不能互相攻击,即任意的两个皇后不能处在同一行,同一列,或同一斜线上。可以把八皇后问题拓展为n皇后问题,
即在n×n的棋盘上摆放n个皇后,使其任意两个皇后都不能处于同一行、同一列或同一斜线上。
首先需要对棋盘进行描述。直观地,棋盘可以用二维数组表示,有皇后的棋格对应数组元素值为1,无皇后的棋格对应数组元素值为0。
但这种存储结构并不是最简单有效的选择。
图8.21中左边部分给棋盘的行、列编了号,提供的摆放方法,就是问题的一个解。
右边的部分,将各行上皇后所在的列数记录下来,用这8个数字(4, 6, 8, 2, 7, 1, 3, 5),也构成了对问题解的一种描述。
由此可以看出,可以定义一个一维数组int x[N];,用x[i]的值表示第i行上皇后所在的列数,n皇后问题的解可以用(x[1], x[2], ….. x[n])的形式描述。
解决了数据表示的问题,设计数据处理的方法。这里要用回溯的策略,设计计算机对n皇后问题的求解方法。以4皇后为例,
如图8.22所示,在图8.22(a)中,第1行第1列上放置一个皇后,图8.22(b)中确定第2行的可能放法,
在尝试第1列、第2列由于相互攻击而放弃之后,确定在第3列放置可以继续,在图8.22(c)中继续对第3行进行考察,
发现将所有4列都尝试过了,也没有办法将皇后安排一个合适的位置,对第4行做任何的尝试都没有意义,这时产生回溯,
结果是在图8.22(d)中将第2行的皇后安排到第4列,然后第3行的暂时可以放在第2列,
在图8.22(e)中试着确定第4行的皇后,却发现无解再次回溯,只能够如图8.22(f)所示将第1行的皇后放到第2列,
再经图8.22(g)、(f)之后找到4皇后问题的一个解,那就是图8.22(g)的(2, 4, 1, 3)。
在图8.23中,给出了求出4皇后问题所有解的完整过程的描述。图中(1 * * *)对应图8.22(a)中第1行皇后安排在第1列,
其他行待定的状态,接下来的(1 3 * *)对应了图8.22(b)中第2行皇后安排在第3列的状态。可以判断出在这个状态下,
继续尝试并不能够完成求解,于是发生回溯(其下方的B代表回溯),于是下一个尝试的状态将是(1 4 * *),……。
将这样的过程继续下去,能够找出4皇后问题的所有解(2 4 1 3)和(3 1 4 2),如图8.23中两个加网格背景的结点。
#include <stdio.h> #include <math.h> #include <malloc.h> void nQueens(int *x, int n); /*求解n皇后问题*/ int place(int *x, int k); /*判断是否可以在第k行第x[k]列摆放皇后*/ void printSolution(int *x, int n); /*输出求解结果*/ int main() { printf("欢迎探讨八皇后问题,请任意输入一个整数,以列出算法:"); int n; int *x; /*存放求解结果的数组首地址*/ scanf("%d", &n); x=(int*)malloc(sizeof(int)*(n+1)); /*动态分配数组空间, x[0]空闲*/ nQueens(x, n); return 0; } /*如果一个皇后能放在第k行第x[k]列,则返回真(1),否则返回假(0)*/ int place(int *x, int k) { int i; /*对前k-1行,逐行考察*/ for(i=1; i<k; i++) { /*如果前k-1行中有某行的皇后与第k行的在同一列或同一斜线,返回0*/ if((x[i]==x[k])||(fabs(x[i]-x[k])==fabs(i-k))) return 0; } /*能执行下一句,说明在第k行第x[k]列摆放皇后,不会互相攻击*/ return 1; } /*求解在n×n的棋盘上,放置n个皇后,使其不能互相攻击*/ void nQueens(int *x, int n) { int k; k = 1; /*k是当前行*/ x[k] = 0; /*x[k]是当前列,进到循环中,立刻就会执行x[k]++,而选择了第1列*/ while(k>0) /*当将所有可能的解尝试完后,k将变为0,结束求解过程*/ { x[k]++; /*移到下一列*/ while(x[k]<=n && !place(x, k)) /*逐列考察,找出能摆放皇后的列x[k]*/ x[k]++; if(x[k]<=n) /*找到一个位置可以摆放皇后*/ { if(k==n) /*是一个完整的解,输出解*/ printSolution(x, n); else /*没有完成最后一行的选择,是部分解,转向下一行*/ { k++; /*接着考察下一行*/ x[k]=0; /*到循环开始执行x[k]++后,下一行将从第1列开始考察*/ } } else /*对应x[k]>n的情形,这一行已经没有再试的必要,回溯到上一行*/ k--; /*上一行在原第x[k]列的下1列开始考察*/ } } /*输出求解结果*/ void printSolution(int *x, int n) { int i, j; for (i = 1; i <= n; i++) /*输出第i行*/ { for (j=1; j<=n; j++) { if (j == x[i]) /*第x[i]列输出Q,其他列输出*号 */ printf("Q"); else printf("*"); } printf("\n"); } printf("\n"); }
关键:
定义一个一维数组int x[N];,用x[i]的值表示第i行上皇后所在的列数,n皇后问题的解可以用(x[1], x[2], ….. x[n])的形式描述。
解决了数据表示的问题,设计数据处理的方法。这里要用回溯的策略,设计计算机对n皇后问题的求解方法。以4皇后为例,
如图8.22所示,在图8.22(a)中,第1行第1列上放置一个皇后,图8.22(b)中确定第2行的可能放法,
在尝试第1列、第2列由于相互攻击而放弃之后,确定在第3列放置可以继续,在图8.22(c)中继续对第3行进行考察,
发现将所有4列都尝试过了,也没有办法将皇后安排一个合适的位置,对第4行做任何的尝试都没有意义,这时产生回溯,
结果是在图8.22(d)中将第2行的皇后安排到第4列,然后第3行的暂时可以放在第2列,
在图8.22(e)中试着确定第4行的皇后,却发现无解再次回溯,只能够如图8.22(f)所示将第1行的皇后放到第2列。
输出结果如下: