1848年国际西洋棋棋手马克斯·贝塞尔提出八皇后问题:在8*8的国际象棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行,同一列或同一斜线,问有多少种摆法。
首先我有以下几个问题需要解决:
第一,国际象棋盘的存储实现;
第二,应该用尝试和回溯的方法,一旦某一行失败,应回溯到上一行;
第三,怎样判断一个位置是否安全。
分析:对于棋盘的存储,我选择用8*8的二维数组实现。现在从第一行开始,要检测第一行第一个数据是否安全,若安全,设置此位置为皇后(数组值为1),然后处理第二行,处理完第二行之后要将第一行第一个数据值改为0,判断第一行第二个数据是否安全......在试了几次之后,从中找到规律:若此行没有安全的位置,则说明上一行摆放的位置不对,回到上一行。
若处理到了第i行,则步骤如下:
1、检测数组下标为[i][j]是否安全;不安全则转到5;
2、安全,设置该位置为一个皇后,则下标[i][j] = 1;
3、处理第i+1行;
4、(处理完i+1行,此时递归处理完所有行),将本位置皇后去掉,[i][j]值为0;
5、j++,转到1。
开始编写程序,首先编写一个输出二维数组的函数,如下:
void showEightQueens(int(*eq) [RANGE]) { int i; int j; for(i = 0; i < RANGE; i++) { for(j = 0; j < RANGE; j++) printf("%3d",eq[i][j]); printf("\n"); } }
以下程序为判断某位置是否安全:
int isPositionSafe(int(*eq)[RANGE], int row, int col)//判断位置是否安全 { int OK = TRUE;
int i; int j;
for(i = row - 1, j = col - 1; OK && i >= 0 && j >= 0; i--, j--) if(eq[i][j] == 1) OK = FALSE; for(i = row - 1, j = col; OK && i >= 0; i--) if(eq[i][j] == 1) OK = FALSE; for(i = row - 1, j = col + 1; OK && i >= 0 && j < RANGE; i--, j++) if(eq[i][j] == 1) OK = FALSE;
return OK; }
最主要的递归函数来了:
void eightQueens(int (*eq)[RANGE], int i) { int j;
if(i >= RANGE) showEightQueens(eq); else { for(j = 0; j < RANGE; j ++) { if(isPositionSafe(eq, i, j))//判断此位置是否安全 { eq[i][j] = 1;//若安全将此位置放皇后 eightQueens(eq, i + 1);//处理下一行 eq[i][j] = 0;//完成处理行的操作后,将皇后去掉。因为要列出所有的可能结果,则应该每个位置都判断 } } } }
编写一个递归函数时,必须考虑函数的出口。以上函数出口为:若行数等于RANGE,那么说明所有的行已经处理完毕,可以输出二维数组。用递归程序一直不熟练,不知道什么时候开始递归,递归出口,什么时候递归完成。曾经为了上台讲汉诺塔程序,用到了一种纸片的方法完成汉诺塔4个盘子的递归,方法是这样的:每个纸片上注明本次执行的函数,要调用的函数,为纸片编上序号,然后按从大到小的序号摞起来(编号小的在下面),我们很清晰的看到整个调用函数以及返回的过程。这是一个很有用的方法,对递归过程不清楚的可以采用纸片方法。
若想要知道八棋盘问题有多少种摆法,我们可以通过计算出共调用了多少次showEightQueens()函数来得出。在showEightQueens()加入static变量实现此功能。以下为完成的八皇后问题程序。
#include#include
#define RANGE 8 #define TRUE 1 #define FALSE 0
void showEightQueens(int(*eq) [RANGE]); int isPositionSafe(int (*eq)[RANGE], int row, int col); void eightQueens(int (*eq)[RANGE], int i);
void eightQueens(int (*eq)[RANGE], int i) { int j;
if(i >= RANGE)//递归函数必须要有一个出口,不能无限递归 showEightQueens(eq); else { for(j = 0; j < RANGE; j ++) { if(isPositionSafe(eq, i, j))//判断此位置是否安全 { eq[i][j] = 1;//若安全将此位置放皇后 eightQueens(eq, i + 1);//处理下一行如果下一行所有位置都不安全,则返回调用递归的位置,继续进行下面的操作。 eq[i][j] = 0;//完成此位置的操作后,将皇后去掉。因为要列出所有的可能结果,则应该每个位置都判断。 } } } }
int isPositionSafe(int(*eq)[RANGE], int row, int col)//判断位置是否安全 { int OK = TRUE;//这是一个技巧,我应该学会,相当于计算机用到很多的标志位 int i; int j;
for(i = row - 1, j = col - 1; OK && i >= 0 && j >= 0; i--, j--) if(eq[i][j] == 1) OK = FALSE; for(i = row - 1, j = col; OK && i >= 0; i--) if(eq[i][j] == 1) OK = FALSE; for(i = row - 1, j = col + 1; OK && i >= 0 && j < RANGE; i--, j++) if(eq[i][j] == 1) OK = FALSE;
return OK; }
void showEightQueens(int(*eq) [RANGE]) { int i; int j; static int count = 0;//用到了静态变量
printf("第%d种摆法\n", ++count);
for(i = 0; i < RANGE; i++) { for(j = 0; j < RANGE; j++) printf("%3d",eq[i][j]); printf("\n"); } }
void main(void) { int eightQueen[RANGE][RANGE] = {0};
eightQueens(eightQueen, 0);
getch(); }
运行结果为92种摆法。