1. 程式人生 > >Java與演算法之(12)

Java與演算法之(12)

貪吃的小老鼠又回來了,這次有什麼新的辦法吃到乳酪呢?

規則不變,只能上下左右在格子內移動。


因為上次的深度優先演算法讓老鼠走了不少冤枉路,這次老鼠帶來了幫手探路鼠。探路鼠的使用規則如下:

小老鼠按右、下、左、上的順序向身邊四個格子嘗試放出探路鼠,如果遇到貓、出邊界、已經有探路鼠存在的格子則放棄。

每隻探路鼠都有唯一的順序號,第一隻從1開始,每放成功一隻序號遞增1。

老鼠探路完成後,找出當前未行動過的順序號最小的探路鼠重複老鼠的工作,即嘗試向右、下、左、上四個格子放出探路鼠。

用圖來解釋一下,第一步,小老鼠放出兩隻探路鼠,如下:


老鼠行動完成,按規則是1號探路鼠行動。由於地形所限,1號嘗試了右、下、左、上四個方向後,只成功放出了3號。


1號完成後,輪到2號行動,也只成功放出一隻,即4號


據此規則不難推算出,接下來依次是:

3號放出5號

4號放出6號

5號放出7號

6號放出8號

7號放出9、10號

8號放出11號

9號放出12號

如下圖:


注意12號探路鼠首先發現了乳酪,這時它向上一級即9號彙報,9號向7號彙報。。。,12->9->7->5->3->1->老鼠,可以計算出最少的步數是6。

上面的探路過程即廣度優先搜尋(Breadth First Search, BFS),與深度優先搜尋的一條路走到黑不同,每到一個新的位置都向四個方向分別探索,找出每一個分支,並對每一個分支繼續探索。

用程式來描繪這一過程,首先需要把迷宮“數字化“,如下圖:


這樣就可以用一個二維陣列儲存迷宮:

int width = 5;  //迷宮寬度
int height = 4;  //迷宮高度
	    
int[][] maze = new int[width][height];
	    
maze[2][0] = 1;
maze[1][2] = 1;
maze[2][2] = 1;
maze[4][1] = 1;
用一個同樣大小的二維陣列標記已經放了探路鼠的點
int[][] mark = new int[width][height];
mark[0][0] = 1;
每個“探路鼠”需要知道自己所在位置(座標),自己的上一級是誰。為了方便,還用它記錄了到達該位置需要的步數。用一個類來表示:
    static class Trace {

        public Trace(int x, int y, int father, int step) {
            this.x = x;
            this.y = y;
            this.father = father;
            this.step = step;
        }

        private int x;
        private int y;
        private int father;
        private int step;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getFather() {
            return father;
        }

        public void setFather(int father) {
            this.father = father;
        }

        public int getStep() {
            return step;
        }

        public void setStep(int step) {
            this.step = step;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
        }
    }
完整程式碼如下:
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.util.ArrayList;
import java.util.List;

/**
 * 老鼠走迷宮 BFS演算法
 * Created by autfish on 2016/9/5.
 */
public class BfsRatMaze {

    int min = Integer.MAX_VALUE;
    int endX = 4;  //目標點橫座標
    int endY = 2;  //目標點縱座標
    int width = 5;  //迷宮寬度
    int height = 4;  //迷宮高度
    int[][] maze;
    int[][] mark;

    static class Trace {

        public Trace(int x, int y, int father, int step) {
            this.x = x;
            this.y = y;
            this.father = father;
            this.step = step;
        }

        private int x;
        private int y;
        private int father;
        private int step;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getFather() {
            return father;
        }

        public void setFather(int father) {
            this.father = father;
        }

        public int getStep() {
            return step;
        }

        public void setStep(int step) {
            this.step = step;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
        }
    }

    public void bfs() {
        int[][] next = new int[][] { //按右->下->左->上的順序嘗試
                {1, 0},
                {0, 1},
                {-1, 0},
                {0, -1}
        };
        int head = 0, tail = 1;
        int startX = 0, startY = 0;
        int nextX, nextY;
        List<Trace> traces = new ArrayList<>();
        traces.add(head, new Trace(startX, startY, -1, 0));
        mark[startX][startY] = 1;
        int flag = 0;
        while(head < tail) {
            for(int k = 0; k <= 3; k++) {
                nextX = traces.get(head).getX() + next[k][0];
                nextY = traces.get(head).getY() + next[k][1];
                if(nextX < 0 || nextX >= width || nextY < 0 || nextY >= height) {  //超出邊界
                    continue;
                }
                //沒有障礙且沒有探索過, 則把當前位置標記為未探索點
                if(maze[nextX][nextY] == 0 && mark[nextX][nextY] == 0) {
                    this.mark[nextX][nextY] = 1;
                    traces.add(tail, new Trace(nextX, nextY, head, traces.get(head).getStep() + 1));
                    tail++;
                }
                if(nextX == endX && nextY == endY) {
                    flag = 1;
                    break;
                }
            }
            if(flag == 1)
                break;
            //一個點的四個方向探索完成, 取編號最小的一個未探索點
            head++;
        }
        Trace end = traces.get(tail - 1);
        int father = end.getFather();
        System.out.println("共" + end.getStep() + "步");

        StringBuilder path = new StringBuilder();
        path.insert(0, "->[" + end.getX() + "," + end.getY() + "]");
        while(father >= 0) {
            Trace prev = traces.get(father);
            father = prev.getFather();
            if(father > -1)
                path.insert(0, "->[" + prev.getX() + "," + prev.getY() + "]");
            else
                path.insert(0, "[" + prev.getX() + "," + prev.getY() + "]");
        }
        System.out.println(path.toString());
    }

    public void initMaze() {
        this.maze = new int[width][height];
        this.mark = new int[width][height];

        this.maze[2][0] = 1;
        this.maze[1][2] = 1;
        this.maze[2][2] = 1;
        this.maze[4][1] = 1;
        this.mark[0][0] = 1;

        //列印迷宮 _表示可通行 *表示障礙 !表示目標
        for(int y = 0; y < height; y++) {
            for(int x = 0; x < width; x++) {
                if(x == endX && y == endY) {
                    System.out.print("!  ");
                }  else if(this.maze[x][y] == 1) {
                    System.out.print("*  ");
                } else {
                    System.out.print("_  ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        BfsRatMaze b = new BfsRatMaze();
        b.initMaze();
        b.bfs();
    }
}
執行結果:
_  _  *  _  _  
_  _  _  _  *  
_  *  *  _  !  
_  _  _  _  _  

共6步
[0,0]->[1,0]->[1,1]->[2,1]->[3,1]->[3,2]->[4,2]

用深度優先搜尋的程式見: