【问题标题】:BFS bad complexityBFS 糟糕的复杂性
【发布时间】:2019-04-07 14:51:19
【问题描述】:

我正在使用邻接表来表示 OCaml 中的图形。然后我从节点s开始在OCaml中实现了以下BFS。

let bfs graph s=
    let size = Array.length graph in
    let seen = Array.make size false and next = [s] in 
    let rec aux  = function 
    |[] -> ()
    |t::q -> if not seen.(t) then  begin seen.(t) <- true;  aux (q@graph.(t)) end  else aux q 
    in aux next

size表示图的节点数。 seen 是一个数组,其中seen.(t) = true 如果我们看到了节点tnext 是我们需要看到的节点的列表。

问题是 BFS 的时间复杂度通常是线性的 (O(V +E)) 但我觉得我的实现没有这种复杂度。如果我没记错的话,q@graph.(t) 的复杂性相当大,因为它是 O(| q |)。所以我的复杂性非常糟糕,因为在每一步我都在连接两个列表,而且时间很长。

因此,我想知道如何调整此代码以制作高效的 BFS?问题(我认为)来自使用列表的队列实现。 OCaml 中 Queue 模块的复杂性是否需要 O(1) 才能添加元素?在这种情况下,我如何使用这个模块来使我的 bfs 工作,因为我不能像列表一样轻松地使用 Queue 进行模式匹配?

【问题讨论】:

    标签: time-complexity ocaml breadth-first-search


    【解决方案1】:

    q@graph.(t) 的复杂度相当大,因为它是 O(| q |)。所以我的复杂性非常糟糕,因为在每一步我都在连接两个列表,而且时间很长。

    你说得对——这是你的 BFS 的瓶颈。您应该可以愉快地使用Queue 模块,因为根据https://ocaml.org/learn/tutorials/comparison_of_standard_containers.html 插入和获取元素的操作是O(1)。

    OCaml 中队列和列表之间的区别之一是队列是可变结构,因此您需要使用非纯函数,例如 addtaketop,它们分别就地插入元素、弹出从前面的元素并返回第一个元素。

    【讨论】:

    • 谢谢!如果我实施的是 DFS,我猜我的算法的复杂性会好吗?由于唯一的变化是 q@graph.(t) 被转换为 graph.(t)@q 并且我得到了 O(graph.(t)) 的复杂度,所以在这种情况下这是可以的。
    • 是的,这是正确的,因为在这个@ 中,您最多只能处理每个节点一次
    【解决方案2】:

    如果我没记错的话,q@graph.(t) 的复杂度相当大,因为它是 O(| q |)。

    这确实是问题所在。你应该使用的是graph.(t) @ q。其复杂度为 O(| graph.(t) |)。

    你可能会问:这有什么不同?

    区别在于|q|可以是从 0 到 V * E. graph.(t) 的任何东西,另一方面,您可以使用。您最多访问图中的每个顶点一次,因此总体复杂度将是

      O(\Sum_V |grahp.(v))
    

    图中每个顶点的所有边的总和。或者换句话说:E.

    这让您了解 O(V + E) 的整体复杂性。

    【讨论】:

    • 在这种情况下,它是 DFS 而不是 BFS
    • 您必须从头开始处理列表。这可以通过拥有 2 个列表来实现。一个您当前正在工作,一个用于附加未来的顶点。然后每次当前列表为空时,您反转未来列表并重新开始。
    • 这是一个传统的不可变队列。为什么你没有在回答中提到这个解决方案?
    猜你喜欢
    • 2022-01-05
    • 2011-12-31
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-26
    • 2013-02-04
    相关资源
    最近更新 更多