假设你想在一个N×M网格上有一个简单的迷宫,一条路径通过,并且有很多死角,但看起来“正确”(就像有人用手工制作而没有太多小小的死角所有这一切)。有没有一种已知的方法可以做到这一点?
答案 0 :(得分:35)
事实证明,有12种经典算法可以生成"完美"迷宫。如果迷宫只有一种解决方案,它就是完美的。以下是每种算法的一些链接,按照我的偏好粗略排列。
有关详细信息,请查看GitHub上的mazelib,这是一个实现所有标准迷宫生成/求解算法的Python库。
答案 1 :(得分:33)
来自http://www.astrolog.org/labyrnth/algrithm.htm
递归回溯:这有点与下面描述的递归回溯解决方法有关,并且需要叠加到迷宫的大小。雕刻时,尽可能贪婪,如果一个人在当前的牢房旁边,他们总是刻在一个未经制作的部分。每次移动到新单元格时,按下堆栈上的前一个单元格。如果当前位置旁边没有未编辑的单元格,则将堆栈弹出到上一个位置。当你从堆栈中弹出一切时,迷宫就完成了。该算法使Mazes具有尽可能高的“河流”因子,具有更少但更长的死角,并且通常是非常长且扭曲的解决方案。它的运行速度非常快,尽管Prim的算法速度要快一些。递归回溯不能用作墙加法器,因为这样做往往会产生一个跟随外边缘的解决方案路径,其中迷宫的整个内部通过单个杆连接到边界。
他们只产生10%的死角
是该方法生成的迷宫的一个例子。
答案 2 :(得分:19)
一个非常简单的解决方案是将随机权重分配给图形边缘,并应用Kruskal's algorithm来查找最小生成树。
关于迷宫生成算法的最佳讨论:http://www.jamisbuck.org/presentations/rubyconf2011/index.html(几天前在HN上)。
答案 3 :(得分:4)
奇怪的是,通过略微更改“规范”规则并从随机配置开始,Conway's Game of Life似乎会产生非常好的迷宫!
(我不记得确切的规则,但这是一个非常简单的修改,往往会“密集”细胞群......)
答案 4 :(得分:3)
我最喜欢的方法是使用Kruskal的算法,但是当随机选择和删除边缘时,根据它所连接的边缘类型对选择进行加权。
通过改变不同边缘类型的权重,您可以生成具有许多不同特征或“#34;个性”的迷宫。请看我的例子:
答案 5 :(得分:2)
递归回溯是最容易实现的算法。
这是Java实现:
此处 Cell 是表示2D网格中单元格的类,而 cells 是 Cell 对象的2D数组。 Cell 具有布尔变量 top , bottom , left 和 right 来指示是否单元格的两边都有壁,一个布尔变量 visited 检查我们是否已遍历它,两个整数变量 row 和 col 表示其位置在网格中。
Cell current = cells[0][0] , next;
current.visited=true;
do{
next = getNeighbour(current);
if(next!=null){
removeWall(current , next);
st.push(current);
current = next;
current.visited = true;
}
else {
current = st.pop();
}
}
while (!st.empty());
private Cell getNeighbour(Cell cell){
ArrayList<Cell> ara = new ArrayList<>();
if(cell.col>0 && !cells[cell.col-1][cell.row].visited)
ara.add(cells[cell.col-1][cell.row]);
if(cell.row>0 && !cells[cell.col][cell.row-1].visited)
ara.add(cells[cell.col][cell.row-1]);
if(cell.col<col-1 && !cells[cell.col+1][cell.row].visited)
ara.add(cells[cell.col+1][cell.row]);
if(cell.row<row-1 && !cells[cell.col][cell.row+1].visited)
ara.add(cells[cell.col][cell.row+1]);
if(ara.size()>0){
return ara.get(new Random().nextInt(ara.size()));
}else{
return null;
}
}
private void removeWall(Cell curr , Cell nxt){
if((curr.col == nxt.col) && (curr.row == nxt.row+1)){/// top
curr.top=nxt.botttom=false;
}
if(curr.col==nxt.col && curr.row == nxt.row-1){///bottom
curr.botttom = nxt.top = false;
}
if(curr.col==nxt.col-1 && curr.row==nxt.row ){///right
curr.right = nxt.left = false;
}
if(curr.col == nxt.col+1 && curr.row == nxt.row){///left
curr.left = nxt.right = false;
}
}
答案 6 :(得分:1)
生成迷宫的方法之一是Prim算法的随机版本。
从一个满墙的网格开始。 选择一个单元格,将其标记为迷宫的一部分。将单元格的墙添加到墙列表中。 虽然列表中有墙:
从列表中选择一个随机墙。如果对面的细胞尚未进入迷宫:
(i)将墙壁作为通道,并将对面的单元格标记为迷宫的一部分。
(ii)将单元格的相邻墙添加到墙列表中。
如果对面的单元格已经在迷宫中,请从列表中移除墙壁。
要获得更多理解,请点击here
答案 7 :(得分:0)
这里的DFS算法写为伪代码:
创建一个CellStack(LIFO)来保存单元格位置列表
set TotalCells =网格中的单元格数量
随机选择一个单元格并将其命名为CurrentCell
设置VisitedCells = 1
而VisitedCells&lt; TotalCells
找到所有邻居的CurrentCell,所有墙壁完好无损
如果找到一个或多个
随意选择一个
击倒它和CurrentCell之间的墙壁
推送CellStack上的CurrentCell位置
制作新单元格CurrentCell
将1添加到VisitedCells
其他
从CellStack中弹出最近的单元格条目
使它成为CurrentCell
万一
endWhile
答案 8 :(得分:0)
我更喜欢递归除法算法的版本。 here.
有详细说明 我将简要介绍一下:
原始的递归除法算法的工作原理如下。首先,从迷宫的空白处开始。添加一堵笔直的墙壁,将腔室一分为二,然后在该墙上的某个地方放一个孔。然后,在两个新腔室中的每一个上递归地重复此过程,直到达到所需的通道大小。这很简单并且效果很好,但是存在明显的瓶颈,使迷宫易于解决。
该变体通过绘制随机的“弯曲”墙而不是直墙来解决此问题,从而使瓶颈不太明显。