一、八皇后问题
1.定义(百度):
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
备注:国际象棋里,皇后可以走直线和斜线。简单来说就是一个“米”形移动方向。
如图:上述箭头是皇后移动方向,由于她的移动方向上没有其他皇后,所以,这个皇后处于安全位置。
2.问题分析
1.棋盘如何表达?
2.如何判断某一皇后处于该位置是否安全?
3.不同皇后间的影响如何解决?
4.什么时候算找到一个解?
3.问题解决
- 棋盘可以用一个 8 * 8的二维数组。其中0代表无皇后,1代表有皇后。
#define WIDTH 8
boolean chessBoard[WIDTH][WIDTH] = {0};
//Boolean 是 unsigned char 类型
显示棋盘:
void showChessBoard(boolean (*chessBoard)[WIDTH]) {
int row, col;
static int count = 0;
// 由于结果有92个,所以显示棋盘相当于调用92次,静态存储可以保留每次调用的次数记录
printf("第%d个情况:\n", ++count);
for (row = 0; row < WIDTH; row++) {
for(col = 0; col <WIDTH; col++) {
printf("%4d",chessBoard[row][col]);
}
printf("\n");
}
}
- 安全判定
简单想法,我们都是从第一行开始放一个皇后,接着逐行放置皇后,每次放置时,我们都要判断这一行的该位置是否安全。 那么如何判断?结合皇后的移动方向。
1)以该位置为基准,其左上方直线上是否存在皇后?
2)以该位置为基准,其正上方直线上是否存在皇后?
3)以该位置为基准,其右上方直线上是否存在皇后?
如图:三个线代表判断方向
boolean isSafe(int row, int col, boolean (*chessBoard)[WIDTH]) {
int i, j;
//左上方
for (i = row - 1, j = col - 1; i >= 0 && j >= 0 ; i--, j--) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
//正上方
for (i = row - 1, j = col; i >= 0; i--) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
//有上方
for (i = row - 1, j = col + 1; i >= 0 && j <WIDTH; --i, ++j) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
return TRUE;//安全
}
- 接下来的三、四两个问题,合起来就是一个递归的解决。
二、八皇后中递归
1.找到递归结束
递归最怕的就是没有结束。那么什么是结束呢?
1.成功找到一个了。这时候一定是前八行全部放置好了。所以这个结束标志是row >= 8;
2.递归过程中,如果哪一行没有找到合理位置放置皇后,那么在这一行所有列遍历过后,本次调用结束,返回上次调用。
2.实现递归
void chess(boolean (*chessBoard)[WIDTH], int row) {
int col;
if (row >= WIDTH) {
showChessBoard(chessBoard);//找到一个,显示出来
return;
}
for (col = 0; col < WIDTH; col++) {
if (isSafe(row, col ,chessBoard)) {
chessBoard[row][col] = TRUE;
chess(chessBoard, row+1);//进入下一层调用
chessBoard[row][col] = FALSE;
}
}//循环结束,代表没有找到合理位置,返回上一层
}
成功情况如下:黑线代表调用关系
返回情况:
蓝色圈是可能的皇后位置,但是由于不安全(蓝色箭头指出),所以这次调用(黑色线)失败,返回上一级调用,此时出现红色箭头(chessBoard[row][col] = FALSE;语句),修改此层皇后位置,如果此层仍失败,继续回到上一层。
运行结果:
三、总结
1.分析好问题,往往能得到解题思路。
2.递归,多动手,光想容易乱。
笔者水平有限,目前只能描述以上问题,如果有其他情况,可以留言,有错误,请指教,有继续优化的,请分享,谢谢!
本篇文章是否有所收获?阅读是否舒服?又什么改进建议?希望可以给我留言或私信,您的的分享,就是我的进步。谢谢。
整体代码如下:
eightQueen.c:
#include <stdio.h>
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
#define WIDTH 8
boolean isSafe(int row, int col, boolean (*chessBoard)[WIDTH]);
void showChessBoard(boolean (*chessBoard)[WIDTH]);
void chess(boolean (*chessBoard)[WIDTH], int row);
void chess(boolean (*chessBoard)[WIDTH], int row) {
int col;
if (row >= WIDTH) {
showChessBoard(chessBoard);//找到一个,显示出来
return;
}
for (col = 0; col < WIDTH; col++) {
if (isSafe(row, col ,chessBoard)) {
chessBoard[row][col] = TRUE;
chess(chessBoard, row+1);
chessBoard[row][col] = FALSE;
}
}//循环结束,代表没有找到合理位置,返回上一层
}
void showChessBoard(boolean (*chessBoard)[WIDTH]) {
int row, col;
static int count = 0;
// 由于结果又92个,所以显示棋盘相当于调用92次,静态存储可以保留每次调用的次数记录
printf("第%d个情况:\n", ++count);
for (row = 0; row < WIDTH; row++) {
for(col = 0; col <WIDTH; col++) {
printf("%4d",chessBoard[row][col]);
}
printf("\n");
}
}
boolean isSafe(int row, int col, boolean (*chessBoard)[WIDTH]) {
int i, j;
//左上方
for (i = row - 1, j = col - 1; i >= 0 && j >= 0 ; i--, j--) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
//正上方
for (i = row - 1, j = col; i >= 0; i--) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
//有上方
for (i = row - 1, j = col + 1; i >= 0 && j <WIDTH; --i, ++j) {
if (chessBoard[i][j] == 1) {
return FALSE;
}
}
return TRUE;//安全
}
int main() {
boolean chessBoard[WIDTH][WIDTH] = {0};
chess(chessBoard, 0);
return 0;
}
2020年03.20 家