文章目录
一、问题的概述、分析
1.问题的概述
1)实验任务:
- 对九宫重排问题,建立图的启发式搜索求解方法;
- 用A*算法求解九宫重排问题。
2)实验要求:
- 3x3九宫棋盘,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。
- 根据给定初始布局和目标布局,移动棋子从初始布局到达目标布局,求解移动步骤并输出。
- 请设计算法,使用合适的搜索策略,在较少的空间和时间代价下找到最短路径。
2.问题的分析
1)A*算法
F=G+H
G:表示从起点A移动到指定方格的移动消耗;
H:表示从指定的方格移动到目标E点的预计消耗,我们假设H的计算方法,忽略障碍物,只可以纵横向计算。
2)九宫格的重新排布
每次交换后都要改变棋盘的布局,使九宫格从初始状态变换为最终状态,空格可与其周围上下左右的数字交换。
二、开发工具及编程语言
1.开发工具
IntelliJ IDEA
2.编程语言
Java语言
三、算法分析(A*算法)
A*算法
求解一点到另一点的最优路径。将地图虚拟化,并将其划分为一个一个的小方块,然后不断地寻找周围的点,选出一个新的点作为起点后再循环找点,直至找到终点。
路径增量
- F=G+H
- G:表示从起点A移动到指定方格的移动消耗,我们假设横向移动一个格子消耗10,斜向移动一个格子消耗14;
- H:表示从指定的方格移动到目标E点的预计消耗。
流程描述
- 从起点s开始,把s作为一个等待检查的方格,放入到“开启列表”中;
- 寻找起点s周围可以到达的方格(最多八个),将它们放入到“开启列表”,并设置它们的父方格为s;
- 从“开启列表”中删除起点s,并将s放入到“关闭列表”中;
- 计算每个周围方格的F值,F=G+H。
好物分享
三、算法分析
Node节点
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;
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 int getF(){
return f;
}
public void setF(int g) {
f=g+h;
}
}
功能模块类
mport java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class ImplicitService {
Node Begin;
Node End;
//open链表存储空格移动后的新状态
List<Node> open = new ArrayList<>();
//close链表存储已经确定了的状态
List<Node> close = new ArrayList<>();
Scanner scanner=new Scanner(System.in);
/**
* 构造函数
*/
public ImplicitService(){
int [][]beginState=new int[3][3];
int [][]purposeState=new int[3][3];
//起始位置
System.out.println("起始位置:");
for(int i=0;i<3;i++) {
for (int j = 0; j < 3; j++) {
System.out.println("请输入你的数字:");
beginState[i][j] = scanner.nextInt();
}
}
//存入初始状态
Begin = new Node(beginState,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 =new Node(purposeState,null,null,null,null,null,0);
}
/**
* 程序执行开关
*/
public void start () {
//判断题目是否有答案
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;
}
//打印输出
print();
} else {
System.out.println("没有结果!");
}
}
/**
* 判断题目是否有答案
* @param begin
* @param end
* @return
*/
public boolean judgeIfResult(Node begin, Node end){
//用一维数组开始记录展开
int []oneArray1 = new int[9];
int []oneArray2 = new int[9];
int inverseNumber1 = 0, inverseNumber2 = 0 ;
int count1 = 0, count2 = 0;
//二维数组展开
int i,j;
for(i=0;i<3;i++) {
for(j=0;j<3;j++){
oneArray1[count1++] = begin.currentState[i][j];
oneArray2[count2++] = end.currentState[i][j];
}
}
//找第一个状态的逆序数
for(i=0;i<9;i++){
for(j=0;j<i;j++) {
if (oneArray1[j] > oneArray1[i] && oneArray1[j] != 0 && oneArray1[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 print(){
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--;
}
}
}
Main函数
public class Main {
public static void main(String[] args) {
ImplicitService implicitService=new ImplicitService();
implicitService.start();
}
}
四、总结
- A*算法算和九宫格的重新排布都算是全新的知识点,导致最初在编写时无从下手,也毫无头绪,但所幸在大量的资料阅读下,最终勉强吧功能实现了,但是可以看出优化的点非常多;
- 在查阅资料的途中,由于资料的杂乱性花费了很多无用功,还是在同学和学长的推荐下看了一些比较好的资料,但在看过资料、对知识点不完全理解的情况下,思维很容易遇到瓶颈,所以平时还是要多阅读一些跟算法有关的材料。