CS 61B 18sp HW4复盘

157 阅读2分钟

作业介绍

简单来说就是编写程序解决拼图游戏,类似小时候玩的滑动拼图,还原图片的游戏,而且要用最短的步骤还原,确实有点AI的意思了

先上AutoGrader

AG.JPG

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也是大的离谱。正确做法应该是把前一个SearchNodemovesTillNow加一,还要注意不要加上父节点。

另外,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()