Java编程思想—八皇后问题(数组法、堆栈法)

实验题目:回溯法实验(八皇后问题)

实验目的:

(1) 掌握回溯法求解问题的思想
(2) 学会利用其原理求解相关问题

实验要求:

	使用贪心法求出给定图各点的最短路径,并计算算法的执行时间,分析算法的有效性。利用回溯法解决八皇后问题,检验结果,并计算算法的执行时间,分析算法的有效性。 
	测试数据可以通过手工寻找三组满足需要的值,测试数组(M,N),其中 M 代表皇后所在的行,N 代表皇后所在的列。

例如,第一组测试数据:
(1,4)、(2,7)、(3,3)、(4、8)、(5,2)、(6,5)、(7,1)、 (8,6);
第二组测试数据
(1,5)、(2,2)、(3,4)、(4,7)、(5,3)、(6,8)、(7,6)、 (8,1);
第三组测试数据
(1,4)、(2,2)、(3,7)、(4,3)、(5,6)、(6,8)、(7,5)、 (8,1)。
最后与编程求得的结果进行比较。如果这三组数据在最后编程求得的结果中,说明程序的编写基本没有什么问题。

实验内容:

(1)问题描述

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后。为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。
八皇后问题

(2)实验步骤:

数组法:

① 根据 8 皇后问题,可以把其设想为一个数组;
② 根据 8 皇后的规则,可以设想为数组上同一直线,横线,斜线的数字都不能相同,由此可以得出判断条件;
③ 根据判断条件之后,建立回溯点,即可解决问题。

堆栈法:

① 检索当前行是否可以放置一个皇后;
② 利用检索过程,通过递归的方式,来确定每个皇后的位置———回溯的思想。

算法伪代码:

堆栈法
数组法

实验结果:

递归法1
递归法2
递归法3
最终结果

实验代码:

public class EightQueensOfBacktracking {
	int count = 0;
	//棋盘初始化 清空操作
	void initialChessBoard(int chessBoard[][]){
		for(int i = 0; i < 8 ; i++){
			for(int j = 0; j < 8; j++){
				chessBoard[i][j] = 0;
			}
		}
	}
	//打印皇后位置
	void showLocation(int chessBoard[][]){
		System.out.println("————————————");
		System.out.println("皇后的坐标为:");
		for(int i = 0; i < 8 ; i++){
			for(int j = 0; j < 8; j++){
				if(chessBoard[i][j] != 0){
					System.out.print(" ( " + (i+1) + " , " + (j+1) + " ) ");
				}
			}
		}
		System.out.println();
		System.out.println("棋盘如下:");
		for(int i = 0; i < 8 ; i++){
			for(int j = 0; j < 8; j++){
				System.out.print(chessBoard[i][j] + " ");
			}
			System.out.println();
		}
	}
	//行列检查 斜线检查
	boolean checkAll(int i, int j, int chessBoard[][]){
		int tempI = i;
		int tempJ = j;
		if((i>7)||(j>7)) return false;
		//check column
		for(int k = 0; k <= j; k++){
			if(chessBoard[i][k] != 0){
				return false;
			}
		}
		//check row
		for(int k = 0; k <= i; k++){
			if(chessBoard[k][j] != 0){
				return false;
			}
		}
		//左上斜线检查
		while(true){
			if(chessBoard[i][j] != 0)
				return false;
			if((i == 0)||(j == 0)) break;
			i--;
			j--;
		}
		//右上斜线检查
		i = tempI;
		j = tempJ;
		while(true){
			if(chessBoard[i][j] != 0)
				return false;
			if((i == 0)||(j == 7)) break;
			i--;
			j++;
		}
		return true;
	}
	
