【算法题集锦之三】--迷宫的随机生成和路径搜索

这个题目和数据结构---图有关,由于我对图掌握的不是很熟悉,所以写这篇博客来加深自己的理解。

迷宫的随机生成和路径搜索主要和图的遍历有关,一般来说图的遍历主要有两种方式:1、深度优先遍历(DFS)2、广度优先遍历(BFS)

两种遍历方式都很好理解,就说说深度优先遍历:

深度优先遍历,顾名思义,就是尽可能往深处遍历,访问到一个节点时,搜索这个节点没有被访问过的相邻节点,选择一个继续做同样的操作,直到没有邻节点为止再回溯到上一个访问的节点,并选择另外的邻节点。

可以这样描述深度遍历:

(1)访问顶点v;

(2)从v的未被访问的邻接点中选取一个顶点w,重复第一步,如果v没有未访问的邻接点,回溯至上一顶点;

(3)重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。

【算法题集锦之三】--迷宫的随机生成和路径搜索_第1张图片

例如上图,一个可能的遍历顺序就是1-->2-->5-->6-->3-->7-->8-->4-->9-->10。

那么,迷宫的生成和路径搜索与图的遍历有什么关系呢?

假设有如下一个图:

【算法题集锦之三】--迷宫的随机生成和路径搜索_第2张图片

试着对这个图进行深度优先遍历并把遍历的轨迹记录下来,如下图。

【算法题集锦之三】--迷宫的随机生成和路径搜索_第3张图片

遍历顺序依次为  1--->2--->6---->10---->9---->5----->13----->14----->15----->11----->12-----16---->8----->7----->3---->4,

是不是有点迷宫的样子了呢?但是上面这个经过遍历生成的路径图还不能完全算一个迷宫,因为还缺少了“墙”,补上即可成为一个完整的迷宫,如下图:

【算法题集锦之三】--迷宫的随机生成和路径搜索_第4张图片

上图中蓝色的节点为墙,在遍历过程中将点之间的墙“抹掉”--即空白的格子,这样就可形成一个完整的迷宫,如上图。

怎么样,是不是就是一个迷宫了,注意到遍历一次后任意两点之间都可以找到一条路, 所以迷宫的出口和入口只需要从外围的点任选两个即可(如上图中选择的是1旁边的和16旁边的点)。

经过上面的分析,我们就知道该怎么生成一个迷宫了。

1、首先初始化迷宫的雏形,即一个矩形图阵,并为图的每个节点旁边都添加上“墙”,如下图:

【算法题集锦之三】--迷宫的随机生成和路径搜索_第5张图片

2、然后对图中的非“墙”节点进行遍历(深度优先或广度优先都行),为了能够生成随机的迷宫,我们可以在遍历过程过适当的做一些随机的操作(代码中会说明),每次遍历时即打通当前遍历的点与上一个点之间的“墙”

3、遍历完所有的点即可生成一个迷宫,然后再选择出口与入口,一个完整的迷宫就形成了。

【算法题集锦之三】--迷宫的随机生成和路径搜索_第6张图片


至于迷宫的路径搜索,那就完全是图的深度遍历了,大概过程如下。

(1)从迷宫起点节点V开始访问

(2)访问这个节点V,标记为可行的路径;

(3)从v的未被访问的非"墙"邻接点中选取一个顶点w,重复第二步。如果v没有未访问的非"墙"邻接点,把这个节点的可行路径标记移除,回溯至上一节点;

(4)重复上述第(2)、(3)步,直至遍历到迷宫的出口节点。

废话不多说,代码才是正道(以一个二维数组来表示迷宫):

package test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

public class Maze {

