【问题标题】:Haskell dynamic program efficiencyHaskell 动态程序效率
【发布时间】:2021-02-16 21:05:17
【问题描述】:

我正在学习更多关于动态编程的知识,并试图在 haskell 中实现它。我正在用不同的方法编写算法进行测试,发现一种比另一种更快。这是斐波那契问题

fib1 :: [Integer]
fib1 = 0:1:zipWith (+) fib1 (tail fib1)

fib2 :: [Integer]
fib2 = 0:1:[(fib2 !! (n-1)) + (fib2 !! (n-2)) | n <- [2..]]

fib1 比 fib2 快得多,但我不知道为什么。 fib2 看起来很直观,第 n 个数字是 (n-1)st 加上 (n-2)nd。 我得到了 fib1,但看起来它每次都在压缩整个列表,所以不会花费更长的时间。只是计算下一个索引?

【问题讨论】:

  • 计算fib2 !! n的成本随n线性增长。这是一个链表而不是向量。顺便说一句,最近关于斐波那契的问题有一些指示:SO_q66180076
  • 我意识到 fib2 !由于链表,n 是线性的。但是fib1呢?那不也是链表吗?
  • @Ro-Bert 是的,但是每次fib1 被要求生成另一个数字时,它只需要做 O(1) 的工作,而不是像 fib2 这样的 O(n)。 (显然,加上 Integer 添加所需的时间,但这对于两种实现来说都是一样的。)
  • 好的。我想我有点预料到会发生这种情况。我知道 fib2 每次都有 O(n),所以它实际上是 O(n^2)。那么我不正确理解 zipWith 吗?我觉得这也应该是 O(n)。除非它不像 for 循环那样遍历列表。
  • 其实这帮了大忙。我在想 zipWith 也是线性的。但这有助于我进行研究。我发现这个线程stackoverflow.com/questions/55291798/… 很好地解释了它。

标签: haskell dynamic fibonacci


【解决方案1】:

Haskell 中的列表是惰性的。它们在被使用时被计算,但不是更进一步。

函数 fib1 确实计算了整个列表,但只计算一次,并且只计算到您要求的索引。

函数 fib2 做了很多额外的工作:它可能会多次计算元素。

试着用笔和纸来做。例如,在 fib2 的情况下!! 5,列表需要扩展到索引5。计算fib2! 0 和 fib2 ! 1 需要很少的时间,因为它们是常数。下一个元素,fib2 ! 2 是通过添加 fib2 来计算的! 0 和 fib2 ! 1,然后是 fib2 ! 3 = fib2 !! 1 + fib2 !! 2,以此类推。

但是。

这里要注意的最重要的一点是,编译器和/或运行时不记忆 fib2 函数,这意味着:它不记得以前的计算。因此,每次代码遇到fib2 !! n 时,它都会重新开始计算,无论之前已经完成了多少次,即使这发生在同一个(递归)函数调用中。

关于计算效率,你的 fib2 实现等价于:

fib3' :: Integer -> Integer
fib3' 0 = 0
fib3' 1 = 1
fib3' n = fib3' (n - 2) + fib3' (n - 1)

fib3 :: [Integer]
fib3 = [fib3' n | n <- [0..]]

同样的效率低下,我只是将列表部分排除在外。

另一方面,fib1 利用之前的计算,通过使用它们来避免重新计算它们。这就是动态编程背后的核心思想:使用可用于存储和检索先前计算结果的数据结构,以将可能昂贵的递归函数调用交换为 - 非常 - 非常便宜的查找。

【讨论】:

    【解决方案2】:

    @netom 抱歉,但我不认为这是正在发生的事情。我按时进行了一些测试,计算出第 10000 个数字需要 0.7 秒。在同一次运行中,可以立即计算出第 10000 + 9999(第 10001 个数字),表明它被记住了。

    然后我测试了新计算第 10001 个所需的时间,计算第 10001 个所需的时间与计算第 10000 个并记住所有其余部分的时间相同。要计算第 10001 个,它不会计算 10000 和 9999(在单独的递归中),它的行为就像你期望的那样,如果它只是索引了记住的列表。

    然而,递归函数需要几乎两倍的时间!所以他们都正确地使用了动态编程。但正如我发现的那样,fib2 每一步都需要 O(n) 才能访问数组,但 fib1 每一步都需要 O(1) 压缩它。

    【讨论】:

      猜你喜欢
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-25
      • 2023-03-03
      • 2015-03-08
      • 2018-12-29
      • 1970-01-01
      相关资源
      最近更新 更多