【问题标题】:Is it possible to implement linear time BFS in Haskell?是否可以在 Haskell 中实现线性时间 BFS?
【发布时间】:2014-11-04 21:49:08
【问题描述】:

我有一个作为邻接列表列表的有向图 G:

newtype Graph Int = Graph [(Int, [Int])]

G 有 n 个顶点和 m 个边。我正在尝试在 Haskell 中实现在 O(m) 时间内运行的 BFS 算法(可能已摊销),但我能够提出在 O(m * log n) 中运行并使用来自Data.Map 的数据结构的最佳解决方案模块。

我对线性解决方案的想法如下:使用来自 Data.Sequence 的结构作为高效的 FIFO 队列并按照命令式 BFS 执行所有操作,但我被困在必须将节点标记为已访问的点上。

我的问题是:是否可以在 O(m) 中运行的 Haskell(或任何其他纯函数式语言)中实现 BFS?如果不是,你可以用什么论据来证明这种说法?

【问题讨论】:

  • 您是在问这样的算法是否可行,还是特别想在 Haskell 中实现它?
  • @ScottHunter:我在考虑任何纯粹的函数式语言(我编辑了我的问题)
  • 您在实现O(1) 队列时遇到问题吗?
  • 结构树的惰性 BFS 队列是 easy(另见 this answer with the cycle detection)。

标签: algorithm haskell functional-programming


【解决方案1】:

我假设你的问题是你无法实现一个好的队列。

看看Data.Sequence - 它应该适用于双端队列,因为在序列末尾的操作非常快。在任一端添加一个元素是O(1),从任一端删除一个元素是O(1)

一旦有了队列,它的性能应该和 DFS 一样好。

您可以使用Vector Int [Int] 代替Map Int [Int](如果您的顶点是从1n 的整数)

要将节点标记为已检查,您可以使用IntSet

这应该让你O(V + E)

bfs :: V.Vector [Int] -> Int -> [Int]
bfs graph start = go IS.empty graph $ S.singleton start

go :: IS.IntSet Int -> V.Vector [Int] -> S.Sequence Int -> [Int]
go seen graph queue = 
  case S.viewL queue of
    S.EmptyL -> []
    vertex S.:< rest = vertex:(go seen' graph queue')
      where neighbors = filter (not . IS.member seen) (graph V.! vertex)
            seen' = S.insert vertex seen
            queue' = queue S.>< S.fromList neighbors

请注意,我们构建此列表的方式完全是懒惰的!因此,如果您只需要例如 BFS 的前半部分,则它不会计算其余部分。

【讨论】:

  • 实现 0(1) 队列不是问题,因为正如您所说,它已经在标准库中。我在将节点标记为已访问时遇到问题。
  • @KCH 嗯,让我看看SetHashSet 的表现。显然,布尔的 mvector 也可以工作
  • @KCH IntSet 应该可以满足您的需求,它基本上具有恒定的时间操作,因为它们的上限为 Int 中的位数
  • 谢谢你提到这个结构,我不知道。解决了实际实现的问题,但理论上的复杂度还是O(m * log n)。
  • @KCH 哎呀,链接到旧版本的文档。看来这是O(1):hackage.haskell.org/package/bitset-1.4.8/docs/…。还要记住,当您开始考虑以位为单位的整数长度作为参数时,您将陷入困境。你认为添加两个Ints 是O(1) 还是O(log n)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-24
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 2017-02-23
相关资源
最近更新 更多