	//堆栈方法
	public void findQueen(int i, int chessBoard[][], EightQueensOfBacktracking eightQueens){
		if(i>7){
			eightQueens.count++;
			eightQueens.showLocation(chessBoard);
		}
		//回溯法
		boolean judge; 
		for(int m = 0; m<8; m++){
			judge = eightQueens.checkAll(i, m, chessBoard);
			if(judge){
				chessBoard[i][m] = 1;
				eightQueens.findQueen(i+1, chessBoard, eightQueens);
				chessBoard[i][m] = 0;
			}
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//函数调用
		EightQueensOfBacktracking eightQueens = new EightQueensOfBacktracking();
		//摆法的数量
		int count = 0;
		int k = 0;//临时变量
		int i = 0, j= 0;//i 行 ;j 列
		boolean judge = true;//检查结果
		//8个皇后,1-8表示
		int queens = 1;
		//棋盘 8*8 
		int chessBoard[][] = new int [8][8];
		//每次找到解后清盘,即初始化
		eightQueens.initialChessBoard(chessBoard);
		
		long startTime2 = System.nanoTime();
		//堆栈方法:
		eightQueens.findQueen(0, chessBoard, eightQueens);
		long endTime2 = System.nanoTime();
		
		
		//数组方法
		//失败1:在当前行非末尾:列+1;
		//失败2:在当前行的末尾:判断行,如果是0行,则所有情况已遍历,结束;
//													如果不是,回到上一行,遍历,查找到上一行的棋子,记录位置,找到下一位置,则结束查找上一个位置;
//															如果上一行也遍历完,列的位置为7,上一个位置处没有下一位置,则再找上上一行的位置,进行上面的循环;
//																	如果上一行是0行,则也结束遍历,flag为0,结束上一位置的查找;
													
		//成功:标记位置,如果位置标记是8,则为一种方案,输出,方法计数加一,标记减1,queens表示下一个要放的棋子;
//					行加1,列归0,通过失败1和失败2再找到上一个棋子的的位置
		long startTime1 = System.nanoTime();
		int flag = 1;
		while(true){
			judge = eightQueens.checkAll(i, j, chessBoard);
			//这一行未检查完,检查这一行下一个位置
			if((judge == false)&&(j != 7)){
				j++;
				continue;
			}
			else if((judge == false)&&(j == 7)){
				if(i == 0) break;//表示所有的情况已经遍历,结束循环
				i--;//回到上一行
				k = 0;//查找,用来遍历
				while(true){
					if(chessBoard[i][k] != 0){//找到上一行棋子的位置
						queens = chessBoard[i][k];//表示这个棋子放到棋盒中
						chessBoard[i][k] = 0;//把棋子取走
						j = k + 1;//找到下一个位置,准备下一次循环
						if(j > 7){//如果上一行也遍历完,找到上上一行
							if(i == 0){
								flag = 0;
								break;//如果上一行是0,那么没有上上一行,结束
							}
							i--;
							k =0;
							continue;//重新开始查找棋子
						}
						break;
					}
					k++;
				}
				if(flag == 0){
					break;
				}
				continue;
			}
			//检查通过,放下棋子,到下一行
			chessBoard[i][j] = queens;
			if(queens == 8){//找到一种方法
				eightQueens.showLocation(chessBoard);
				count++;
				queens--;
				//输出后,假装这个摆法不行,继续查找
			}
			queens++;//queens的值表示下一个要放的棋子
			i++;
			j = 0;
			
		}
		long endTime1 = System.nanoTime();
		System.out.println("数组方法共有" + count + "种摆法");
		System.out.println("堆栈递归方法共有" + eightQueens.count + "种摆法");
		System.out.println("数组程序运行时间:" + (endTime1 - startTime1) + "ns");
		System.out.println("堆栈递归程序运行时间:" + (endTime2 - startTime2) + "ns");
	}

}

出现的问题:

问题一:条件检查

实验时,排序的结果出现问题,斜线的情况不能检查出来。
问题1
于是我仔细检查了判断部分的代码,发现是变量的重复使用,导致无法正常判断。i和j的值被重复使用。我通过临时变量进行存储,在上一次使用后进行重新赋值,解决了问题,如红圈。
解决1

问题二:数组法 跳出循环情况分析

一开始不知道递归方法,想用情况分析,循环查找,但是发现在查找时,只能查找到第一个棋子在1,1位置的情况。
问题2.1
我猜测是跳出循环的判断出了问题,于是在我的检查下,发现下图第一个圈是要跳出循环,结束整个查找,由于是双重循环,所以我直接在第二个圈设置如果i= =0,则结束循环,共两次跳出。
问题2.2
但是我忽略了限制条件,即在i = = 0的时候,并不是都是要结束的,只有圈1的那一种情况才跳出,所以我设置flag变量进行传递,纠正了程序的错误。 更正的代码 见 实验代码 部分。

实验心得:

本次实验体现的是回溯法。经过本次实验,发现自己对回溯的理解并不全面,不会应用。初次做这个题,想的只是遍历,在循环中,进行人工的回溯。后来发现回溯时的情况分析十分复杂,并不能很好的发现并且处理所有情况,只求出4种结果。经过学习后了解到,对于回溯,本题不需要自己考虑情况,只需给出限制条件进行筛选,在满足条件的情况下进行重复调用自身函数,在完成函数后,进行自身位置的值的清空,为之后回溯进行准备即可。让我明白递归是回溯的一种很好的实现方式。
在使用java的过程中,为了解决遇到的问题还进行了调试,让我对debug和调试有了进一步的掌握。
说明:递归法是调用系统堆栈进行操作,所以属于堆栈法。

递归堆栈方法可以参考链接:八皇后递归堆栈方法

posted @ 2019-05-23 17:59  Comet_Fei  阅读(479)  评论(0编辑  收藏  举报