八皇后详解(.java .cpp .c)三种代码(含数据结构栈的应用)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_40176716/article/details/85336869

本篇文章上半部分来自于公众号 程序员小灰

八皇后问题是一个古老而著名的问题,是回溯算法的的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8´8格的国际象棋棋盘上,安放八个皇后,要求没有一个皇后能够“吃掉”任何其他一个皇后,即任意两个皇后都不能处于同一行、同一列或同一条对角线上,求解有多少种摆法。

高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法得出结论,有92种摆法。

 

国际象棋中的皇后,可以横向、纵向、斜向移动。如何在一个8X8的棋盘上放置8个皇后,使得任意两个皇后都不在同一条横线、竖线、斜线方向上

让我们来举个栗子,下图的绿色格子是一个皇后在棋盘上的“封锁范围”,其他皇后不得放置在这些格子:

下图的绿色格子是两个皇后在棋盘上的“封锁范围”,其他皇后不得放置在这些格子:

如何解决八皇后问题?

所谓递归回溯,本质上是一种枚举法。这种方法从棋盘的第一行开始尝试摆放第一个皇后,摆放成功后,递归一层,再遵循规则在棋盘第二行来摆放第二个皇后。如果当前位置无法摆放,则向右移动一格再次尝试,如果摆放成功,则继续递归一层,摆放第三个皇后......

如果某一层看遍了所有格子,都无法成功摆放,则回溯到上一个皇后,让上一个皇后右移一格,再进行递归。如果八个皇后都摆放完毕且符合规则,那么就得到了其中一种正确的解法。

说起来有些抽象,我们来看一看递归回溯的详细过程。

1.第一层递归,尝试在第一行摆放第一个皇后

2.第二层递归,尝试在第二行摆放第二个皇后(前两格被第一个皇后封锁,只能落在第三格):

3.第三层递归,尝试在第三行摆放第三个皇后(前四格被第一第二个皇后封锁,只能落在第五格):

4.第四层递归,尝试在第四行摆放第四个皇后(第一格被第二个皇后封锁,只能落在第二格):

 

5.第五层递归,尝试在第五行摆放第五个皇后(前三格被前面的皇后封锁,只能落在第四格):

6.由于所有格子都“绿了”,第六行已经没办法摆放皇后,于是进行回溯,重新摆放第五个皇后第八格。:

7.第六行仍然没有办法摆放皇后,第五行也已经尝试遍了,于是回溯到第四行,重新摆放第四个皇后第七格。:

8.继续摆放第五个皇后,以此类推......

八皇后问题的代码实现?

解决八皇后问题,可以分为两个层面:

1.找出第一种正确摆放方式,也就是深度优先遍历。

2.找出全部的正确摆放方式,也就是广度优先遍历。

在研究代码实现的时候,我们需要解决几个问题:

1.国际象棋的棋盘如何表示?

很简单,用一个长度是8的二维数组来表示即可。

static final int MAX = 8;
int qipan[][] = new int[MAX][MAX];

由于这里使用的是int数组,int的初始值是0,代表没有落子。当有皇后放置的时候,对应的元素值改为1。

在这里,二维数组的第一个维度代表横坐标,第二个维度代表纵坐标,并且从0开始。比如qipan[3][4]代表的是棋盘第四行第五列格子的状态。

2.如何判断皇后的落点是否合规?

定义一个check方法,传入新皇后的落点,通过纵向和斜向是否存在其他皇后来判断是否合规。

// 检查棋盘
	boolean cherk(int x, int y) {
		// 检查
		for (int i = 0; i < y; i++) {
			// 检查纵向
			if (qipan[x][i] != 0)
				return false;
			// 检查左斜
			if (x - 1 - i >= 0 && qipan[x - 1 - i][y - 1 - i] != 0)
				return false;
			// 检查右斜
			if (x + 1 + i < MAX && qipan[x + 1 + i][y - 1 - i] != 0)
				return false;
		}
		// 若可以放置则放置
		return true;
	}

3.如何进行递归回溯?

递归回溯是本算法的核心,代码逻辑有些复杂

PS:此处代码修改了,小灰的代码知识输出一个,本次修改完后输出92种

// 递归函数放置皇后
	boolean setqueen(int y) {
		// 行数超过8,则说明找出正确答案
		if (y == MAX)
			return true;
		// 遍历当前行,逐一验证
		for (int i = 0; i < MAX; i++) {
			// 为当前行清零,防止出现脏数据
			for (int x = 0; x < MAX; x++) {
				qipan[x][y] = 0;
			}
			// 检查是否符合规则,符合则进一步递归
			if (cherk(i, y)) {
				qipan[i][y] = 1;
				// 递归如果返回true说明下层找到解决,无需继续循环
				if (setqueen(y + 1)) {
					System.out.println("第"+ k + "个排列");
					k++;
					printqipan();
					return false;
				}
			}
		}
		return false;
	}

