作业介绍
简单来说就是编写程序解决拼图游戏,类似小时候玩的滑动拼图,还原图片的游戏,而且要用最短的步骤还原,确实有点AI的意思了
先上AutoGrader
skeleton里提供了一个WorldState接口
public interface WorldState {
/** Provides an estimate of the number of moves to reach the goal.
* Must be less than or equal to the correct distance. */
int estimatedDistanceToGoal();
/** Provides an iterable of all the neighbors of this WorldState. */
Iterable<WorldState> neighbors();
/** Estimates the distance to the goal. Must be less than or equal
* to the actual (and unknown) distance. */
default public boolean isGoal() {
return estimatedDistanceToGoal() == 0;
}
}
整个作业最重要的想法就是所谓的Best-First Search。
最普通、最自然的想法就是遍历WorldState的所有neighbors来寻找最终的答案,Best-First Search优先遍历更接近最终答案的neighbor,从而能缩短搜索的时间。
因此,需要定义一个变量表征接近正确答案的程度,定义了priorty为到达当前WorldState移动的次数+estimatedDistanceToGoal()。
Solver
在写Solver的时候,SearchNode中定义了一个int型变量movesTillNow用来记录从开始到搜索到当前WorldState所经历的搜索次数。一开始,犯了一个比较严重的错误,就是在把一个节点插入PQ之后movesTillNow就加一,最后在测试的时候会把所有遍历过的WorldState都输出出来,并且move也是大的离谱。正确做法应该是把前一个SearchNode的movesTillNow加一,还要注意不要加上父节点。
另外,solution()方法返回的Iterable应该沿着SearchNode的引用一层层向上找,因此,应该使用栈或者可以从头插入的数据结构。
package hw4.puzzle;
import edu.princeton.cs.algs4.MinPQ;
import java.util.LinkedList;
public class Solver {
private SearchNode end;
private class SearchNode implements Comparable<SearchNode> {
private WorldState ws;
private int movesTillNow;
private SearchNode prev;
private SearchNode(WorldState initial) {
ws = initial;
movesTillNow = 0;
prev = null;
}
private SearchNode(WorldState ws, int movesTillNow, SearchNode prev) {
this.ws = ws;
this.movesTillNow = movesTillNow;
this.prev = prev;
}
@Override
public int compareTo(SearchNode o) {
int curNum = this.movesTillNow + this.ws.estimatedDistanceToGoal();
int oNum = o.movesTillNow + o.ws.estimatedDistanceToGoal();
return curNum - oNum;
}
}
public Solver(WorldState initial) {
MinPQ<SearchNode> pq = new MinPQ<>();
pq.insert(new SearchNode(initial));
SearchNode min = pq.delMin();
WorldState curr = min.ws;
while (!curr.isGoal()) {
for (WorldState next : curr.neighbors()) {
if (min.prev == null || !next.equals(min.prev.ws)) {
pq.insert(new SearchNode(next, min.movesTillNow + 1, min));
}
}
min = pq.delMin();
curr = min.ws;
}
end = min;
}
public int moves() {
return end.movesTillNow;
}
public Iterable<WorldState> solution() {
LinkedList<WorldState> list = new LinkedList<>();
SearchNode ptr = end;
while (ptr != null) {
list.addFirst(ptr.ws);
ptr = ptr.prev;
}
return list;
}
}
作业中提到反复计算estimatedDistanceToGoal()的问题我这里没有再进行进一步的优化,应该是可以通过定义一个map暂时缓存遍历过的WorldState。
Board
到这一部分才是要解决拼图问题,感觉这一部分好像比前一部分的Solver简单一些,这一部分一开始犯的错误是把0也当成了一个实实在在的数进行计算,导致进行测试的时候总是超时错误。一开始还以为是我的电脑太差,后来才发现了算法的错误。
package hw4.puzzle;
import edu.princeton.cs.algs4.Queue;
public class Board implements WorldState {
private final int BLANK = 0;
private int size;
private final int[][] board;
/*
Constructs a board from an N-by-N array of tiles where tiles[i][j] = tile at row i, column j
*/
public Board(int[][] tiles) {
size = tiles.length;
board = new int[size][size];
for (int i = 0; i < this.size(); i++) {
for (int j = 0; j < this.size(); j++) {
board[i][j] = tiles[i][j];
}
}
}
private int xyTo1D(int x, int y) {
return x * size + y;
}
/*
Returns value of tile at row i, column j (or 0 if blank)
*/
public int tileAt(int i, int j) {
if (i < 0 || i > size() - 1 || j < 0 || j > size() - 1) {
throw new IndexOutOfBoundsException();
}
return board[i][j];
}
/*
Returns the board size N
*/
public int size() {
return size;
}
/*
Hamming estimate described below
Return the number of tiles int the wrong position.
*/
public int hamming() {
int count = 0;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < size(); j++) {
if (board[i][j] != BLANK && board[i][j] != xyTo1D(i, j) + 1) {
count++;
}
}
}
return count;
}
/*
Manhattan estimate described below
Return the sum of the Manhattan distances (sum of the vertical and horizontal distance)
from the tiles to their goal positions
*/
public int manhattan() {
int count = 0;
for (int i = 0; i < size(); i++) {
for (int j = 0; j < size(); j++) {
if (board[i][j] != BLANK) {
int num = board[i][j];
count += Math.abs(rightPosition(num).x - i)
+ Math.abs((rightPosition(num).y - j));
}
}
}
return count;
}
private class Position {
private int x;
private int y;
}
private Position rightPosition(int num) {
Position pos = new Position();
pos.x = (num - 1) / size();
pos.y = (num - 1) % size();
return pos;
}
/*
Returns true if this board's tile values are the same position as y's
*/
public boolean equals(Object y) {
if (!(y instanceof Board)) {
return false;
}
Board that = (Board) y;
if (that.size() != this.size()) {
return false;
}
for (int i = 0; i < this.size(); i++) {
for (int j = 0; j < this.size(); j++) {
if (this.board[i][j] != that.board[i][j]) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return super.hashCode();
}
/*
Estimated distance to goal. This method should
simply return the results of manhattan() when submitted to Gradescope.
*/
@Override
public int estimatedDistanceToGoal() {
return manhattan();
}
/**
* Returns the neighbors of the current board.
*
* @author http://joshh.ug/neighbors.html
*/
@Override
public Iterable<WorldState> neighbors() {
Queue<WorldState> neighbors = new Queue<>();
int hug = size();
int bug = -1;
int zug = -1;
for (int rug = 0; rug < hug; rug++) {
for (int tug = 0; tug < hug; tug++) {
if (tileAt(rug, tug) == BLANK) {
bug = rug;
zug = tug;
}
}
}
int[][] ili1li1 = new int[hug][hug];
for (int pug = 0; pug < hug; pug++) {
for (int yug = 0; yug < hug; yug++) {
ili1li1[pug][yug] = tileAt(pug, yug);
}
}
for (int l11il = 0; l11il < hug; l11il++) {
for (int lil1il1 = 0; lil1il1 < hug; lil1il1++) {
if (Math.abs(-bug + l11il) + Math.abs(lil1il1 - zug) - 1 == 0) {
ili1li1[bug][zug] = ili1li1[l11il][lil1il1];
ili1li1[l11il][lil1il1] = BLANK;
Board neighbor = new Board(ili1li1);
neighbors.enqueue(neighbor);
ili1li1[l11il][lil1il1] = ili1li1[bug][zug];
ili1li1[bug][zug] = BLANK;
}
}
}
return neighbors;
}
/** Returns the string representation of the board.
* Uncomment this method. */
public String toString() {
StringBuilder s = new StringBuilder();
int N = size();
s.append(N + "\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
s.append(String.format("%2d ", tileAt(i, j)));
}
s.append("\n");
}
s.append("\n");
return s.toString();
}
}
最后还有一个对我来说比较容易出错的地方,重写equals()之后一定要重写hashCode()。