递归调用在编程中是个十分重要的部分,掌握起来也有一定的难度。本篇将用经典例题----八皇后问题,来整理递归调用的思路。
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
即在一个8*8的棋盘上,每一行放置一个皇后,这个皇后所在的斜边竖边和横边都没有其他皇后。
先解决第一个问题,如何得到一个棋盘?
答案是二维数组。我们先建立一个行为8列为8的二维数组,每个元素的值用0和1表示,1表示有皇后,0表示无皇后,以此建立出一个棋盘。
#include
#include "tyz.h"
#define WIDTH 8
int main() {
boolean chessboard[WIDTH][WIDTH] = {
0};
return 0;
}
tyz.h是我定义的一个头文件,里面有我经常要用到的宏和类型,boolean的本质是一个unsigned char类型,因为二维数组的元素只有0和1两种,为节省空间,使用boolean类型。
为方便修改棋盘的大小,在此将其定义成一个宏
以下为我的头文件代码,这个头文件在我的编程中会经常使用到。
#ifndef _TYZ_H_
#define _TYZ_H_
#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1
typedef unsigned char boolean;
int skipBlank(const char *str);
boolean isRealStart(int ch);
#endif
定义完棋盘之后,我们需要将它显示出来,此时编写一个显示棋盘的函数。
void show(boolean (*chessboard)[WIDTH]);
void show(boolean (*chessboard)[WIDTH]) {
int row;
int col;
static int count = 0;
printf("第%d个解:\n", ++count);
for (row = 0; row < WIDTH; row++) {
for (col = 0; col < WIDTH; col++) {
printf("%5d", chessboard[row][col]);
}
printf("\n");
}
}
*我在此函数里定义了一个静态存储类count,用于记录show函数被调用的次数,即解的数量,*静态存储类变量不会随着函数的调用结束而释放,所以可以记录其次数
接下来思考第二个问题,每一行放置皇后时的方法一样吗?
我们可以自己画一个棋盘,然后自己放置皇后,几轮下来思考这个过程,即手工过程,我们会发现:每一行放置皇后的方法是一样的 ,认识到这一点我们才可以做出递归的思考。
从第一行开始放置皇后时,我们只需要考虑三个条件:
* 1.此行的左上方及其上方一条线上是否有皇后?
* 2.此行的正上方极其上方一条线上是否有皇后?
* 3.此行的右上方及其上方一条线上是否有皇后?*
基于这三个条件,我们可以写出一个判断放置皇后是否安全的函数,以便在放置皇后时进行判断。
boolean isSafe(boolean (*chessboard)[WIDTH], int row, int col);
boolean isSafe(boolean (*chessboard)[WIDTH], int row, int col) {
int i;
int j;
for (i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
for (i = row - 1, j = col; i >= 0; i--) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
for (i = row - 1, j = col + 1; i >= 0 && j < WIDTH; i--,j++) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
return TRUE;
}
现在我们已经完成了准备工作,只剩最后一个问题就是递归调用。
要实现递归调用,先考虑递归结束条件。
八皇后问题递归结束的条件是什么?是一个解法出现的时候,即从第一行到最后一行,每一行都放了一个皇后。
有了以上的分析,我们就可以开始补足其余的代码了。
递归代码如下
void chess(boolean (*chessboard)[WIDTH], int row);
void chess(boolean (*chessboard)[WIDTH], int row) {
int col;
if (row >= WIDTH) {
showChess(chessboard);
return;
}
for (col = 0; col < WIDTH; col++) {
if (isSafe(chessboard, row, col)) {
chessboard[row][col] = TRUE;
chess(chessboard, row + 1);
chessboard[row][col] = FALSE;
}
}
}
这段代码虽然不多,但是其中的递归的思想很巧妙。
如果到了某一行,皇后无法摆下去,此时这一轮函数调用结束,指令回到上一次函数调用的下一行代码,即chessboard[row][col] = FALSE; 这句代码的意思是将上一行放置的皇后取走,然后重新进行col++,再试下一个位置。
完整代码如下
#include
#include "tyz.h"
#define WIDTH 8
boolean isSafe(boolean (*chessboard)[WIDTH], int row, int col);
void showChess(boolean (*chessboard)[WIDTH]);
void chess(boolean (*chessboard)[WIDTH], int row);
void chess(boolean (*chessboard)[WIDTH], int row) {
int col;
if (row >= WIDTH) {
showChess(chessboard);
return;
}
for (col = 0; col < WIDTH; col++) {
if (isSafe(chessboard, row, col)) {
chessboard[row][col] = TRUE;
chess(chessboard, row + 1);
chessboard[row][col] = FALSE;
}
}
}
void showChess(boolean (*chessboard)[WIDTH]) {
int row;
int col;
static int count = 0;
printf("第%d个解:\n", ++count);
for (row = 0; row < WIDTH; row++) {
for (col = 0; col < WIDTH; col++) {
printf("%5d", chessboard[row][col]);
}
printf("\n");
}
}
boolean isSafe(boolean (*chessboard)[WIDTH], int row, int col) {
int i;
int j;
for (i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
for (i = row - 1, j = col; i >= 0; i--) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
for (i = row - 1, j = col + 1; i >= 0 && j < WIDTH; i--,j++) {
if (1 == chessboard[i][j]) {
return FALSE;
}
}
return TRUE;
}
int main() {
boolean chessboard[WIDTH][WIDTH] = {
0};
chess(chessboard, 0);
return 0;
}