4.如何输出结果?

这个问题很简单,直接遍历二维数组并输出就可以。

PS:此处代码修改了,小灰的代码是输出二维数组,这个输出一个棋盘

	void printqipan() {
		// 遍历数组输出
		for (int j = 0; j < MAX; j++) {
			for (int i = 0; i < MAX; i++) {
				if (qipan[i][j]!=0) {
					System.out.print("●");
				}else {
					System.out.print("□");
				}
			}	
			System.out.println();
		}
	}

5.如何把这些方法串起来?

在main函数里分三步来调用:

第一步:初始化

第二步:递归摆放皇后

第三步:最后输出结果。

其中Queen是整个类的名字。


public class Main {

	
	public static void main(String[] args) {
		Queen queen = new Queen();
		queen.setqueen(0);
	}
	
}

最终输出如下:(图太长,就不粘贴了)

其实写到这,↑面的90%都是抄的,怎么算原创呢?

下面才是自己改的并写的。

java本来就是在小灰的基础上改的

附上java代码

public class Queen{

	static final int MAX = 8;
	int qipan[][] = new int[MAX][MAX];
	int k=1;
	
	
	// 检查棋盘
	boolean cherk(int x, int y) {
		// 检查
		for (int i = 0; i < y; i++) {
			// 检查纵向
			if (qipan[x][i] != 0)
				return false;
			// 检查左斜
			if (x - 1 - i >= 0 && qipan[x - 1 - i][y - 1 - i] != 0)
				return false;
			// 检查右斜
			if (x + 1 + i < MAX && qipan[x + 1 + i][y - 1 - i] != 0)
				return false;
		}
		// 若可以放置则放置
		return true;
	}

	// 递归函数放置皇后
	boolean setqueen(int y) {
		// 行数超过8,则说明找出正确答案
		if (y == MAX)
			return true;
		// 遍历当前行,逐一验证
		for (int i = 0; i < MAX; i++) {
			// 为当前行清零,防止出现脏数据
			for (int x = 0; x < MAX; x++) {
				qipan[x][y] = 0;
			}
			// 检查是否符合规则,符合则进一步递归
			if (cherk(i, y)) {
				qipan[i][y] = 1;
				// 递归如果返回true说明下层找到解决,无需继续循环
				if (setqueen(y + 1)) {
					System.out.println("第"+ k + "个排列");
					k++;
					printqipan();
					return false;
				}
			}
		}
		return false;
	}

	void printqipan() {
		// 遍历数组输出
		for (int j = 0; j < MAX; j++) {
			for (int i = 0; i < MAX; i++) {
				if (qipan[i][j]!=0) {
					System.out.print("●");
				}else {
					System.out.print("□");
				}
			}	
			System.out.println();
		}
	}

}

Main代码


public class Main {

	
	public static void main(String[] args) {
		Queen queen = new Queen();
		queen.setqueen(0);
	}
	
}

接下来C代码(.cpp文件下运行)

#include<stdio.h>

#define MAX 8
int qipan[MAX][MAX];
int k=1;

bool cherk(int x,int y);
bool setqueen(int y);
void printqipan();

int main(){
	printf("hello\n");
	setqueen(0);
	return 0;
}

	// 检查棋盘
	bool cherk(int x, int y) {
		// 检查
		for (int i = 0; i < y; i++) {
			// 检查纵向
			if (qipan[x][i] == 1)
				return false;
			// 检查左斜
			if (x - 1 - i >= 0 && qipan[x - 1 - i][y - 1 - i] == 1)
				return false;
			// 检查右斜
			if (x + 1 + i < MAX && qipan[x + 1 + i][y - 1 - i] == 1)
				return false;
		}
		// 若可以放置则放置
		return true;
	}

	// 递归函数放置皇后
	bool setqueen(int y) {
		// 行数超过8,则说明找出正确答案
		if (y == MAX)
			return true;
		// 遍历当前行,逐一验证
		for (int i = 0; i < MAX; i++) {
			// 为当前行清零,防止出现脏数据
			for (int x = 0; x < MAX; x++) {
				qipan[x][y] = 0;
			}
			// 检查是否符合规则,符合则进一步递归
			if (cherk(i, y)) {
				qipan[i][y] = 1;
				// 递归如果返回true说明下层找到解决,无需继续循环
				if (setqueen(y + 1)) {
					printf("第%d个排列\n",k);
					k++;
					printqipan();
					return false;
				}
			}
		}
		return false;
	}

	void printqipan() {
		// 遍历数组输出
		for (int i = 0; i < MAX; i++) {
			for (int j = 0; j < MAX; j++) {
			printf("%d",qipan[i][j]);
			}	
			printf("\n");
		}
	}

