一、实验题目
- 实验要求:
- 对九宫重排问题,建立图的启发式搜索求解方法;
- 用A*算法求解九宫重排问题。
- 实验题目
3х3九宫棋盘,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,移动棋子从初始布局到达目标布局,求解移动步骤并输出。请设计算法,使用合适的搜索策略,在较少的空间和时间代价下找到最短路径。
二、编程语言以及开发环境
1. 编程语言:
选择Java,因为在上学期 project 01中对Java进行了一次提高,现在习惯用Java来编写
2. 开发环境:
选择IntelliJ IDEA,使用起来更加方便
三、源代码
1.main类
package ImplicitFigure;
public class main {
public static void main(String[]args){
serves serves = new serves();
serves.thisIsStart();
}
}
2.节点类
package ImplicitFigure;
public class Node {
//用三维数组存储当前状态的情况
int [][] currentState = new int[3][3];
//初始位置没有父状态
Node DadState =null;
//空格变化,可能引起的状态变化有四个,即四个方向,记它变化后的四个状态为四个孩子状态
Node child1 = null , child2 = null , child3 = null , child4=null;
//评估价值的函数 f = g + h , h为变化层数,假设初始状态为第0层。下一状态为第1层
private int f;
//层数默认初始状态为0层
int h=0;
/**
* 带参数的构造函数
* @param States
* @param dad
* @param child1
* @param child2
* @param child3
* @param child4
* @param h
*/
public Node(int [][]States,Node dad,Node child1,Node child2,Node child3,Node child4,int h){
//将外界数组传入
for(int i=0;i<3;i++){
for(int j=0;j<3;j++)
currentState[i][j]=States[i][j];
}
//孩子状态传入
this.child1 =child1;
this.child2 =child2;
this.child3 =child3;
this.child4 =child4;
//层数传入
this.h=h;
}
public void setF(int g)
{
f=g+h;
}
public int getF(){
return f;
}
}
3.算法类
package ImplicitFigure;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class serves {
//node类:数组,父状态,子状态,层数,价值函数f
Node Begin;
Node End;
//open链表记录空格移动后的新状态
List<Node> open = new ArrayList<>();
//close链表存储已经确定了的状态
List<Node> close = new ArrayList<>();
Scanner scanner=new Scanner(System.in);
/**
* 构造函数
*/
public serves(){
//题目要求二维数组逐个输入
System.out.println("起始位置:");
int [][]startState=new int[3][3];
int [][]purposeState=new int[3][3];
for(int i=0;i<3;i++)
for(int j=0;j<3;j++){
System.out.println("请输入你的数字:");
startState[i][j]=scanner.nextInt();
}
//begin存入初始状态
Begin = new Node(startState,null,null,null,null,null,0);
//目标要求二维数组逐个输入
System.out.println("目标位置:");
for(int i=0;i<3;i++)
for (int j = 0; j < 3; j++) {
System.out.println("请输入你的数字:");
purposeState[i][j] = scanner.nextInt();
}
//End存入结果状态
End =new Node(purposeState,null,null,null,null,null,0);
}
/**
* 判断题目是否有答案
* @param begin
* @param end
* @return
*/
public boolean judgeIfResult(Node begin, Node end){
//用一维数组开始记录展开
int []oneArray = new int[9];
int []oneArray2 = new int[9];
int inverseNumber1 = 0, inverseNumber2 = 0 , count1 = 0, count2 = 0;
//二维数组展开
int i,j;
for(i=0;i<3;i++) {
for(j=0;j<3;j++){
oneArray[count1++] = begin.currentState[i][j];
oneArray2[count2++] = end.currentState[i][j];
}
}
//找第一个状态的逆序数
for(i=0;i<9;i++){
for(j=0;j<i;j++){
if(oneArray[j]>oneArray[i] && oneArray[j]!=0 && oneArray[i]!=0)
inverseNumber1 ++;
}
}
//找第二个状态的逆序数
for(i=0;i<9;i++){
for(j=0;j<i;j++){
if(oneArray2[j]>oneArray2[i] && oneArray2[j]!=0 && oneArray2[i]!=0)
inverseNumber2 ++;
}
}
//判断两个逆序数的奇数偶性是否相等
if(inverseNumber1 %2 == inverseNumber2 %2)
return true;
else
return false;
}
/**
* 判断是不是已经完成了题目的要求
* @param begin
* @param end
* @return
*/
public boolean ifFinish (Node begin, Node end) {
//判断两个二维数组是否一样
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++){
if(begin.currentState[i][j] != end.currentState[i][j])
return false;
}
}
return true;
}
/**
*打印显示移动步骤
*/
public void printShow(){
System.out.println("移动步骤如下:");
for(int count = close.size()-1; count >= 0 ; count--)
{
System.out.println("第" + (close.size()-count) + "步:");
//输出当前已经确定的close链表里的状态的二维数组
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++){
System.out.print(close.get(count).currentState[i][j]+" ");
}
System.out.println();
}
}
}
/**
*添加空格移动四个方向之后的四个状态
* @param parent
*/
public void addState( Node parent ){
//存放空格位置
int count;
//移动后的新状态
Node newState;
//定义四个二维数组用来存储
int [][]twoArray1 = new int[3][3];
int [][]twoArray2 = new int[3][3];
int [][]twoArray3 = new int[3][3];
int [][]twoArray4 = new int[3][3];
//把当前状态的二维数组赋值给新定义的四个数组
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++){
twoArray1[i][j]=parent.currentState[i][j];
twoArray2[i][j]=parent.currentState[i][j];
twoArray3[i][j]=parent.currentState[i][j];
twoArray4[i][j]=parent.currentState[i][j];
}
}
//寻找空格位置
int x=0,y=0;
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++){
if(twoArray1[i][j]==0)
{
x=i;
y=j;
}
}
}
//判断空格是否能够上移
if ( x-1 >= 0)
{
//空格赋值
count = twoArray1[x][y];
//空格上边的值换到空格位置
twoArray1[x][y]=twoArray1[x-1][y];
//空格放到空格上边的位置
twoArray1[x-1][y]=count;
//新状态定义
newState = new Node(twoArray1,null,null,null,null,null,0);
//判断移动后的状态是否为要求的最终状态
if (!compare(Begin, newState))
{
//设置当前状态为父状态的第一个子状态
parent.child1 = newState;
//设置当前状态的父状态
newState.DadState = parent;
//层数+1
newState.h = parent.h + 1;
//计算g值,即当前状态与最终状态不一样的值的个数
int count2 = differentNum(newState, End);
//设置当前状态的f值
newState.setF(count2);
//将新状态添加到open链表
open.add(newState);
}
}
//判断空格是否能够下移
if ( x+1 <= 2)
{
count=twoArray2[x][y];
twoArray2[x][y]=twoArray2[x+1][y];
twoArray2[x+1][y]=count;
newState = new Node(twoArray2,null,null,null,null,null,0);
if (!compare(Begin, newState))
{
parent.child2=newState;newState.DadState=parent;
newState.h=parent.h+1;
int count2 =differentNum(newState, End);
newState.setF(count2);
open.add(newState);
}
}
//判断空格能不能左移
if ( y-1 >= 0)
{
count=twoArray3[x][y];
twoArray3[x][y]=twoArray3[x][y-1];
twoArray3[x][y-1]=count;
newState = new Node(twoArray3,null,null,null,null,null,0);
if (!compare(Begin, newState))
{
parent.child3=newState;newState.DadState=parent;
newState.h=parent.h+1;
int count2=differentNum(newState, End);
newState.setF(count2);
open.add(newState);
}
}
//判断空格能不能右移
if ( y+1 <= 2)
{
count=twoArray4[x][y];
twoArray4[x][y]=twoArray4[x][y+1];
twoArray4[x][y+1]=count;
newState = new Node(twoArray4,null,null,null,null,null,0);
if (!compare(Begin, newState))
{
parent.child4=newState;newState.DadState=parent;
newState.h=parent.h+1;
int count2=differentNum(newState, End);
newState.setF(count2);
open.add(newState);
}
}
}
/**
* 比较是否完成运算
* @param begin
* @param newState
* @return
*/
public boolean compare(Node begin,Node newState){
//判断是否完成
if (begin != null && ifFinish(begin,newState))
return true;
if (begin.child1 != null)
compare(begin.child1, newState);
if (begin.child2 != null)
compare(begin.child2, newState);
if (begin.child3 != null)
compare(begin.child3, newState);
if (begin.child4 != null)
compare(begin.child4, newState);
return false;
}
/**
* 计算g值,即当前状态与最终状态不一样的值的个数
* @param first
* @param second
* @return
*/
public int differentNum(Node first,Node second){
int count=0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if(first.currentState[i][j]!=second.currentState[i][j])
count++;
return count;
}
/**
*寻找open链表里面状态F值最小的一个,然后重新排列open,双向冒泡排序
*/
public void openSort(){
//替换节点
Node node;
int left=0,right= open.size()-1;
while(left<right)
{
for(int i=left+1 ; i <= right ; i++){
if(open.get( left).getF() > open.get(i).getF() ){
node = open.get(i);
open.set(i, open.get(left));
open.set(left,node);
}
}
left++;
for(int i=right-1;i >= left ; i--){
if(open.get(right).getF() < open.get(i).getF()){
node= open.get(i);
open.set(i, open.get(right));
open.set(right,node);
}
}
right--;
}
}
/**
* 程序执行开关
*/
public void thisIsStart(){
//判断题目是否有答案
if (judgeIfResult(Begin, End)) {
Node replace = Begin;
//将初始态加入到Open链表
open.add(replace);
//判断是否初始态与结果相同,如果不同
while(!ifFinish(replace, End)){
//删除该状态
open.remove(0);
//对初始状态运行空格移动的方法
addState(replace);
//之后对open链表进行排序
openSort();
//将open链表里f值最小的一个赋值给replace
replace = open.get(0);
}
//将replace添加到close链表中
Node willAddClose = replace;
while (willAddClose != null)
{
close.add(willAddClose);
willAddClose = willAddClose.DadState;
}
//打印输出
printShow();
}
//没有答案
else{
System.out.println("没有结果!");
}
}
}
四、运行结果
1. 初始界面:
2. 输入起始位置:
3. 输入目标位置:
4. 开始运行:
五、实验小结
-
在本次实验中,关于A*算法存在知识盲区,需要进行查询才能做下去,同时对于数据结构有了更深的理解,对于算法有关的问题开始不再恐惧,逐渐有了解决这一方面问题的自信心。
-
发现自己预习的 A* 算法跟当前九宫格隐式图使用的 A* 算法存在不同,但是原理是一样的,我预习的A*算法是存在障碍物的,然后 f = g + h 中,h取自初始位置移动到下一位置的价值,g取自下一位置距离中点位置的绝对价值。在九宫格中,h取自层数,g取自下一位置距离终点位置不同数字的个数。
-
A*算法重点是价值函数,不同的价值函数可能存在不一样的结果。
今日喜欢的句子:
如果有一个人爱上在这亿万颗星星中仅有的一朵花,这人望着星空的时候,就会觉得幸福。