題目原文:http://coursera.cs.princeton.edu/algs4/assignments/8puzzle.html
題目要求:設計一個程序解決8 puzzle問題以及該問題的推廣,例如8-puzzle是3*3,程序要能解決n*n的同類問題(2 ≤ n < 128)
典型的8 puzzle如下:
算法設計參照A*搜索算法,即使不了解A*搜索算法,題目也已經將解法解釋的很具體了。
Best-first search:設計參照A* 搜索算法。定義一個 search node類,包含board,從初始狀態到達當前board狀態的移動權重moves,和previous search node。
- 插入初始的search node,其board設為初始board,moves設為0,previous search node設置為0
- 將初始化的search node置於MinPQ類型的優先級隊列中
- 刪除優先級隊列中的min節點,再將該節點的鄰居節點放入優先級隊列中。
- 重復2和3操作直至從優先級隊列中刪除的min節點是目標board
A* 搜索算法的優先級判定依據是f(n) = g(n) + h(n),g(n)是從初始節點到達當前節點的代價,h(n)是當前節點到目標節點的預估代價。
在本題中search node中的moves就是g(n),而關於h(n)題目給出了兩種候選:
Hamming priority function: 處於錯誤位置的block的個數(空白處不算block)
Manhatten priority function: 處於錯誤位置的block距離其各自目標位置的橫向和縱向距離之和
h(n)采用這兩者均可,根據題目中的圖示,顯然發現題目推薦采用manhatten方法。
至此優先級隊列中的優先級判斷依據就是當前search node的moves+manhatten value
A critical optimization: 上述Best-first search中可能會存在剛出隊列的節點又被當成其鄰居節點的鄰居而被放回優先級隊列的情況,這種情況會造成很大的性能損失。為了阻止這種情況的發生,可以在Best-first search的第3步“將該節點的鄰居節點放入優先級隊列”時比較下這個鄰居節點的board是否與本節點的board相同。
例如此時{{8,1,3},{4,0,2},{7,6,5}}就不應該放入優先級隊列中。
A second optimization: 建議在search node的構造函數中計算其manhattan值,也就是在search node的構造函數中確定其優先級。
Detecting unsolvable puzzles:如果一個board是不可解的,那么隨便在該board中選擇一對block互換位置,就能將其變為可解的。為此采用同時對board和其互換了一對block的twindboard進行求解,如果board先實現目標解,那么其就是可解的,相反,如果twinboard先實現目標節,那么該board就不可解。
1 import java.util.ArrayList; 2 /** 3 * @author evasean www.cnblogs.com/evasean/ 4 */ 5 public class Board { 6 private static final int BLANK = 0; 7 private final int n; 8 private int[][] blocks; 9 10 public Board(int[][] inBlocks) { 11 // construct a board from an n-by-n array of blocks 12 // (where blocks[i][j] = block in row i, column j) 13 n = inBlocks.length; 14 blocks = new int[n][n]; 15 copy(blocks, inBlocks); 16 } 17 18 private void copy(int[][] toBlocks, int[][] fromBlocks) { 19 for (int row = 0; row < n; row++) 20 for (int col = 0; col < n; col++) 21 toBlocks[row][col] = fromBlocks[row][col]; 22 } 23 24 public int dimension() { 25 // board dimension n 26 return n; 27 } 28 29 private int getRow(int value) { 30 return (value - 1) / n; 31 } 32 33 private int getCol(int value) { 34 return (value - 1) % n; 35 } 36 37 private int getValue(int row, int col) { 38 return row * n + col + 1; 39 } 40 41 public int hamming() { 42 // number of blocks out of place 43 int hamming = 0; 44 for (int row = 0; row < n; row++) 45 for (int col = 0; col < n; col++) 46 if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col)) 47 hamming++; 48 return hamming; 49 } 50 51 public int manhattan() { 52 // sum of Manhattan distances between blocks and goal 53 int manhattan = 0; 54 for (int row = 0; row < n; row++) 55 for (int col = 0; col < n; col++) 56 if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col)) 57 manhattan += Math.abs(getRow(blocks[row][col]) - row) + Math.abs(getCol(blocks[row][col]) - col); 58 return manhattan; 59 } 60 61 public boolean isGoal() { 62 // is this board the goal board? 63 for (int row = 0; row < n; row++) 64 for (int col = 0; col < n; col++) 65 if (blocks[row][col] != BLANK && blocks[row][col] != getValue(row, col)) 66 return false; 67 return true; 68 } 69 70 public Board twin() { 71 // a board that is obtained by exchanging any pair of blocks 72 Board twinBoard = new Board(blocks); 73 int firRow = 0; 74 int firCol = 0; 75 if (blocks[firRow][firCol] == BLANK) 76 firCol++; 77 for (int row = 0; row < n; row++) { 78 for (int col = 0; col < n; col++) { 79 if (blocks[row][col] != blocks[firRow][firCol] && blocks[row][col] != BLANK) { 80 twinBoard.swap(firRow, firCol, row, col); 81 return twinBoard; 82 } 83 } 84 } 85 return twinBoard; 86 } 87 88 private void swap(int vRow, int vCol, int wRow, int wCol) { 89 int t = blocks[vRow][vCol]; 90 blocks[vRow][vCol] = blocks[wRow][wCol]; 91 blocks[wRow][wCol] = t; 92 } 93 94 public boolean equals(Object y) { 95 // does this board equal y? 96 if (y == null) 97 return false; 98 if (y == this) 99 return true; 100 if (y.getClass().isInstance(this)) { 101 Board yb = (Board) y; 102 if (yb.n != this.n) 103 return false; 104 else { 105 for (int row = 0; row < n; row++) 106 for (int col = 0; col < n; col++) 107 if (yb.blocks[row][col] != blocks[row][col]) 108 return false; 109 return true; 110 } 111 } else 112 return false; 113 } 114 115 public Iterable<Board> neighbors() { 116 // all neighboring boards 117 ArrayList<Board> neighbors = new ArrayList<Board>(); 118 for (int row = 0; row < n; row++) { 119 for (int col = 0; col < n; col++) { 120 if (blocks[row][col] == BLANK) { 121 // 空白的位置分別與上下左右的元素交換一次位置就得到一個鄰居board 122 // 與上方元素互換 123 if (row > 0) { 124 Board neighborT = new Board(blocks); 125 neighborT.swap(row, col, row - 1, col); 126 neighbors.add(neighborT); 127 } 128 // 與下方元素互換 129 if (row < n - 1) { 130 Board neighborB = new Board(blocks); 131 neighborB.swap(row, col, row + 1, col); 132 neighbors.add(neighborB); 133 } 134 // 與左邊元素互換 135 if (col > 0) { 136 Board neighborL = new Board(blocks); 137 neighborL.swap(row, col, row, col - 1); 138 neighbors.add(neighborL); 139 } 140 // 與右邊元素互換 141 if (col < n - 1) { 142 Board neighborR = new Board(blocks); 143 neighborR.swap(row, col, row, col + 1); 144 neighbors.add(neighborR); 145 } 146 } 147 } 148 } 149 return neighbors; 150 } 151 152 public String toString() { 153 // string representation of this board (in the output format specified 154 // below) 155 StringBuilder sb = new StringBuilder(); 156 sb.append(n + "\n"); 157 for (int row = 0; row < n; row++) { 158 for (int col = 0; col < n; col++) { 159 //本來考慮到n<128時元素可能會很大,設置的是%6d,但是提交時不滿足校驗規則 160 //校驗規則要求必須是%2d,很奇怪的校驗 161 sb.append(String.format("%2d ", blocks[row][col])); 162 } 163 sb.append("\n"); 164 } 165 return sb.toString(); 166 } 167 168 public static void main(String[] args) { 169 // unit tests (not graded) 170 // int[][] test = { { 0, 1}, {2,3 }}; 171 // Board b = new Board(test); 172 // System.out.println(b); 173 // System.out.println(b.hamming()); 174 // System.out.println(b.manhattan()); 175 } 176 }
1 import edu.princeton.cs.algs4.In; 2 import edu.princeton.cs.algs4.MinPQ; 3 import edu.princeton.cs.algs4.Stack; 4 import edu.princeton.cs.algs4.StdOut; 5 /** 6 * @author evasean www.cnblogs.com/evasean/ 7 */ 8 public class Solver { 9 10 private SearchNode currentNode; 11 private SearchNode twincurrentNode; 12 private Stack<Board> solution; 13 14 private class SearchNode implements Comparable<SearchNode>{ 15 public Board board; 16 public int moves; 17 public SearchNode preSearchNode; 18 19 public final int priority; 20 21 public SearchNode(Board inboard, SearchNode inPreSearchNode){ 22 board = inboard; 23 preSearchNode = inPreSearchNode; 24 if(inPreSearchNode == null) moves = 0; 25 else moves = inPreSearchNode.moves + 1; 26 priority = moves + board.manhattan(); 27 } 28 29 @Override 30 public int compareTo(SearchNode o) { 31 return Integer.compare(this.priority, o.priority); 32 } 33 } 34 35 36 public Solver(Board initial) { 37 // find a solution to the initial board (using the A* algorithm) 38 if(initial == null) 39 throw new IllegalArgumentException("Constructor argument Board is null!"); 40 currentNode = new SearchNode(initial,null); 41 twincurrentNode = new SearchNode(initial.twin(),null); 42 MinPQ<SearchNode> priorityQueue = new MinPQ<SearchNode>(); 43 MinPQ<SearchNode> twinPriorityQueue = new MinPQ<SearchNode>(); 44 priorityQueue.insert(currentNode); 45 twinPriorityQueue.insert(twincurrentNode); 46 while(true){ 47 currentNode = priorityQueue.delMin(); 48 if(currentNode.board.isGoal()) break; 49 putNeighBorsIntoPQ(currentNode,priorityQueue); 50 51 twincurrentNode = twinPriorityQueue.delMin(); 52 if(twincurrentNode.board.isGoal()) break; 53 putNeighBorsIntoPQ(twincurrentNode,twinPriorityQueue); 54 } 55 } 56 57 private void putNeighBorsIntoPQ(SearchNode searchNode, MinPQ<SearchNode> pq){ 58 Iterable<Board> neighbors = searchNode.board.neighbors(); 59 for(Board neighbor : neighbors){ 60 //只有在當前搜索節點的鄰居們的borad不與當前節點的preSearchNode的borad相同 61 //才將該鄰居放入優先隊列 62 63 if(searchNode.preSearchNode==null || !neighbor.equals(searchNode.preSearchNode.board)) 64 pq.insert(new SearchNode(neighbor,searchNode)); 65 } 66 } 67 68 public boolean isSolvable() { 69 // is the initial board solvable? 70 return currentNode.board.isGoal(); 71 } 72 73 public int moves() { 74 // min number of moves to solve initial board; -1 if unsolvable 75 if(currentNode.board.isGoal()) 76 return currentNode.moves; 77 else 78 return -1; 79 } 80 81 public Iterable<Board> solution() { 82 // sequence of boards in a shortest solution; null if unsolvable 83 if(currentNode.board.isGoal()){ 84 solution = new Stack<Board>(); 85 SearchNode node = currentNode; 86 while(node != null){ 87 solution.push(node.board); 88 node = node.preSearchNode; 89 } 90 return solution; 91 }else 92 return null; 93 } 94 95 public static void main(String[] args) { 96 // solve a slider puzzle (given below) 97 // create initial board from file 98 // In in = new In(args[0]); 99 In in = new In("8puzzle/puzzle3x3-unsolvable.txt"); //本地測試之用 100 int n = in.readInt(); 101 int[][] blocks = new int[n][n]; 102 for (int i = 0; i < n; i++) 103 for (int j = 0; j < n; j++) 104 blocks[i][j] = in.readInt(); 105 Board initial = new Board(blocks); 106 107 // solve the puzzle 108 Solver solver = new Solver(initial); 109 110 // print solution to standard output 111 if (!solver.isSolvable()) 112 StdOut.println("No solution possible"); 113 else { 114 StdOut.println("Minimum number of moves = " + solver.moves()); 115 for (Board board : solver.solution()) 116 StdOut.println(board); 117 } 118 } 119 }