三种迷宫生成算法

来源:互联网 发布:发型匹配软件 编辑:程序博客网 时间:2024/06/05 21:04

三种迷宫生成算法

递归回溯:

static char block = 'x';static char way = ' ';enum DIRECTION {UP, DOWN, LEFT, RIGHT};static List<String> history = new ArrayList<String>();static Random ran = new Random(System.currentTimeMillis());/** *  * 递归回溯(核心思想为打洞) * 在迷宫中任意选择一个点 * 从这个点朝任意一个方向挖洞,打通下一个点(已走过的点不能被打通) * 从下个点继续打洞,直到无法某个点无法打通到下个点时,回退到上个点 * 直到回退到初始点时,迷宫完成 *  */public static char[][] createMaze() {int size = 20;char[][] maze = new char[size * 2 - 1][size * 2 - 1];for (int i = 0; i < size * 2 - 1; i++) {for (int j = 0; j < size * 2 - 1; j++) {if (i % 2 == 0 && j % 2 == 0) {maze[i][j] = way;} else {maze[i][j] = block;}}}Random ran = new Random(System.currentTimeMillis());int x = ran.nextInt(size), y = ran.nextInt(size);history.clear();digHole(maze, x, y);return maze;}private static void digHole(char[][] maze, int x, int y) {int size = (maze.length + 1) / 2;if (x < 0 || y < 0 || x >= size || y >= size) {return;}Set<DIRECTION> dirList = new HashSet<DIRECTION>();int dir = ran.nextInt(4);history.add(x + "," + y);while (dirList.size() < 4) {// 选择一个方向while (dirList.contains(DIRECTION.values()[dir])) {dir = (dir >= 3 ? 0 : dir + 1);}int nextX = -1, nextY = -1;// 定位下个点DIRECTION direction = DIRECTION.values()[dir];switch (direction) {case UP:nextX = x - 1;nextY = y;break;case DOWN:nextX = x + 1;nextY = y;break;case LEFT:nextX = x;nextY = y - 1;break;case RIGHT:nextX = x;nextY = y + 1;break;}// 挖洞if (nextX >= 0 && nextX < size && nextY >= 0 && nextY < size&& !history.contains(nextX + "," + nextY)) {maze[nextX * 2][nextY * 2] = way;try {switch (direction) {case UP:maze[nextX * 2 + 1][nextY * 2] = way;break;case DOWN:maze[nextX * 2 - 1][nextY * 2] = way;break;case LEFT:maze[nextX * 2][nextY * 2 + 1] = way;break;case RIGHT:maze[nextX * 2][nextY * 2 - 1] = way;break;}} catch (Exception e) {}// 去下个点继续挖洞digHole(maze, nextX, nextY);} dirList.add(direction);}return;}

运行效果:


递归分割:

static class Rect {int x1, y1;int x2, y2;}/** * * 递归分割(核心思想为筑墙) * 在一个空白的区域中,随意选择横向或纵向筑一道墙,同时在墙上任意一个位置打一个洞 * 继续在被分隔出来的两个区域筑墙 * 直到所有区域无法筑墙(即在某个方向上,该区域两个边界之间只剩一条通道的宽度)为止 *  */public static char[][] createMaze2() {int size = 20;char[][] maze = new char[size * 2 - 1][size * 2 - 1];for (int i = 0; i < size * 2 - 1; i++) {for (int j = 0; j < size * 2 - 1; j++) {maze[i][j] = way;}}Rect rect = new Rect();rect.x1 = 0;rect.y1 = 0;rect.x2 = maze.length - 1;rect.y2 = maze.length - 1;divide(maze, rect);return maze;}private static void divide(char[][] maze, Rect rect) {if (rect.x1 == rect.x2 || rect.y1 == rect.y2) {return;}// 取方向:0为横向,1为纵向int dir = ran.nextInt(2);// 取分割线int line = 0;// 开口int hole = 0;// 分离出两个区域Rect first = new Rect();Rect second = new Rect();first.x1 = rect.x1;first.y1 = rect.y1;second.x2 = rect.x2;second.y2 = rect.y2;if (dir == 0) {// 随机选择分割线和线上的开口line = ran.nextInt((rect.x2 - rect.x1) / 2);hole = ran.nextInt((rect.y2 - rect.y1) / 2 + 1);// 筑墙for (int i = rect.y1; i <= rect.y2; i++) {maze[rect.x1 + line * 2 + 1][i] = block;}maze[rect.x1 + line * 2 + 1][rect.y1 + hole * 2] = way;// 定位两个区域first.x2 = rect.x1 + line * 2;first.y2 = rect.y2;second.x1 = rect.x1 + (line + 1) * 2;second.y1 = rect.y1;} else {line = ran.nextInt((rect.y2 - rect.y1) / 2);hole = ran.nextInt((rect.x2 - rect.x1) /2 + 1);for (int i = rect.x1; i <= rect.x2; i++) {maze[i][rect.y1 + line * 2 + 1] = block;}maze[rect.x1 + hole * 2][rect.y1 + line * 2 + 1] = way;first.x2 = rect.x2;first.y2 = rect.y1 + line * 2;second.x1 = rect.x1;second.y1 = rect.y1 + (line + 1) * 2;}// 递归divide(maze, first);divide(maze, second);}


运行效果:


Eller算法:

/** *  * Eller算法(核心思想为打洞) * 从第一行开始,横向随机打洞(即为打洞与不打洞二选一),若两个格子为同个区域,则不打洞 * 将连通的格子合并为一个区域 * 接着纵向打洞,保证每个区域都有一个格子与下一行连通 * 一直向下,到达最后一行横向打洞时,需要将所有区域都打通 *  */public static char[][] createMaze3(int size) {char[][] maze = new char[size * 2 - 1][size * 2 - 1];for (int i = 0; i < size * 2 - 1; i++) {for (int j = 0; j < size * 2 - 1; j++) {if (i % 2 == 0 && j % 2 == 0) {maze[i][j] = way;} else {maze[i][j] = block;}}}// 由于所有区域都与下一行连通,所以只需存储两行的信息即可保证所有区域最终能够通到底部int[][] check = new int[2][size];Map<Integer, Boolean> downMap = new HashMap<Integer, Boolean>();Map<Integer, Integer> downMapIndex = new HashMap<Integer, Integer>();int index = 0;int area = 0;while (index < size) {// 判断格子是否同一区域,随机打通格子// 最后一行需要打通所有区域for (int i = 0; i < size - 1; i++) {int a = check[index % 2][i];int b = check[index % 2][i + 1];// 两个格子属于同一区域if (a != 0 && b != 0 && a == b) {continue;}// 0:打洞,1:不打洞if (ran.nextInt(2) == 0) {maze[index * 2][i * 2 + 1] = way;if (a == 0) {check[index % 2][i]  = ++area;}check[index % 2][i + 1] = check[index % 2][i];} else {// 该格子与其他区域不连通,作为单独一个区域if (index == size - 1) {maze[index * 2][i * 2 + 1] = way;} else if (b == 0) {check[index % 2][i + 1] = ++area;}}}// 向下打洞,每个区域至少有一个格子打通下一层for (int i = 0; i < size; i++) {if (ran.nextInt(2) == 0) {try {// 打洞,将格子合并到区域maze[index * 2 + 1][i * 2] = way;check[(index + 1) % 2][i] = check[index % 2][i];downMap.put(check[index % 2][i], true);} catch (Exception e) {}} else {// 不打洞,记录该区域未连通下行时的最后一个格子check[(index + 1) % 2][i] = 0;if (!downMap.containsKey(check[index % 2][i]) || !downMap.get(check[index % 2][i])) {downMap.put(check[index % 2][i], false);downMapIndex.put(check[index % 2][i], i);}}}// 打通未与下一层连通的区域for (Map.Entry<Integer, Boolean> entity : downMap.entrySet()) {if (!entity.getValue()) {int col = downMapIndex.get(entity.getKey());try {maze[index * 2 + 1][col * 2] = way;check[(index + 1) % 2][col] = check[index % 2][col];} catch (Exception e) {}}}downMap.clear();downMapIndex.clear();index++;}return maze;}

运行效果:


打印迷宫:

public static void printMaze(char[][] maze) {int size = (maze.length + 1) / 2;for (int i = 0; i <= size * 2; i++) {if (i == 1) {System.out.print("  ");} else {System.out.print("x ");}}System.out.println();for (int i = 0; i < size * 2 - 1; i++) {for (int j = 0; j < size * 2 - 1; j++) {if (j == 0) {System.out.print("x ");}System.out.print(maze[i][j] + " ");if (j == size * 2 - 2) {System.out.print("x ");}}System.out.println();}for (int i = 0; i <= size * 2; i++) {if (i == size * 2 - 1) {System.out.print("  ");} else {System.out.print("x ");}}System.out.println();}

递归回溯法使用递归方式实现容易造成栈溢出,所以重新实现了递归回溯:

public static char[][] createMazeX(int size) {char[][] maze = new char[size * 2 - 1][size * 2 - 1];for (int i = 0; i < size * 2 - 1; i++) {for (int j = 0; j < size * 2 - 1; j++) {if (i % 2 == 0 && j % 2 == 0) {maze[i][j] = way;} else {maze[i][j] = block;}}}Random ran = new Random(System.currentTimeMillis());int sx = ran.nextInt(size), sy = ran.nextInt(size);int x = sx, y = sy;Set<String> history = new HashSet<String>();Set<DIRECTION> dirList = new HashSet<DIRECTION>();Deque<String> step = new ArrayDeque<String>();while (true) {while (dirList.size() < 4) {history.add(x + "," + y);// 选择一个方向int dir = ran.nextInt(4);while (dirList.contains(DIRECTION.values()[dir])) {dir = (dir >= 3 ? 0 : dir + 1);}int nextX = -1, nextY = -1;// 定位下个点DIRECTION direction = DIRECTION.values()[dir];switch (direction) {case UP:nextX = x - 1;nextY = y;break;case DOWN:nextX = x + 1;nextY = y;break;case LEFT:nextX = x;nextY = y - 1;break;case RIGHT:nextX = x;nextY = y + 1;break;}// 挖洞if (nextX >= 0 && nextX < size && nextY >= 0 && nextY < size&& !history.contains(nextX + "," + nextY)) {try {switch (direction) {case UP:maze[nextX * 2 + 1][nextY * 2] = way;break;case DOWN:maze[nextX * 2 - 1][nextY * 2] = way;break;case LEFT:maze[nextX * 2][nextY * 2 + 1] = way;break;case RIGHT:maze[nextX * 2][nextY * 2 - 1] = way;break;}} catch (Exception e) {}// 去下个点继续挖洞step.push(x + "," + y);x = nextX;y = nextY;dirList.clear();continue;} else {dirList.add(direction);}}String pre = step.pop();x = Integer.parseInt(pre.split(",")[0]);y = Integer.parseInt(pre.split(",")[1]);dirList.clear();if (x == sx && y == sy) {break;}}return maze;}

参考:

http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking

http://weblog.jamisbuck.org/2011/1/12/maze-generation-recursive-division-algorithm

http://weblog.jamisbuck.org/2010/12/29/maze-generation-eller-s-algorithm


0 0
原创粉丝点击