Programming Assignment 4: 8 Puzzle
Write a program to solve the 8-puzzle problem (and its natural generalizations) using the A* search algorithm.
Algorithms 第四周的编程任务,主要目的是编写一个Board
类来代表\(n*n\)的一个格子,一个Solver
类来计算Board
类是否有解。
课程给出的四个数据类型说明(API):
Board and Solver data types. Organize your program by creating an immutable data type Board
with the following API:
public class Board { public Board(int[][] blocks) // construct a board from an n-by-n array of blocks // (where blocks[i][j] = block in row i, column j) public int dimension() // board dimension n public int hamming() // number of blocks out of place public int manhattan() // sum of Manhattan distances between blocks and goal public boolean isGoal() // is this board the goal board? public Board twin() // a board that is obtained by exchanging any pair of blocks public boolean equals(Object y) // does this board equal y? public Iterable<Board> neighbors() // all neighboring boards public String toString() // string representation of this board (in the output format specified below) public static void main(String[] args) // unit tests (not graded) }
Also, create an immutable data type Solver
with the following API:
public class Solver { public Solver(Board initial) // find a solution to the initial board (using the A* algorithm) public boolean isSolvable() // is the initial board solvable? public int moves() // min number of moves to solve initial board; -1 if unsolvable public Iterable<Board> solution() // sequence of boards in a shortest solution; null if unsolvable public static void main(String[] args) // solve a slider puzzle (given below) }
方案
Board
类,参照API以及文档书写即可,不再赘述。
实现代码
import edu.princeton.cs.algs4.Stack;
import java.util.Arrays;
/**
* @author revc
*/
public class Board {
private final int[] blocks1D;
private int len;
/**
* construct a board from an n-by-n array of blocks (where blocks[i][j] = block in row i, column j).
*
* @param blocks n-by-n array of blocks.
*/
public Board(int[][] blocks) {
if (blocks == null) {
throw new IllegalArgumentException();
}
len = blocks.length;
this.blocks1D = new int[len * len];
for (int i = 0; i < len; ++i) {
System.arraycopy(blocks[i], 0, this.blocks1D, i * len, len);
}
}
private Board(int[] blocks) {
len = (int) Math.sqrt(blocks.length);
this.blocks1D = Arrays.copyOf(blocks, blocks.length);
}
/**
* Return board dimension n.
*
* @return board dimension n.
*/
public int dimension() {
return len;
}
/**
* Return number of blocks out of place.
*
* @return number of blocks out of place.
*/
public int hamming() {
int ham = 0;
for (int i = 0; i < blocks1D.length; ++i) {
if (blocks1D[i] != 0 && blocks1D[i] != i + 1) {
ham++;
}
}
return ham;
}
/**
* Return sum of Manhattan distances between blocks and goal
*
* @return sum of Manhattan distances between blocks and goal
*/
public int manhattan() {
int man = 0;
for (int i = 0; i < blocks1D.length; ++i) {
if (blocks1D[i] != 0 && blocks1D[i] != i + 1) {
man += Math.abs((blocks1D[i] - 1) / len - i / len) + Math.abs((blocks1D[i] - 1) % len - i % len);
}
}
return man;
}
/**
* Is this board the goal board?
*
* @return true if this is the goal board, false otherwise.
*/
public boolean isGoal() {
return manhattan() == 0;
}
/**
* Return a board that is obtained by exchanging any pair of blocks
*
* @return a board that is obtained by exchanging any pair of blocks
*/
public Board twin() {
int[] twinBlocks;
if (blocks1D[0] != 0 && blocks1D[1] != 0) {
twinBlocks = swapBlocks(0, 1);
} else {
twinBlocks = swapBlocks(len * len - 2, len * len - 1);
}
return new Board(twinBlocks);
}
/**
* Does this board equal y?
*
* @return true if any one element in y is equal to the corresponding element of {@code this} object,
* false otherwise.
*/
@Override
public boolean equals(Object y) {
if (y == this) {
return true;
}
if (y == null || y.getClass() != this.getClass()) {
return false;
}
Board that = (Board) y;
return this.dimension() == that.dimension() && Arrays.equals(this.blocks1D, that.blocks1D);
}
/**
* Return all neighboring boards that exchanged the blank block and one of the blocks around the blank block.
*
* @return all neighboring boards.
*/
public Iterable<Board> neighbors() {
Stack<Board> neighbors = new Stack<>();
int[] swappedBlocks;
int indexOfBlank = -1;
int indexOfNeighborBlock;
while (blocks1D[++indexOfBlank] != 0) {
}
int[] diffX = {-1, 1, 0, 0};
int[] diffY = {0, 0, -1, 1};
for (int i = 0; i < 4; ++i) {
int rowOfNeighbourBlock = indexOfBlank / len + diffX[i];
int colOfNeighbourBlock = indexOfBlank % len + diffY[i];
if (rowOfNeighbourBlock >= 0 && rowOfNeighbourBlock < len && colOfNeighbourBlock >= 0 && colOfNeighbourBlock < len) {
indexOfNeighborBlock = rowOfNeighbourBlock * len + colOfNeighbourBlock;
swappedBlocks = swapBlocks(indexOfBlank, indexOfNeighborBlock);
neighbors.push(new Board(swappedBlocks));
}
}
return neighbors;
}
/**
* exchange the positions of the two blocks
*/
private int[] swapBlocks(int i, int j) {
int[] blocks = Arrays.copyOf(blocks1D, blocks1D.length);
int swap = blocks[i];
blocks[i] = blocks[j];
blocks[j] = swap;
return blocks;
}
/**
* Return string representation of this board.
*
* @return string representation of this board.
*/
@Override
public String toString() {
StringBuilder board = new StringBuilder();
board.append(len).append("\n");
for (int i = 0; i < blocks1D.length; i++) {
board.append(String.format("%2d ", blocks1D[i]));
if ((i + 1) % len == 0) {
board.append("\n");
}
}
return board.toString();
}
}
Solver
类,我们同时采用两个MinPQ
一个用于处理当前Board
,另一个处理Twin
,取出最小优先级对象的同时,不断将neighbors
对象加入到MinPQ
中,直至两个MinPQ
中的任意一方,到达目标位置。一个小优化:我们可以通过比较当前节点和祖父节点来避免相同节点的加入。
实现代码
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.Stack;
import edu.princeton.cs.algs4.StdOut;
/**
* @author revc
*/
public class Solver {
private boolean solvable;
private int moves;
private SearchNode current;
/**
* Find a solution to the initial board (using the A* algorithm).
*/
public Solver(Board initial) {
if (initial == null) {
throw new IllegalArgumentException();
}
MinPQ<SearchNode> pq = new MinPQ<>();
MinPQ<SearchNode> pqTwin = new MinPQ<>();
SearchNode node;
SearchNode nodeTwin;
pq.insert(new SearchNode(initial, 0, null));
pqTwin.insert(new SearchNode(initial.twin(), 0, null));
while (!(pq.min().board.isGoal() || pqTwin.min().board.isGoal())) {
node = pq.delMin();
nodeTwin = pqTwin.delMin();
for (Board board : node.board.neighbors()) {
if (node.moves == 0 || !board.equals(node.previous.board)) {
pq.insert(new SearchNode(board, node.moves + 1, node));
}
}
for (Board board : nodeTwin.board.neighbors()) {
if (nodeTwin.moves == 0 || !board.equals(nodeTwin.previous.board)) {
pqTwin.insert(new SearchNode(board, nodeTwin.moves + 1, nodeTwin));
}
}
}
solvable = pq.min().board.isGoal();
current = pq.min();
moves = pq.min().moves;
}
/**
* Is the initial board solvable?
*
* @return true if the initial board is solvable, false otherwise.
*/
public boolean isSolvable() {
return solvable;
}
/**
* Min number of moves to solve initial board; -1 if unsolvable
*
* @return min number of moves to solve initial board.
*/
public int moves() {
if (!isSolvable()) {
return -1;
}
return moves;
}
/**
* Return sequence of boards in a shortest solution; null if unsolvable.
*
* @return sequence of boards in a shortest solution; null if unsolvable.
*/
public Iterable<Board> solution() {
if (!isSolvable()) {
return null;
}
Stack<Board> boards = new Stack<>();
SearchNode node = current;
while (node != null) {
boards.push(node.board);
node = node.previous;
}
return boards;
}
private class SearchNode implements Comparable<SearchNode> {
Board board;
SearchNode previous;
int moves;
int priority;
SearchNode(Board board, int moves, SearchNode previous) {
this.board = board;
this.moves = moves;
priority = this.moves + this.board.manhattan();
this.previous = previous;
}
@Override
public int compareTo(SearchNode that) {
if (this.priority == that.priority) {
return this.board.manhattan() - this.board.manhattan();
} else {
return this.priority - that.priority;
}
}
}
/**
* Solve a slider puzzle;
*/
public static void main(String[] args) {
In in = new In(args[0]);
int n = in.readInt();
int[][] blocks = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
blocks[i][j] = in.readInt();
}
}
Board initial = new Board(blocks);
Solver solver = new Solver(initial);
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);
}
}
}
}