	/**
	 * 随机生成迷宫
	 * @param size
	 * @return
	 */
	public static Point[][] createMaze(int size){
		
		int realSize = 2 * size + 1;
		Point[][] maze = new Point[realSize][realSize];
		//初始化迷宫雏形
		for(int i = 0; i < realSize; i ++){
			
			for(int j = 0; j < realSize; j ++){
				Point p = new Point();
				if(i % 2 == 0){
					
					p.value = 1;
					
				}else{
					
					if(j % 2 == 0){
						
						p.value = 1;
						
					} else{
						
						p.value = 0;
						
					}
				}
				p.x = i;
				p.y = j;
				maze[i][j] = p;
			}
		}
		// 保存轨迹
		List trace = new ArrayList();
		Point p = maze[1][1];
		List tmpList = new ArrayList();
		while(trace.size() < size * size){
			
			tmpList.clear();
			// 如果没有访问过,设置为访问过
			if(!p.visited){
				
				p.visited = true;
				// 加入到轨迹中
				trace.add(p);
			}
			//依次查看上下左右四个方向的节点是否被访问过,如果没有访问过,加入列表中
			// 上
			if(p.x - 2 > 0) {
				
				Point upPoint = maze[p.x - 2][p.y];
				if(!upPoint.visited)
					tmpList.add(upPoint);
				
			}
			// 下
			if(p.x + 2 < realSize){
				
				Point downPoint = maze[p.x + 2][p.y];
				if(!downPoint.visited)
					tmpList.add(downPoint);
			}
			// 左
			if(p.y - 2 > 0){
				
				Point leftPoint = maze[p.x][p.y - 2];
				if(!leftPoint.visited)
					tmpList.add(leftPoint);
			}
			// 右
			if(p.y + 2 < realSize){
				
				Point rightPoint = maze[p.x][p.y + 2];
				if(!rightPoint.visited)
					tmpList.add(rightPoint);
			}
			// 如果上下左右还有点没有访问
			if(tmpList.size() != 0){
			
				// 随机取一个邻点,这样就可以随机生成路径达到随机生成迷宫的目的
				Point nextPoint = tmpList.get(new Random().nextInt(tmpList.size()));
				// 打通与邻点的“墙”
				maze[(p.x + nextPoint.x) / 2][(p.y + nextPoint.y) / 2].value = 0;
				// 设置这个邻点为下一个访问点
				p = nextPoint;
			} else{
				// 如果没有邻点,从之前轨迹中的点中随机取一个,也是为了随机生成迷宫
				p = trace.get(new Random().nextInt(trace.size()));
			}
			
		}
		return maze;
	}
	
	public static Point[][] search(Point[][] maze, Point enterPoint, Point exitPoint){
		
		int realSize = maze.length;
		// 保存轨迹
		Stack trace = new Stack();
		// 出发点
		Point p = enterPoint;
		boolean hasFind = false;
		do{
			// 如果没有访问过,设置为访问过
			if(!p.visited){
				
				p.visited = true;
				// 设置value为2(表示可行的路径
				p.value = 2;
				// 如果到了终点,中断
				if(p == exitPoint){
					hasFind = true;
					break;
				}
				// 加入到轨迹中
				trace.push(p);
				
			}
			Point nextPoint = null;
			//依次查看上下左右四个方向的节点是否可以走
			// 上
			if(p.x - 1 > 0 && maze[p.x - 1][p.y].value == 0 && !maze[p.x-1][p.y].visited) {
				
				nextPoint = maze[p.x - 1][p.y];
			}
			// 下
			else if(p.x + 1 < realSize && maze[p.x + 1][p.y].value == 0 && !maze[p.x+1][p.y].visited){
				
				nextPoint = maze[p.x + 1][p.y];
			}
			// 左
			else if(p.y - 1 > 0 && maze[p.x][p.y - 1].value == 0 && !maze[p.x][p.y - 1].visited){
				
				nextPoint = maze[p.x][p.y - 1];
			}
			// 右
			else if(p.y + 1 < realSize && maze[p.x][p.y + 1].value == 0 && !maze[p.x][p.y + 1].visited){
				
				nextPoint = maze[p.x][p.y + 1];
			} 
			// 如果上下左右有节点可以走
			if(nextPoint != null){
				// 设置这个邻点为下一个访问点
				p = nextPoint;
			} else{
				// 如果没有邻点,回退到上一个节点
				// 改变value
				p.value = 0;
				p = trace.pop();
			}
			
		} while(!trace.isEmpty());
		if(hasFind)
			System.out.println("找到路径");
		else
			System.out.println("没找到路径");
		return maze;
	}
	public static void main(String[] args) {
		
		int size = 5;
		int realSize = size * 2 + 1;
		// 生成迷宫
		Point[][] maze = createMaze(size);
		
		// 指定迷宫出口、入口
		// 入口
		Point enterPoint = maze[1][0];
		// 设置入口的值为0(表示可通)
		enterPoint.value = 0;
		// 出口
		Point exitPoint = maze[realSize - 2][realSize - 1];
		// 设置出口的值为0(表示可通)
		exitPoint.value = 0;
		
		// 输出迷宫
		for(int i = 0; i < realSize; i ++){
			
			for(int j = 0; j < realSize; j ++){
				
				System.out.print(maze[i][j].value + " ");
				// 重置每个点的访问性
				maze[i][j].visited = false;
			}
			System.out.println();
		}
		// 搜索出口
		maze = search(maze, enterPoint, exitPoint);
		System.out.println();
		for(int i = 0; i < realSize; i ++){
			
			for(int j = 0; j < realSize; j ++){
				
				System.out.print(maze[i][j].value + " ");
			}
			System.out.println();
		}
	}
	
	static class Point{
		public int x;
		public int y;
		public int value;
		boolean visited = false;
	}
}


运行结果如下图:(1表示墙,0表示通路,2表示有效路径,有点丑,有兴趣的同学可以改成图形界面版的··)

【算法题集锦之三】--迷宫的随机生成和路径搜索_第7张图片

你可能感兴趣的:(算法)