【回溯法】-【N皇后问题】的2种解法

N皇后问题

给定一个N*N的棋盘,要在棋盘上摆放N个皇后,
并且满足N个皇后中任意两个皇后都不处于
同一行、同一列、同一斜线上(正斜线、反斜线)

在这里插入图片描述

再中级软件设计师考试中,是用一维数组来表示每一个皇后的行列位置(而不是用二维数组)。

第i个皇后(即放在第i行上的皇后)的位置表示为:(i, q[i])。
i 表示行数;q[i] 表示列数。

在这里插入图片描述

总行数为N,假设N=4,则:
i 表示行数,
当i=0时,表示还没有开始往棋盘上放皇后;当i=4时,表示尝试到了最后一行。
q[i] 表示列数,
q[i]从1到4逐渐增大,表示在第i行上从1到4逐列移动,依次尝试是否是合法的位置。当q[i]>4时,表示超出了棋盘的范围,也就是该行没有找到合法的位置。

第i行元素:Qi
第j行元素:Qj

核心思想:

// 判断是否在同一列上
Qi列==Qj列

// 判断是否在同一斜线上
|行号之差|=|列号之差|,即:|Qi行-Qj行|=|Qi列-Qj列|

#include<stdio.h>
#include<math.h> 	//

#define N 4 	// 有4个皇后

int answer = 0; 	// 方案数

int q[N+1]; 	
/*
q[i]表示第i个元素
q[i]中存储的数字,表示的是是第i个皇后的列号(第i行的皇后当前位于哪列)
q[i]=0时,表示棋子还没有放上棋盘
*/



// 检查第j个皇后(放在第j行的皇后)的位置是否合法——(如果不合法则返回0,如果合法则返回1)
// 思路:从第1行到第j-1行逐行检查,检查这些行上已放置的皇后的位置和第j行上的皇后的位置是否有冲突
int check(int j){

	int i;
	
	for(i=1; i<j; i++){
		if((q[i]==q[j])||(abs(i-j)==abs(q[i]-q[j]))){
			return 0;
		}
	}
	
	return 1;
}
// 通过调研上边这个方法,来找出N个皇后的位置(即求解N皇后方案)

方法1:非递归方法(循环迭代)

题面已经给出的前提是:每个元素按行往下放,所以不需要判断是否是在同一行上
(不存在两个元素放在同一行的情况)
最终输出满足条件的方案——这四行的元素分别位于哪一列上。

// 求解N皇后的方案(找出N个皇后的位置)
void queen(){

	int i;
	
	// N个皇后的位置赋初值,都赋值为0(表示都还没有放入棋盘)
	for(i=1; i<=N; i++){
		q[i]=0;
	} 	

	int j=1;
	
	while(j>=1){
		
		// 从第1行开始,每一行从列下标0位置开始往后,依次向后边的列尝试各位置
		q[j]=q[j]+1;
		
		// 如果当前位置不合法,就向后移动一列
		// 不断向后移动尝试,要么是在4列之中找到了一个合法位置时停下来,要么是在尝试到了第5列时(超出范围)停下来
		while(q[j]<=N && !check(j)){
			q[j]=q[j]+1;
		}
		
		/*
		刚刚尝试摆放第j个皇后,在第j行上从前到后逐个尝试最后停下,判断停下来的时候该皇后的位置是那种情况:
		(1)在第j行上的前4列之中找到了一个合法位置时停下来(表示第j个皇后找到了一个合法的位置)
		(2)在尝试到了第5列时因为超出范围停下来(表示第j个皇后没有找到了一个合法位置)
		*/ 
		if(q[j]<=N){
			// 表示第j个皇后找到了一个合法的位置
			
			if(j==N){
				// 表示当前已经尝试到了最后一行(已经尝试到了最后一个皇后)
				answer=answer+1;
				printf("方案%d:",answer);
				for(i=1; i<=N; i++){
					printf("%d ",q[i]);
				}
				printf("\n");
			} else {
				// 表示当前找到合法位置的皇后还不是最后一个皇后(还没有尝试到最后一行),继续摆放下一个皇后
				j=j+1;
			}
		
		} else {
			// 表示第j个皇后没有找到一个合法位置
			
			// 回溯
			q[j] = 0;		// 将第j个皇后移出棋盘(下一次尝试摆放第j行时,从第一列开始重新尝试)
			j = j-1;		// 回溯(回退到上一行,尝试上一行中的下一种可能性)
			// 当j减小到0的时候,表示所有的可能性已经都尝试过一遍了,所以while后边的条件是j>=1
		}
	}

}



//运行代码
int main(){

	queen();
	
	return 0;
}

方法2:递归方法

check()方法以及之前的代码是一样的

区别主要在于queen()方法

// 求解N皇后的方案(找出N个皇后的位置)
// j代表行数
void queen(int j){ 		
	
	int i;
	
	for(i=1; i<=N; i++){
	
		q[j] = i; 		// 第j行上的皇后,从第1列到第4列的位置依次尝试
		
		if(check(j)){
			// 若check(j)为真,则表示第j行上的皇后的当前位置(j, q[j])合法
			// 若check(j)为假,则表示第j行上的皇后的当前位置(j, q[j])不合法,不需要做其他操作,直接尝试下一列是否合法就行了(也就是循环中的i++,然后把i赋值给q[j])

			if(j==N){
				// 当前已经找到了最后一个皇后(最后一行的皇后位置已确定)
				answer=answer+1;
				print("方案%d:",answer);
				for(i=1; i<=N; i++){
					printf("%d ",q[i]);
				}
				printf("\n");

			} else {
				// 当前找到的皇后还不是最后一个皇后(最后一行的皇后位置还未确定)
				// (递归摆放下一个皇后的位置)
				queen(j+1);
			}

		} else {
			// 若check(j)为假,则表示第j行上的皇后的当前位置(j, q[j])不合法,不需要做其他操作,直接尝试下一列是否合法就行了(也就是循环中的i++,然后把i赋值给q[j])
		}
		
	}

}



//运行代码
int main(){

	// 从第1行开始放,如果位置合法,则递归(开始尝试在第2行放置),知道第N=4行为止
	queen(1);
	
	return 0;
}

递归方法调用时的实现原理,可以用“栈”来理解。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值