什么是生成迷宫的好算法?

时间:2008-09-01 21:57:45

标签: algorithm maze

假设你想在一个N×M网格上有一个简单的迷宫,一条路径通过,并且有很多死角,但看起来“正确”(就像有人用手工制作而没有太多小小的死角所有这一切)。有没有一种已知的方法可以做到这一点?

9 个答案:

答案 0 :(得分:35)

事实证明,有12种经典算法可以生成"完美"迷宫。如果迷宫只有一种解决方案,它就是完美的。以下是每种算法的一些链接,按照我的偏好粗略排列。

  1. Kruskal's
  2. Prim's
  3. Recursive Backtracker
  4. Aldous-Broder
  5. Growing Tree
  6. Hunt-and-Kill
  7. Wilson's
  8. Eller's
  9. Cellular Automaton(简单)
  10. Recursive Division(非常简单)
  11. Sidewinder(可预测)
  12. Binary Tree(有缺陷)
  13. 有关详细信息,请查看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;个性”的迷宫。请看我的例子:

https://mtimmerm.github.io/webStuff/maze.html

答案 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.

有详细说明

我将简要介绍一下:
原始的递归除法算法的工作原理如下。首先,从迷宫的空白处开始。添加一堵笔直的墙壁,将腔室一分为二,然后在该墙上的某个地方放一个孔。然后,在两个新腔室中的每一个上递归地重复此过程,直到达到所需的通道大小。这很简单并且效果很好,但是存在明显的瓶颈,使迷宫易于解决。

该变体通过绘制随机的“弯曲”墙而不是直墙来解决此问题,从而使瓶颈不太明显。