8-puzzle问题是cousera上algorithm的第四周作业。
8-puzzle问题如上所示,用A*算法求从一个乱序的数字拼图(initial board)到顺序(goal board)数字拼图的最短路径,每次移动只能通过将空白块与数字块交换来实现。
感觉cousera上作业的specification已经将A*算法讲的很清楚了,主要是有些需要注意的地方,如果不知道的话很难得到满分。
这题的A*算法的大概思路为:
1.将initial放入一个优先级队列中(优先级是队列中元素与goal的曼哈顿距离以及initial移动到队列中元素的距离);
2.若队列不为空,从优先级队列中取出优先级最高的board;
3.判断2中取出的board,是否是goal board,是的话则得到了最终的goal board,可以找出最短路径,得到结果;否则转4;
4.找出这个board的neighbour(即移动一次得到的所有board),放入优先级队列中),转2;
但这样子就有个问题,3中从优先级队列中取出了优先级最高的元素之后,如何反推得到移动路线和移动次数。单纯用Board类无法做到,就得新建一个类searchnode,类中存放着board和board的移动次数以及board的前一个board(这样的话通过这个就可以反推最短路径),因此这个优先级队列应该是searchnode的优先级队列,而不是Board的。
又因为不是每一个initial board都是可以移动得到goal board(有解)的,如何检测呢,作业说明中介绍了这样一个结论:如果一个initial board有解,那么它的twin board(交换任意两个相邻非空白元素得到的board)则无解;如果initial无解,twin board就有解;
因此只要改进以下A*算法的实现思路,将initial和一个twin都放入同一个优先级队列,最后一定可以得到一个goal,检查goal的源头到底是initial还是twin即可判断initial是否有解。
作业需要实现两个类。
Borad类的实现没啥大问题,需要注意的是equals的实现完整性,判断是否为空,为自己,类是否相同以及toString的实现要按照课程的格式。
Solver类的实现需要注意的有:
1,将一些需要多次调用的函数,提前用实例变量存储下来,比如求的曼哈顿距离,优先级;
2.提前判断,避免重复计算的情况,这里主要是指一个board的prev和neighbour相同的情况(剔除走回头路的情况)。
满分的代码如下:
Board.java:
/******************************************************************************
* NetID: lark
* CreatedTime: 2020/2/18 17:09.
*
* Description: Board.java
******************************************************************************/
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import java.util.Iterator;
public class Board {
private int[][] tiles;
private int n;
private final int hamming;
public Board(int[][] tiles)
{
n = tiles.length;
this.tiles = new int[n][n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
this.tiles[i][j] = tiles[i][j];
}
}
int ham = 0;
for (int i = 0; i < n; i