最后,是数据结构中的栈和此代码的结合,并规范使其在.c下运行

/**
*计算机科学与工程学院
*软件工程 数据结构实训第5组
*		八皇后
*要求:用到栈
*组长:莫言情难忘
*/
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 8

typedef int SElemType; 
#define MAX 8

int qipan[MAX][MAX];
int k=1;

bool cherk(int x,int y);//检查棋盘
bool setqueen(int y);//回溯

typedef struct{
	SElemType   *base;
	SElemType   *top;
	int stacksize;
}SqStack;

SqStack S;

//初始化
SqStack InitStack(SqStack *S)
{
	S->base=(SElemType*)malloc(STACK_INIT_SIZE*sizeof(SElemType));
	if(!S->base)
		printf("申请失败");
	else
	{
		S->top=S->base;
		S->stacksize=STACK_INIT_SIZE;
	}
	return *S;
}

//进栈
push (SqStack *S,SElemType e)
{
	if(S->top -S->base >=STACK_INIT_SIZE )
		printf("栈满");
	else
	{
		*S->top=e;
		S->top++;
	}
}

//出栈
pop(SqStack *S)
{
	int e;
	if(S->top == S->base )
		printf("栈空");
	else
	{
		--S->top ;
		e=*S->top ;
		//printf("出栈元素:%d  \n",e);
	}
}

//输出
void Outputstack(SqStack *S)
{
	int shuzu[8];
	int i=0;
	SElemType *p;
	//数组使其逆序
	for(p=S->top-1;p>=S->base ;p--){
		shuzu[i]=*p;
		i++;
	}
	//输出
	for(i=MAX-1;i>=0;i--)
	{
		switch(shuzu[i]){
		case 1:printf("●□□□□□□□\n");break;
		case 2:printf("□●□□□□□□\n");break;
		case 3:printf("□□●□□□□□\n");break;
		case 4:printf("□□□●□□□□\n");break;
		case 5:printf("□□□□●□□□\n");break;
		case 6:printf("□□□□□●□□\n");break;
		case 7:printf("□□□□□□●□\n");break;
		case 8:printf("□□□□□□□●\n");break;
		}
	//	printf("%d    ",shuzu[i]);
	}
		
	printf("\n");
}


void main()
{
	//初始化栈
	S=InitStack(&S);
	setqueen(0);
}

// 检查棋盘
	bool cherk(int x, int y) {
		// 检查
		for (int i = 0; i < y; i++) {
			// 检查纵向
			if (qipan[x][i] == 1)
				return false;
			// 检查左斜
			if (x - 1 - i >= 0 && qipan[x - 1 - i][y - 1 - i] == 1)
				return false;
			// 检查右斜
			if (x + 1 + i < MAX && qipan[x + 1 + i][y - 1 - i] == 1)
				return false;
		}
		// 若可以放置则放置
		return true;
	}

	// 递归函数放置皇后
	bool setqueen(int y) {
		// 行数超过8,则说明找出正确答案
		if (y == MAX)
			return true;
		// 遍历当前行,逐一验证
		for (int i = 0; i < MAX; i++) {
			// 为当前行清零,防止出现脏数据
			for (int x = 0; x < MAX; x++) {
				qipan[x][y] = 0;
			}
			// 检查是否符合规则,符合则进一步递归
			if (cherk(i, y)) {
				qipan[i][y] = 1;
				push(&S,i+1);
				// 递归如果返回true说明下层找到解决,无需继续循环
				if (setqueen(y + 1)) {
					printf("第%d个排列\n",k);
					k++;
					Outputstack(&S);
					pop(&S);//出栈,栈里8变7,Y为7
					pop(&S);//出栈,栈里7变6,Y为7
					return false;
				}	
			}	
		}
		pop(&S);
		return false;
	}

结语:此篇文章是我们数据结构课程实训结束后发的,我以前就用java写过八皇后问题的源码,这次实训,借用了小灰的思维,加以更改,顺利的完成了此次实训。

 

 

猜你喜欢

转载自blog.csdn.net/qq_40176716/article/details/85336869
cpp
今日推荐