三种迷宫生成算法
2016-01-07 17:02
513 查看
三种迷宫生成算法
递归回溯:
运行效果:
递归分割:
运行效果:
Eller算法:
运行效果:
打印迷宫:
递归回溯法使用递归方式实现容易造成栈溢出,所以重新实现了递归回溯:
参考:
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
递归回溯:
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
相关文章推荐
- 获取地图
- mysql case结合group+having使用
- 学习spring mabits
- GCD的常见用法
- 优秀博客推荐
- JPA使用非JTA数据源
- SSH 远程控制 Linux
- Javassist 教程(一)
- windows mysql 5.7 启动和登录问题
- 后台绑定datagrid的SelectionItem事件时,anchor为null,致使shift多选出错
- UIPickerView选择器的使用方法
- 高频交易[z]
- Android使用Mob开发平台免费短信验证
- Android登录对话框,Android弹出登录窗口
- node.js学习笔记之promise
- tomcat源码分析
- 一键完成RHCE6.0上午的考试
- Ubuntu 一些基本命令
- c++11智能指针解析——揭开底层面纱,完整理解智能指针
- java环境配置-jdk安装