【问题标题】:Why is this simple O(n) Haskell algorithm behaving more like O(2^n)? [duplicate]为什么这个简单的 O(n) Haskell 算法表现得更像 O(2^n)? [复制]
【发布时间】:2016-01-25 05:16:38
【问题描述】:

Haskell 缓存纯函数调用的结果,这是区分纯行为和不纯行为的众多原因之一。然而,这个函数应该在 O(n) 中运行,其中 n 是下面的50,运行速度非常慢:

lucas 1 = 1
lucas 2 = 3
lucas n = lucas (n-1) + lucas (n-2)
map (lucas) [1..50]

前 30 项左右一起计算不到一秒,然后 31 需要半秒左右,32 需要一整秒,33 需要几秒,34 需要 6 秒,35 需要 11 秒,36 需要 17秒...

为什么这个功能这么慢?如果 GHC 交互式运行时正在缓存结果,那么对 lucas 的每次调用应该只涉及对前两个缓存项的求和。一次加法运算可找到第 3 项,一次加法运算可找到第 4 项,一次加法运算可找到第 5 项,因此在考虑缓存的情况下,总共只有 48 次加法即可达到第 50 项。该函数不应该花费任何时间来找到至少前几千个术语,为什么性能如此糟糕?

我已经等待lucas 500 计算半个多小时了。

【问题讨论】:

  • 没有缓存 - 至少不是你期望的那样 - 这将与任何其他语言的行为相同
  • 行为不是O(n^2),而是O(2^n)
  • @WillemVanOnsem 哈哈哈,我看我有点太乐观了
  • 无论如何我提到的两个版本都应该在几秒钟内为您提供前 500 个数字(基本上是在屏幕上打印它们所需的时间);)
  • 顺便说一句,您自信地说“Haskell 缓存纯函数调用的结果”。为什么?那里有我们可以纠正的来源吗?很多人似乎都有这种误解......

标签: algorithm haskell caching big-o time-complexity


【解决方案1】:

您的版本非常慢的原因是lucas (n-1)lucas (n-2) 部分没有memoization - 所以这两个值将一遍又一遍地重新计算(递归) - 这是慢得令人痛苦。

解决方案是将计算值保存在某处:

使用列表惰性

这是一个简单的版本,它的作用与您的 code-sn-p 相同,但应该更快 - 它会将已经计算的部分保留在列表本身中:

lucasNumbers :: [Integer]
lucasNumbers = 1:3:zipWith (+) lucasNumbers (tail lucasNumbers)

first50 :: [Integer]
first50 = take 50 lucasNumbers

这样更快的原因是现在列表的惰性可以帮助你记忆不同的部分

如果您寻找Fibonacci sequences in Haskell,您可以了解很多相关信息(它真的和您的一样;))

使用unfoldr

另一种(可能不那么magic看起来)方法是使用Data.List.unfoldr - 这里已经计算的部分(或重要的部分 - 最后一个和倒数第二个元素)将是在 state 中,您为 unfold 操作传递:

lucasNumbers :: [Integer]
lucasNumbers = unfoldr (\ (n,n') -> Just (n, (n',n+n'))) (1,3)

您的评论/问题:

假设您在谈论x(n) = x(n-1)^2-2,那么您可以这样做:

lucasLehmer :: [Integer]
lucasLehmer = 4 : map (\ x -> x^2-2) lucasLehmer

会产生这样的结果:

λ> take 5 lucasLehmer
[4,14,194,37634,1416317954]

也许你应该自己试试unfoldr 版本

【讨论】:

  • 如何使用仅适用于前一项的列表操作来执行此操作,例如 Lucas-Lehmer 数,其中第一项是 4,并且每个后续项等于前一项的平方小二?
  • 在这种情况下更容易。您可以使用 unfoldr 来实现,但 iterate 正是为此目的而编写的。
  • @amalloy 是的,当然(对于lucasLehmer 序列)这是真的……这可能还有其他数百种方法;)-我不想添加另一种方法(这将不适用于原始问题 - 或者不容易 - 无论如何)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
  • 1970-01-01
  • 2020-12-16
  • 1970-01-01
相关资源
最近更新 更多