基礎搜尋演算法,華容道小遊戲解法
阿新 • • 發佈:2018-12-11
首先,本人宣告一下,這篇部落格是看了其他人寫的部落格之後,對這個面試題十分感興趣,所以才想分享一下。 在明白這個面試題的時候,先通過一個簡單的迷宮問題,明白一下幾個問題: 什麼是“搜尋”?什麼是“剪枝”?什麼是“回溯”? “搜尋”: 每個點都可以按照右下左上的方向進行嘗試,如果是“牆壁”,就換一個方向,如果可以走,就往前走到下一點,然後再接著嘗試。 “剪枝”: 之前走過的路,就不在往那邊走了,因為回去的話,下一步還要回來。不在搜尋一些明顯不對的地方,剪掉沒用的分支,提高效率。 “回溯”: 如果我們正在往前搜尋,前面沒有路的時候,我們就需要返回來,找到之前有可能出現岔路口的地方,再去下一個方向搜尋 明白以上問題以後,來看這個類似華容道的問題:
如圖所示,空格不能往右走,被牆擋住了,不過可以往上、下、左走,這個狀態,空格就有上、下、左三個方向可以進行搜尋,空格往下走一步,這時候可以往左和往上,往上的話又回到原來狀態了,所以應該“剪枝”,那麼什麼時候應該進行“回溯”呢?就是在當前狀態無論哪個方向都行不通的時候,但是這時候的“回溯”,不是像迷宮問題中走不通的時候才用,二十前面的路是之前走過的,也不應該再往前走了,應該“回溯”,像這個華容道,如果是按照右上方的方向移動,確實可以無線進行。但是轉到第三圈的時候,會發現和剛開始的狀態一直,所以不應該再繼續轉下去了,所以這個時候應該考慮“回溯”。 那麼明白搜尋過程後,應該怎麼寫程式碼實現“回溯”演算法呢?
package huarongdao;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class HuaRongDao {
// 定義方向
private static final int LEFT = 1;
private static final int RIGHT = 2;
private static final int UP = 3;
private static final int DOWN = 4;
// 3x3的九宮格
private int[][] arr;
// 記錄空格的位置
private int x;
private int y;
// 定義移動的陣列
private List<Integer> moveArr = new LinkedList<Integer>();
// 定義終點狀態
private static final Integer WIN_STATE = 123456780;
// 儲存已經搜尋過的狀態
private Set<Integer> statusSet = new HashSet<Integer>();
// 初始化,數字0代表空格,先遍歷,找出空格的位置
public HuaRongDao(int[][] arr) {
this.arr = arr;
for(int i=0; i<arr.length; i++) {
for(int j=0; j<arr.length; j++) {
if(arr[i][j] == 0) {
x = i;
y = j;
}
}
}
}
// 判斷是否可以朝某個方向進行移動
private boolean canMove(int direction) {
switch (direction) {
// y > 0才能左移
case LEFT:
return y > 0;
// y < 2才能右移
case RIGHT:
return y < 2;
// x > 0才能上移
case UP:
return x > 0;
// x < 2才能下移
case DOWN:
return x < 2;
}
return false;
}
// 朝某個方向進行移動,該函式不作判斷,直接移動
// 呼叫前請自行用canMove先行判斷
private void move(int direction) {
int temp;
switch (direction) {
// 空格和左側數字交換
case LEFT:
temp = arr[x][y - 1];
arr[x][y - 1] = 0;
arr[x][y] = temp;
y = y - 1;
break;
// 空格和右側數字交換
case RIGHT:
temp = arr[x][y + 1];
arr[x][y + 1] = 0;
arr[x][y] = temp;
y = y + 1;
break;
// 空格和上方數字交換
case UP:
temp = arr[x - 1][y];
arr[x - 1][y] = 0;
arr[x][y] = temp;
x = x - 1;
break;
// 空格和下方數字交換
case DOWN:
temp = arr[x + 1][y];
arr[x + 1][y] = 0;
arr[x][y] = temp;
x = x + 1;
break;
}
// 該方向記錄
moveArr.add(direction);
}
// 某個方向的回退,該函式不作判斷,直接移動
// 其操作和move方法正好相反
private void moveBack(int direction) {
int temp;
switch (direction) {
// 空格和左側數字交換
case LEFT:
temp = arr[x][y + 1];
arr[x][y + 1] = 0;
arr[x][y] = temp;
y = y + 1;
break;
// 空格和右側數字交換
case RIGHT:
temp = arr[x][y - 1];
arr[x][y - 1] = 0;
arr[x][y] = temp;
y = y - 1;
break;
// 空格和上方數字交換
case UP:
temp = arr[x + 1][y];
arr[x + 1][y] = 0;
arr[x][y] = temp;
x = x + 1;
break;
// 空格和下方數字交換
case DOWN:
temp = arr[x - 1][y];
arr[x - 1][y] = 0;
arr[x][y] = temp;
x = x - 1;
break;
}
// 記錄的移動步驟出棧
moveArr.remove(moveArr.size() - 1);
}
// 獲取狀態,這裡把9個數字按順序組成一個整數來代表狀態
// 方法不唯一,只要能區分九宮格狀態就行
private Integer getStatus() {
int status = 0;
for(int i=0; i<arr.length; i++) {
for(int j=0; j<arr.length; j++) {
status = status * 10 + arr[i][j];
}
}
return status;
}
// 搜尋方法
private boolean search(int direction) {
// 如果能夠朝該方向行走
if(canMove(direction)) {
// 往該方向移動
move(direction);
// 移動後的狀態
Integer status = getStatus();
// 如果已經是勝利狀態,返回true
if(WIN_STATE.equals(status)) {
return true;
}
// 如果是之前走過的狀態了,返回false
if(statusSet.contains(status)) {
// 這一步走錯了,回退
moveBack(direction);
return false;
}
// 將當前狀態存入set
statusSet.add(status);
// 繼續朝四個方向進行搜尋
boolean searchFourOk = search(RIGHT) || search(DOWN) || search(LEFT) || search(UP);
if(searchFourOk) {
return true;
} else {
// 這一步走錯了,把它的記錄去除
moveBack(direction);
return false;
}
}
return false;
}
// 解題入口方法
public boolean solve() {
Integer status = getStatus();
// 如果已經是勝利狀態,返回true
if(WIN_STATE.equals(status)) {
return true;
}
// 初始狀態先記錄
statusSet.add(status);
// 朝4個方向進行搜尋
return search(RIGHT) || search(DOWN) || search(LEFT) || search(UP);
}
// 列印路徑
public void printRoute() {
for(int i=0; i<moveArr.size(); i++) {
System.out.print(getDirString(moveArr.get(i)));
System.out.print(" ");
}
}
// 方向與其對應的字串
private String getDirString(int dir) {
switch (dir) {
case LEFT:
return "左";
case RIGHT:
return "右";
case UP:
return "上";
case DOWN:
return "下";
}
return null;
}
// 列印當前華容道的狀態
public void print() {
for(int i=0; i<arr.length; i++) {
for(int j=0; j<arr.length; j++) {
System.out.print(arr[i][j]);
System.out.print(" ");
}
System.out.println();
}
}
}
1 2 3
4 5 6
8 7 0
無法勝利
1 2 3
4 0 6
7 5 8
可以勝利,路徑為:下 右
3 4 1
5 6 0
8 2 7
可以勝利,路徑為:左 左 上 右 下 左 下 右 右 上 左 左 下 右 上 上 右 下 左 左 上 右 下 右 下
Process finished with exit code 0
之所以喜歡這個面試題,是因為這個面試題用到了棧、佇列、連結串列等結構的思想,對他們的理解也更加深刻了,如果還覺得不錯,點個關注支援下唄。