【问题标题】:Something wrong with BFS maze solving algorithm in OCamlOCaml 中的 BFS 迷宫求解算法有问题
【发布时间】:2013-05-31 16:59:44
【问题描述】:

http://ideone.com/QXyVzR

上面的链接包含我编写的使用 BFS 算法解决迷宫的程序。迷宫表示为一个二维数组,最初以数字形式传入,(0 表示可以访问的空块,任何其他数字表示“墙”块),然后转换为我定义的记录类型,它保持跟踪各种数据:

type mazeBlock = {
    walkable   : bool;
    isFinish   : bool;
    visited    : bool;
    prevCoordinate : int * int
}

输出是有序对(坐标/索引)的列表,它们跟踪从起点到终点穿过迷宫的最短路径,其坐标都作为参数传入。

它适用于具有低分支因子的较小迷宫,但是当我在较大的迷宫(例如 16 x 16 或更大)上测试它时,尤其是在没有墙壁的迷宫(高分支因子)上,它会占用大量时间并且记忆。我想知道这是算法固有的还是与我实现它的方式有关。有哪位 OCaml 黑客可以向我介绍他们的专业知识吗?

另外,我对 OCaml 的经验非常少,因此对于如何从风格上改进代码的任何建议将不胜感激。谢谢!


编辑:

http://ideone.com/W0leMv

这是该程序的清理、编辑版本。我修复了一些风格问题,但没有改变语义。和往常一样,第二次测试仍然占用大量资源,似乎根本无法完成。仍在寻求有关此问题的帮助...

编辑2:

已解决。非常感谢两位回答者。这是最终代码:

http://ideone.com/3qAWnx

【问题讨论】:

    标签: ocaml breadth-first-search maze


    【解决方案1】:

    在你的临界区,即mazeSolverLoop,你应该只访问以前没有访问过的元素。当您从队列中取出元素时,您应该首先检查该元素是否已被访问,在这种情况下,除了递归获取下一个元素之外什么都不做。这正是算法具有良好时间复杂度的原因(您永远不会访问一个地方两次)。

    否则,是的,您的 OCaml 样式可以改进。一些备注:

    • OCaml-land 中的约定是write_like_this 而不是writeLikeThis。我建议您遵循它,但不可否认,这是一个品味问题,而不是客观标准。

    • 如果数据结构是已更新的可变结构,则返回数据结构毫无意义;当它与输入完全相同时,为什么要始终返回(grid, pair) 队列?您可以让这些函数返回 unit,并编写更简单、更易于阅读的代码。

    • pair 允许的抽象级别很好,应该保留它;你目前没有。例如,let (foo, bar) = dimension grid in if in_bounds pos (foo, bar) 没有任何意义。只需将维度命名为 dim 而不是 (foo, bar),如果您不需要单独将其拆分为两个组件,则没有任何意义。请注意,对于邻居,您现在确实使用 neighborXneighborY 进行数组访问,但这是一个风格错误:您应该有辅助函数来获取和设置数组中的值,将一对作为输入,所以您不必在主函数中破坏该对。尝试将单个函数中的所有代码保持在同一抽象级别:所有代码都在单独的坐标上工作,或者都在对上工作(这样命名而不是一直被构造/解构)。

    【讨论】:

    • 感谢您让我了解大会。我稍后会修复这些。关于队列:顺便说一句,我写了targetForVisitation 函数(希望我做得对吗?),保证所有出队的元素都不会被访问;这就是为什么我对第二次测试在每次运行时都会占用我所有内存感到困惑的原因......
    • 我认为targetForVisitation 中的测试还不够。如果当前位置 A 有邻居 B 和 C,它们都有共同的邻居 D,你会访问 B,添加它的所有邻居(包括 D),然后访问 C,因为它还没有被访问过,所以再次添加 D。这意味着您将在主循环中多次遍历 D。甚至在第二次遍历时,它的所有邻居都不会被标记为已访问。
    • PS:我实现了我描述的更改,并且代码运行得更快了。
    • 哇...我不敢相信我错过了如此重要的细节。无意义的重复数量几乎呈指数级增长,难怪代码速度如此之慢。我想我会通过在入队(作为邻居)而不是出队时标记节点来解决它。你能把你在评论中说的话放到你的答案中,我会马上选择它吗?非常感谢!
    • 在一般情况下 (Dijsktra),当您将尚未访问的邻居添加到工作队列时,重要的是不要访问它们,因为您可能会发现它们与另一个的距离更短稍后结点。因此,我建议的修复比您实施的修复要好。但是,我怀疑在所有距离都恰好为1 的受限情况下,这是等价的。
    【解决方案2】:

    如果我理解正确,对于没有墙壁的 N x N 网格,您有一个包含 N^2 个节点和大约 4*N^2 个边的图。对于 N = 16,这些似乎不是很大的数字。

    我想说唯一的技巧是确保您正确跟踪访问过的节点。我浏览了您的代码,没有发现您的操作方式有任何明显错误。

    这是一个很好的 OCaml 习惯用法。你的代码说:

    let isFinish1 = mazeGrid.(currentX).(currentY).isFinish in
    let prevCoordinate1 = mazeGrid.(currentX).(currentY).prevCoordinate in
    mazeGrid.(currentX).(currentY) <-
        { walkable = true;
         isFinish = isFinish1;
         visited = true;
         prevCoordinate = prevCoordinate1}
    

    你可以这样说更经济一点:

    mazeGrid.(currentX).(currentY) <-
        { mazeGrid.(currentX).(currentY) with visited = true }
    

    【讨论】:

    • 我以前不知道这个。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2013-04-16
    • 2014-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 1970-01-01
    相关资源
    最近更新 更多