我最近遇到了这个Haskell的memoized fibonacci实现:
fibonacci :: Int -> Integer
fibonacci = (map fib [0 ..] !!)
where fib 0 = 0
fib 1 = 1
fib n = fibonacci (n - 1) + fibonacci (n - 2)
我想知道第一次产生第n个斐波纳契数的时间复杂度。因为Haskell中的列表查找,它是O(n ^ 2)吗?如果是,那么有没有办法以某种方式使O(n)像查找操作为O(1)的语言一样?
答案 0 :(得分:3)
是否因为Haskell中的列表查找而导致O(n ^ 2)?
是
如果是,那么有没有办法以某种方式使O(n)像查找操作为O(1)的语言一样?
最简单的方法是使用具有O(1)随机访问权限的惰性数组。这意味着,您必须指定一个数组大小,因此您不再具有无限序列,但您在其他语言中具有相同的限制。例如,你可以使用Data.Vector
:
import Data.Vector
fibsUpto100 :: Vector Integer
fibsUpto100 = generate 100 fib
where fib 0 = 0
fib 1 = 1
fib n = fibsUpto100 ! (n-1) + fibsUpto100 ! (n-2)
由于懒惰,在计算向量的元素之前不会计算任何内容,此时还会评估向量的所有先前元素(之前未评估过)。一旦评估,每个值都存储在向量中,因此不会对任何内容进行多次评估。
当然,拥有无限的数字列表会更好。实现此目的的一种方法是将标准O(n)方法计算第n个Fibonacci数(使用跟踪当前和前一个元素的while循环)转换为递归Haskell函数,然后调整该方法以存储每个元素一个清单。
while循环的基本翻译是:
fib 0 = 0
fib n = fibHelper n 0 1
where
fibHelper 0 _ current = current
fibHelper n previous current =
fibHelper (n-1) current (current + previous)
调整此项以保留列表,我们得到:
fibs = 0 : genFibs 0 1
where
genFibs previous current =
current : genFibs current (current + previous)
实现相同目标的另一种更简洁的方法是使用自己的尾部定义列表。也就是说,我们希望列表中的每个元素都是前一个元素+之前的元素,我们通过获取列表及其尾部,将它们添加到一起并将结果反馈到列表中来实现。这导致以下定义:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
这里0和1分别是第一个和第二个元素,然后其余元素在zipWith (+) fibs (tail fibs)
生成的列表中。该列表的第一个元素(即整个列表的第三个元素)将是fibs
的第一个元素+ tail fibs
的第一个元素,所以0 + 1 = 1
,下一个元素将是1 + 1 = 2
等等。所以这个定义确实产生了斐波纳契数列。
¹尽管对于那些不习惯在他们头上缠上递归结的人来说可能不那么容易理解。
答案 1 :(得分:2)
这在O(n)时间内运行。
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
fibonacci = (fibs !!)
答案 2 :(得分:-3)
如果使用memoized fibonacci,时间复杂度应为O(n).
,因为在任何索引中{i} fib(i)
只计算一次。它是动态编程之美。