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++)
{
for (int j = 0; j < n; j++)
{
if (tiles[i][j] != (i * n + j + 1))
{
ham++;
}
}
}
hamming = ham - 1;
}
// string representation of this board
public String toString()
{
StringBuilder result = new StringBuilder();
result.append(dimension()+"\n");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
result.append(String.format("%2d ",tiles[i][j]));
}
result.append("\n");
}
return result.toString();
}
// board dimension n
public int dimension()
{
return n;
}
// number of tiles out of place
public int hamming()
{
return hamming;
}
// sum of Manhattan distances between tiles and goal
public int manhattan()
{
int res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (tiles[i][j] != 0)
{
int newadd = 0;
if (tiles[i][j] % n == 0)
{
newadd = Math.abs(tiles[i][j] / n -1 - i) + Math.abs((tiles[i][j] - 1) % n - j);
}
else
{
newadd = Math.abs(tiles[i][j] / n - i) + Math.abs((tiles[i][j] - 1) % n - j);
}
res += newadd;
}
}
}
return res;
}
// is this board the goal board?
public boolean isGoal()
{
if (hamming == 0)
{
return true;
}
return false;
}
// does this board equal y?
public boolean equals(Object y)
{
if (y == this) return true;
if (y == null) return false;
if (y.getClass() != this.getClass()) return false;
Board that = (Board) y;
if (that.dimension() != this.dimension()) return false;
if (this.toString().compareTo(that.toString()) == 0)
{
return true;
}
else
{
return false;
}
}
// all neighboring boards
public Iterable<Board> neighbors()
{
return new Boards();
}
private class Boards implements Iterable<Board>
{
Board[] neighours = new Board[4];
int index = 0;
Boards()
{
int col = 0,row = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (tiles[i][j] == 0)
{
col = i;
row = j;
}
}
}
if (col != 0)
{
exch(col - 1,row,col,row);
neighours[index++] = new Board(tiles);
exch(col - 1,row,col,row);
}
if ( col != (n-1))
{
exch(col + 1, row, col, row);
neighours[index++] = new Board(tiles);
exch(col + 1, row, col, row);
}
if (row != 0)
{
exch(col, row - 1, col, row);
neighours[index++] = new Board(tiles);
exch(col, row - 1, col, row);
}
if (row != (n-1))
{
exch(col, row + 1, col, row);
neighours[index++] = new Board(tiles);
exch(col, row + 1, col, row);
}
}
@Override
public Iterator<Board> iterator() {
return new arounditerator();
}
private class arounditerator implements Iterator<Board>
{
int cur = 0;
@Override
public boolean hasNext()
{
if (cur < index)
{
return true;
}
return false;
}
@Override
public Board next() {
return neighours[cur++];
}
}
}
// a board that is obtained by exchanging any pair of tiles
public Board twin()
{
int row = 0,col = 0;
if (tiles[row][col] == 0 || tiles[row + 1][col] == 0)
{
col++;
}
exch(row,col,row+1,col);
Board result = new Board(tiles);
exch(row,col,row+1,col);
return result;
}
private void exch(int i,int j,int m,int n)
{
int temp = tiles[i][j];
tiles[i][j] = tiles[m][n];
tiles[m][n] = temp;
}
// unit testing (not graded)
public static void main(String[] args)
{
In in = new In(args[0]);
int n = in.readInt();
int[][] tiles = new int[n][n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
tiles[i][j] = in.readInt();
Board initial = new Board(tiles);
StdOut.println(initial);
StdOut.println(initial.dimension());
StdOut.println("manhattan=" + initial.manhattan());
StdOut.println("hamming=" + initial.hamming());
StdOut.println(initial.isGoal());
}
}
Solver.java:
/******************************************************************************
* NetID: lark
* CreatedTime: 2020/2/18 22:57.
*
* Description: Solver.java
******************************************************************************/
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.MinPQ;
import java.util.Arrays;
public class Solver {
private Board[] result;//用來存放路徑
private boolean issolvable = false;//是否有解
// find a solution to the initial board (using the A* algorithm)
public Solver(Board initial)
{
MinPQ<searchnode> gameTree = new MinPQ<>();//A*算法所需要的最小堆。
if (initial == null) throw new IllegalArgumentException();
gameTree.insert(new searchnode(initial));
gameTree.insert(new searchnode(initial.twin()));
while (!gameTree.isEmpty())
{
searchnode min = gameTree.delMin();
for (Board neighour:min.board.neighbors())
{
if (min.moves >= 1 && neighour.equals(min.prev.board));
else
{
gameTree.insert(new searchnode(neighour,min));
}
}
if (min.board.isGoal())
{
searchnode cur = min;
result = new Board[min.moves + 1];
for (int i = min.moves; i >= 0; i--)
{
result[i] = cur.board;
cur = cur.prev;
}
if (result[0].equals(initial)) issolvable = true;
break;
}
}
}
// is the initial board solvable? (see below)
public boolean isSolvable()
{
boolean result = issolvable;
return result;
}
// min number of moves to solve initial board
public int moves()
{
if (issolvable) return result.length - 1;
else return -1;
}
// sequence of boards in a shortest solution
public Iterable<Board> solution()
{
if (issolvable) return Arrays.asList(result);
else return null;
}
private class searchnode implements Comparable<searchnode>
{
private final Board board;
private final int moves;
private final searchnode prev;
private final int priority;
private final int manhattan;
searchnode(Board node)
{
board = node;
moves = 0;
prev = null;
manhattan = board.manhattan();
priority = manhattan + moves;
}
searchnode(Board node,searchnode pre)
{
board = node;
prev = pre;
moves = pre.moves + 1;
manhattan = board.manhattan();
priority = manhattan + moves;
}
@Override
public int compareTo(searchnode o)
{
int sub = priority - o.priority;
if (sub == 0)
{
return manhattan - o.manhattan;
}
return sub;
}
}
// test client (see below)
public static void main(String[] args) {
// create initial board from file
In in = new In(args[0]);
int n = in.readInt();
int[][] tiles = new int[n][n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
tiles[i][j] = in.readInt();
Board initial = new Board(tiles);
// solve the puzzle
Solver solver = new Solver(initial);
// print solution to standard output
if (!solver.isSolvable())
StdOut.println("No solution possible");
else {
StdOut.println("Minimum number of moves = " + solver.moves());
for (Board board : solver.solution())
StdOut.println(board);
}
solver.moves();
solver.isSolvable();
solver.isSolvable();
if (!solver.isSolvable())
StdOut.println("No solution possible");
else {
StdOut.println("Minimum number of moves = " + solver.moves());
for (Board board : solver.solution())
StdOut.println(board);
}
}
}