本人郵箱: <kco1989@qq.com>
歡迎轉載,轉載請註明網址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代碼已經所有託管github有須要的同窗自行下載java
迷宮對於你們都不會陌生.那麼迷宮是怎麼生成,已經迷宮要如何找到正確的路徑呢?用java代碼又怎麼實現?帶着這些問題.咱們繼續往下看.git
有一種算法就作並查集(find-union).什麼意思呢?好比如今有零散的甲乙丙丁戊
五我的.他們之間剛開始互相不認識.用代碼解釋就是find(person1, person2) == false
,以後在某一次聚合中,甲
認識了乙
,乙
認識了丙
,丙
認識了戊
和丁
等等,那麼就能夠用代碼解釋以下.github
union("甲", "乙"); union("乙", "丙"); union("丙", "戊"); union("丙", "丁");
那麼這個時候甲
就能夠經過乙
認識到了丙
在經過丙
認識到庚
和丁
.
這是甲乙丙丁戊
經過朋友或者朋友的朋友最終都互相認識.換另外一種說法就是若是甲
要認識丁
,那麼甲
必須先經過乙
認識丙
,再經過丙
去認識丁
就好了.算法
對於迷宮生成,其實更上面朋友圈有點相似.生成步驟以下數組
首先,先建立一個n*m的二維密室.每一個單元格四方都是牆.微信
隨機選擇密室中的一個單元格.以後在隨機選擇一面要打通的牆壁.網絡
判斷要打通的牆壁是否爲邊界.是則返回步驟3,不是則繼續dom
判斷步驟的單元個和要打通的牆壁的對面是否聯通(用find算法)maven
若是兩個單元格不聯通,則把步驟2選中的牆壁打通(用union算法).不然返回步驟2.ide
判斷迷宮起點和終點是否已經聯通,是則迷宮生成結束,不然返回步驟2.
對於
並查集(find-union)
的實現,網絡上有很多實現,這裏不展開說明了.
public enum Wall { /** * 牆壁 */ BLOCK, /** * 通道 */ ACCESS }
牆壁,是一個枚舉變量,用於判斷當前的牆壁是否能夠通行.
public class Box { private Wall[] walls; public Box(){ walls = new Wall[4]; for (int i = 0; i < walls.length; i ++){ walls[i] = Wall.BLOCK; } } public void set(Position position, Wall wall){ walls[position.getIndex()] = wall; } public Wall get(Position position){ return walls[position.getIndex()]; } }
一個單元格(小房間)是有四面牆組成的,剛開始四面牆都是牆壁.
public enum Position { TOP(0), RIGHT(1), DOWN(2), LEFT(3); public int index; Position(int index) { this.index = index; } public static Position indexOf(int index){ int pos = index % Position.values().length; switch (pos){ case 0: return TOP; case 1: return RIGHT; case 2: return DOWN; case 3: default: return LEFT; } } public Position anotherSide(){ switch (this){ case TOP: return DOWN; case RIGHT: return LEFT; case DOWN: return TOP; case LEFT: default: return RIGHT; } } public int getIndex() { return index; } }
方向,枚舉類,用於判斷單元格的那一面牆壁須要打通.
這裏我使用find-union
的兩種實現方式實現,
一種是用數組的方式MazeArrayBuilder
一種使用map的方式實現MazeMapBuilder
因此我把迷宮生成的一些共同方法和屬性抽取出現,編寫了一個抽象類AbstractMazeBuilder
.而後再在MazeArrayBuilder
或MazeMapBuilder
作具體的實現.
如今咱們來看看AbstractMazeBuilder
這個類
public abstract class AbstractMazeBuilder { /** * 迷宮行列最大值 */ public static final int MAX_ROW_LINE = 200; /** * 行 */ protected int row; /** * 列 */ protected int line; /** * 迷宮格子集合,每一個格子有四面牆 */ protected Box[][] boxes; /** * 求解迷宮中間變量 */ protected int[][] solverPath; /** * 迷宮時候已經算出最佳路徑 */ protected boolean isSolver; /** * 使用貪婪算法,算出最佳路徑集合 */ protected List<MazePoint> bestPath; protected Random random; public AbstractMazeBuilder(int row, int line){ if (row < 3 || row > MAX_ROW_LINE || line < 3 || line > MAX_ROW_LINE){ throw new IllegalArgumentException("row/line 必須大於3,小於" + MAX_ROW_LINE); } this.row = row; this.line = line; isSolver = false; boxes = new Box[row][line]; solverPath = new int[row][line]; bestPath = new ArrayList<MazePoint>(); random = new Random(); for (int i = 0; i < row; i ++){ for (int j = 0; j < line; j ++){ boxes[i][j] = new Box(); solverPath[i][j] = Integer.MAX_VALUE; } } } /** * 查詢與point點聯通的最大格子的值 * @param point point * @return 查詢與point點聯通的最大格子的值 */ protected abstract int find(MazePoint point); /** * 聯通point1和point2點 * @param point1 point1 * @param point2 point2 */ protected abstract void union(MazePoint point1, MazePoint point2); /** * 判斷時候已經生成迷宮路徑 * @return 判斷時候已經生成迷宮路徑 */ protected abstract boolean hasPath(); /** * 生成迷宮 */ public void makeMaze(){ while (hasPath()){ // 生成 當前點, 當前點聯通的方向, 當前點聯通的方向對應的點 ThreeTuple<MazePoint, Position, MazePoint> tuple = findNextPoint(); if (tuple == null){ continue; } union(tuple.one, tuple.three); breakWall(tuple.one, tuple.two); breakWall(tuple.three, tuple.two.anotherSide()); } breakWall(new MazePoint(0,0), Position.LEFT); breakWall(new MazePoint(row - 1, line - 1), Position.RIGHT); } /** * 生成 當前點, 當前點聯通的方向, 當前點聯通的方向對應的點 * @return * ThreeTuple.one 當前點 * ThreeTuple.two 當前點聯通的方向 * ThreeTuple.three 當前點聯通的方向對應的點 */ private ThreeTuple<MazePoint, Position, MazePoint> findNextPoint() { MazePoint currentPoint = new MazePoint(random.nextInt(row), random.nextInt(line)); Position position = Position.indexOf(random.nextInt(Position.values().length)); MazePoint nextPoint = findNext(currentPoint, position); if (nextPoint == null || find(currentPoint) == find(nextPoint)){ return null; } return new ThreeTuple<MazePoint, Position, MazePoint>(currentPoint, position, nextPoint); } /** * 打通牆 * @param point 當前點 * @param position 當前點的方向 */ private void breakWall(MazePoint point, Position position) { boxes[point.x][point.y].set(position, Wall.ACCESS); } /** * 經過當前點以及對應當前點的方向找到下一個點 * @param currentPoint 當前點 * @param position 方向 * @return 下個點,若該點在迷宮內,這返回,不然返回null */ private MazePoint findNext(MazePoint currentPoint, Position position) { MazePoint nextPoint; switch (position){ case TOP: nextPoint = new MazePoint(currentPoint.x - 1, currentPoint.y); break; case RIGHT: nextPoint = new MazePoint(currentPoint.x, currentPoint.y + 1); break; case DOWN: nextPoint = new MazePoint(currentPoint.x + 1, currentPoint.y); break; case LEFT: default: nextPoint = new MazePoint(currentPoint.x, currentPoint.y - 1); break; } if (nextPoint.x < 0 || nextPoint.x >= row || nextPoint.y < 0 || nextPoint.y >= line){ return null; } return nextPoint; } public Box getBoxes(int x, int y) { return boxes[x][y]; } public int getRow() { return row; } public int getLine() { return line; } /** * 求解迷宮路徑 * @return 迷宮路徑 */ public List<MazePoint> solvePath(){ // 1 迷宮時候已經求解完成,是的話,則直接返回,沒必要再次計算 if (isSolver){ return bestPath; } // 2 不然計算迷宮最佳路徑 bestPath = new ArrayList<MazePoint>(); solverPath(new MazePoint(0, 0), 0); addPath(new MazePoint(row - 1, line - 1)); Collections.reverse(bestPath); isSolver = true; return bestPath; } /** * 從終點逆推,添加最佳路徑 * @param point 當前點 */ private void addPath(MazePoint point) { bestPath.add(point); // 遍歷當前點的每一個方向,若是該方向能聯通,這步數跟當前點的步數相差1步,這添加改點,遞歸計算下去 for (Position position :Position.values()){ MazePoint next = findNext(point, position); if (next == null || getBoxes(point.x, point.y).get(position) == Wall.BLOCK){ continue; } if (solverPath[point.x][point.y] - 1 == solverPath[next.x][next.y]){ addPath(next); return; } } } /** * 遞歸求解迷宮最佳路徑 * @param point 當前點 * @param count 從開始走到當前點所須要的步數 */ private void solverPath(MazePoint point, int count) { // 判斷當前點的步數時候小於如今走到這個點的步數, // 若是當前點的步數比較小,則直接返回 if (solverPath[point.x][point.y] <= count){ return; } // 不然表示當前點,有更短的路徑 solverPath[point.x][point.y] = count; // 再遍歷當前點的每一個方向 for (Position position : Position.values()){ MazePoint next = findNext(point, position); // 若是下一個點不在迷宮內,或當前點對應的方向是一面牆壁,則跳過繼續編寫下一個方向 if (next == null || getBoxes(point.x, point.y).get(position) == Wall.BLOCK){ continue; } // 不然,步數加1, 遞歸計算 solverPath(next, count + 1); } } public static class MazePoint{ public final int x; public final int y; public MazePoint(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return "MazePoint{" + "x=" + x + ", y=" + y + '}'; } } }
代碼上有註釋,理解起來仍是比較容易.
MazeArrayBuilder
和MazeMapBuilder
的實現就參考github了.
AbstractMazeBuilder
中還包括了迷宮的求解.
迷宮的求解,通常我會使用如下兩種方法
右手規則,從起點出發,遇到牆壁,則向右手邊轉,按照這個規則.通常是能夠找到出口的.不過若是迷宮有閉環,則沒法求解,並且解出來的路徑也不是最短路徑.
迷宮最短路徑算法.
從起點出發,計數count=0
遍歷該點的任意方向,若是是牆壁,則忽略,否則count++,進入下一個聯通的格子
判斷當前格子的的count(默認值通常是比較大的數)是否比傳入的參數大,是說明該格子是一條捷徑,將當前各自的count=入參,繼續第2步;不然,說明該點已經被探索過且不是一條捷徑,忽略
若是反覆,暴力遍歷全部單元格,便可以求出最短路徑
遍歷完以後,從出口開始找,此時出口的數字,表示從入口走到出口須要的最小步數.依次減1,找到下一個格子,直到找打入口.則最短路徑就生成了.
若是以爲個人文章寫的還過得去的話,有錢就捧個錢場,沒錢給我捧我的場(幫我點贊或推薦一下)