本人邮箱: <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,找到下一个格子,直到找打入口.则最短路径就生成了.
若是以为个人文章写的还过得去的话,有钱就捧个钱场,没钱给我捧我的场(帮我点赞或推荐一下)