【问题标题】:Recursive backtracking maze leaves tiles sometimes递归回溯迷宫有时会留下瓷砖
【发布时间】:2015-03-30 09:16:02
【问题描述】:

我有一个为我生成迷宫的基本回溯算法。但有时它不会“访问”所有图块/单元格。我想知道出了什么问题,该算法确实可以正确回溯,它应该检查每个图块/单元格上的所有方向,但“未访问”的图块/单元格根本没有被触及。

这是回溯算法:

void GenerateMaze(Coordinate tilePos)
    {
        //Mark the current position visited
        tileMap[tilePos.x, tilePos.y].visited = true;
        //Randomize directions
        Shuffle<Coordinate>(directions);
        foreach(Coordinate d in directions)
        {
            //Check if the new position is within bounds
            if (tilePos.x + d.x >= 0 && tilePos.x + d.x < mapWidth && tilePos.y + d.y >= 0 && tilePos.y + d.y < mapHeight)
            {
                //Check if the tile is already visited
                if (!tileMap[tilePos.x + d.x, tilePos.y + d.y].visited)
                {
                    //Carve through walls from this tile to next
                    Carve(tilePos, d);
                    //Recursively call this method on the next tile
                    GenerateMaze(new Coordinate(tilePos.x + d.x, tilePos.y + d.y));
                }
            }
        }
    }

如果您有兴趣,这是Carve 方法:

private void Carve(Coordinate position, Coordinate direction)
    {
        if (direction.Equals(new Coordinate(-1, 0)))
        {
            Debug.Log("Carving West from: ");
            tileMap[position.x, position.y].west = true;
            tileMap[position.x + direction.x, position.y + direction.y].east = true;
        }
        else if (direction.Equals(new Coordinate(1, 0)))
        {
            tileMap[position.x, position.y].east = true;
            tileMap[position.x + direction.x, position.y + direction.y].west = true;
        }
        else if (direction.Equals(new Coordinate(0, -1)))
        {
            tileMap[position.x, position.y].south = true;
            tileMap[position.x + direction.x, position.y + direction.y].north = true;
        }
        else if (direction.Equals(new Coordinate(0, 1)))
        {
            tileMap[position.x, position.y].north = true;
            tileMap[position.x + direction.x, position.y + direction.y].south = true;
        }
    }

它只是根据算法前进的方向将正确的墙壁标志设置为真。

在下图中,您可以看到迷宫有 3 个“未访问”的图块。这主要发生在角落。

在这里它保留了一块未触及的瓷砖,但这次不是在侧面。

在 10x10 的迷宫中,这似乎会发生大约 1/10 次。问题瓦片未被访问,因此算法根本不处理它们。但是,由于它经过他们并且邻居的每个方向都经过测试,他们真的应该加入迷宫。那么有什么问题呢?

【问题讨论】:

  • ehm .... 在我看来,您只会访问您的(伪)随机游走将您带到的图块 - 所以如果您不能回到一个单元格,因为它周围的一切都已经访问过你不会访问它... backtrack 不会帮助你,因为你访问过的标志是全局的,所以一个分支中的操作在回溯之后仍然存在(请不要使用 global-state带递归函数)
  • @CarstenKönig 不确定您的意思。它应该访问每个单元格并检查每个单元格的所有方向。如果找到有效的移动/方向,它最终应该总是回来检查其他方向。这是一个 ruby​​ 实现:weblog.jamisbuck.org/2010/12/27/…
  • @CarstenKönig 如果我只是在未连接的单元格上执行GenerateMaze(new Coordinate(x + 1, y));,它可以解决问题。但是为什么它没有在回归的路上修复它呢?应该检查每个方向,以便它应该捕获未访问的单元格。
  • 我完全支持 @CarstenKönig 这张支票 if (!tileMap[tilePos.x + d.x, tilePos.y + d.y].visited) 没有出现在 buckblog 源代码中,并且在网页上观看算法的运行似乎没有问题重新访问已经雕刻出来的节点,直到所有节点被访问。
  • 可以分享directions的内容是什么吗?

标签: c# algorithm recursion unity3d maze


【解决方案1】:

问题是

Shuffle<Coordinate>(directions);

在每个步骤中,您将 directions 中的内容打乱

但是,还要记住,在每一步中,您都会遍历 directions 中的每个坐标

foreach(Coordinate d in directions)
{
     //Visit child node
}

所以,因为您使用DFS 样式发现矩阵,因此,当您在父节点中迭代directions 时,您还访问了它的所有子节点。同样,shufflingdirections 在访问每个子节点时,这可能会通过打乱 directions 中元素的当前顺序来随机破坏父节点中的迭代过程。

简单示例

In parent, directions order is (0,1,2,3)

Visit first child (direction 0)-> shuffle directions (1,0,2,3)

Go back to parent node, now you will skip one node (direction 1), as the directions content has been changed.

将此 DFS 更改为 BFS 将解决此问题。

伪代码:

Queue<Coordinate> q;
q.add(start)
while(q is not empty){
    Coordinate point = q.dequeue();
    shuffle directions
    for(each direction in directions){
        Add unvisited child node into q
    }
}

【讨论】:

  • 当然,这可能就是它。从未与Queu 合作过,我也可以复制方向List。使用队列必须更快,但从未使用过。几个小时后看看。
  • 我脑海中闪过的一件事是,如果父级的方向列表与子级的方向一起洗牌,它不应该更频繁地留下“空白”单元格吗? foreach 循环不是已经为父级“制作”了吗?
  • @MennoGouw 我认为这取决于您用于directionsLink 的集合类型。如果是 List,据我在查看documentation 时的理解:An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.,因此在我们的情况下无法预测行为。
  • 点数用得着,感谢您提供Queue 的解决方案,这似乎是最好的解决方案,因为克隆或深度复制完整列表是过度杀伤,尤其是在大型网格上。目前我只是通过在递归方法中初始化列表并在那里洗牌来测试它。
  • @MennoGouw 很高兴有帮助:)
猜你喜欢
  • 2014-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-08
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
相关资源
最近更新 更多