数据结构与算法 & 八皇后问题 & 递归实例

一、八皇后问题

1.定义(百度):

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。

备注:国际象棋里,皇后可以走直线和斜线。简单来说就是一个“米”形移动方向。
在这里插入图片描述如图:上述箭头是皇后移动方向,由于她的移动方向上没有其他皇后,所以,这个皇后处于安全位置。

2.问题分析

1.棋盘如何表达?
2.如何判断某一皇后处于该位置是否安全?
3.不同皇后间的影响如何解决?
4.什么时候算找到一个解?

3.问题解决

  1. 棋盘可以用一个 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. 安全判定
    简单想法,我们都是从第一行开始放一个皇后,接着逐行放置皇后,每次放置时,我们都要判断这一行的该位置是否安全。 那么如何判断?结合皇后的移动方向。

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.找到递归结束

递归最怕的就是没有结束。那么什么是结束呢?
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 家

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值