【问题标题】:Why is my recursive maze algorithm not back tracking?为什么我的递归迷宫算法不回溯?
【发布时间】:2014-06-14 21:53:01
【问题描述】:

请原谅我是递归的新手,但据我了解这应该可以工作,但它没有。我做了这个在成功找到路径时递归调用自身的方法:

private void RandStep(Point pos)
    {       
        Collections.shuffle(Arrays.asList(directions), rand); //Incorrect, should have a local directions array. Nothing to do with the question though.
        for (int i = 0; i < directions.length;i++)
        {
            //Check if the positions are within map bounds.
            if (pos.x + directions[i].x >= 0 && pos.x + directions[i].x < width && pos.y + directions[i].y >= 0 && pos.y + directions[i].y < height)
            {
                //Check if the position is unvisited.
                if (!mazeMap[pos.x + directions[i].x][pos.y + directions[i].y].visited)
                {
                    //Break walls this tile.
                    CarvePassage(pos, directions[i]);
                    mazeMap[pos.x + directions[i].x][pos.y + directions[i].y].visited = true;
                    position.setLocation(pos.x + directions[i].x, pos.y + directions[i].y);
                    RandStep(position);
                }
            }
        }
    }
  • 首先它随机化一个具有 4 个方向的数组。
  • 然后我循环遍历数组以找到可能的方向。
  • 然后它检查找到的方向是否有效,否则它会转到数组中的下一个方向
  • 当它有效时,它会调用另一个方法来雕刻当前瓷砖和下一个瓷砖的墙壁。
  • 它将当前位置更改为下一个并将其标志设置为已访问。
  • 最后它再次调用自己进行下一步。

这一切都有效,直到它第一次卡在访问的单元格或地图边界之间。如果我正确理解递归,它应该退出此方法并转到RandStep 的上一次运行并完成方向循环。当它在那里没有找到任何有效的单元格时:它应该再次退出并完成前一个RandStep 中的循环。这应该重复,直到它完成第一个 RandStep 运行的方向循环。

就像我说的,它会在找不到任何有效单元格的那一刻停止。它不会继续递归堆栈上的先前方法。

【问题讨论】:

    标签: java algorithm recursion libgdx path-finding


    【解决方案1】:

    很简单,就是因为你没有退后一步!

    position.setLocation(pos.x + directions[i].x, pos.y + directions[i].y);
    RandStep(position);
    

    应该是这样的:

    position.setLocation(pos.x + directions[i].x, pos.y + directions[i].y);
    RandStep(position);
    position.setLocation(pos.x - directions[i].x, pos.y - directions[i].y);
    

    作为一个直觉,想想在递归的基本情况下会发生什么。你周围的所有瓷砖都被访问了,你处于死胡同。这种情况看起来像:

     _
    | |
    |x|
    

    (x = "你在这里")

    然后,position.setLocation(pos.x + directions[i].x, pos.y + directions[i].y); 把你带到这里:

     _
    |x|
    | |
    

    然后,RandStep(position);什么都不做,因为你周围的所有位置都被探索过了。所以接下来你要做的就是后退一步,这可以通过以下方式完成:position.setLocation(pos.x - directions[i].x, pos.y - directions[i].y);

    【讨论】:

    • 是的,很明显,或者为每种方法使用本地位置。谢谢!
    • 实际上,递归远非显而易见:)
    • 是的,自从哥斯达黎加在那里打进第二个进球后,我一直在为这件事头疼:D。但实际上它仍然不起作用,它没有完全回溯并填满完整的地图。
    • 其实,没关系,这可能行不通。我有点需要查看其余代码以提出任何进一步的建议。但我认为我的建议是普遍问题。
    • 我认为您最初的回答是正确的。我实际上正在踩最后一块瓷砖,检查所有瓷砖然后失败并返回。然后它应该在那里反转方向以退后一步。但做得更好,但不知何故没有填满完整的地图。
    【解决方案2】:

    让我们仔细看看幕后发生的事情。假设您熟悉堆和堆栈是什么,变量position 位于heap,因为它不是您的函数RandStep 的本地变量。另一方面,pos 是本地的,所以它驻留在堆栈上。每次递归调用都会分配一个新的堆栈帧(从 frameA 到 frameC)。为简洁起见,我将位置表示为单个值。假设您从位置 0 开始,然后向位置 1 打破墙壁。发生的情况是为 pos 的新值分配了一个新帧,但 position 被覆盖。当你从位置 1 过渡到位置 2 时也是如此。假设从位置 2 无处可去,所以我们需要追溯。然后释放 frameC。但是,您对RandStep 的调用是在position 上进行的,而不是在顶部框架中使用pos 的值(现在是frameB),因此您使用的值是堆上的值,它仍然存在尽管释放了帧,但没有改变。

    Intredasting 建议的“后退”实际上意味着手动更新堆,以便position 遵循与pos 相同的演变。在我看来,这违背了递归的目的。它的美妙之处在于它通过释放来处理后退部分。

    总之,您需要做的是完全避免在递归函数中更改堆。

    |STACK          |   |STACK         |    |STACK          |
    |               |   |              |    |               |
    |               |   |              |    |frameC: pos = 2|---> got stuck, deallocate frame
    |               |   |frameB: pos=1 |    |frameB: pos = 1|     and backtrack
    |frameA: pos=0  |   |frameA: pos=0 |    |frameA: pos = 0|
    |---------------|   |--------------|    |---------------|
    |HEAP           |   |HEAP          |    |HEAP           |
    |position = 0   |   |position=1    |    |position = 2   |
    |               |   |              |    |               |
    |               |   |              |    |               |
    -----------------   ----------------    -----------------
    

    【讨论】:

    • 是的,这正是方向数组发生的情况,在我的初始代码中进行了编辑/评论。感谢您的深入解释。