为什么计算斐波纳契数需要很长时间?

时间:2014-05-13 05:59:52

标签: ocaml

我几天前开始学习Ocaml。我试图制作一个fibonaaci数字程序:

  let rec fib a=
      if a=1||a=2 then 1 else fib(a-1)+fib(a-2);;

此代码不是最佳的,因为我不知道如何处理异常情况。但就目前而言,如果我尝试计算fib 50或fib 100,那么计算机需要很长时间才能进行评估。我想知道为什么,因为Ocaml应该非常快,并且添加数字显然是线性时间任务。如果我将此代码粘贴到"尝试Ocaml" (http://try.ocamlpro.com/),然后当我执行fib 50时整个网站冻结。

很抱歉,问题的级别太低了。

4 个答案:

答案 0 :(得分:16)

因为这是错误递归使用的海报示例。应该禁止使用它作为递归优雅的一个例子,因为它真的是完全不优雅的w.r.t.机器。

对于fib的每次通话,您都会收到另外两个fib来电:

depth | call tree
------+------------------------------------------------------------------------
1     | fib-----+
      | |        \
2     | fib      fib
      | |   \    |   \
3     | fib  fib fib  fib
      | ....
4     | fib fib  fib  fib fib fib  fib  fib
      | ....
5     | fib fib  fib  fib fib fib  fib  fib 
      | fib fib  fib  fib fib fib  fib  fib
      | ....
6     | fib fib  fib  fib fib fib  fib  fib  
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib  
      | fib fib  fib  fib fib fib  fib  fib
      | ....
7     | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | ....
8     | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib          
      | ....
9     | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib
      | fib fib  fib  fib fib fib  fib  fib

......并且深度为50,......

 [562,949,950,000,000] × fib

...所有并行存在,大约有5.6亿个数据包,每个数百万fib

你所拥有的不是OCaml问题,而是计算/算法问题。对于不希望“简单”递归的情况,Fibonacci数是一个很好的例子。它看起来简单而优雅,但否认现实。

所以,将其作为一个循环来实现,你的问题就会消失。

复杂性

这是指出计算复杂性(Big O Notation)的一个很好的问题。您的实现具有指数复杂度 O(2 n ,即运行算法所需的时间以指数方式增长w.r.t.对输入。求和循环将在 O(n)中运行,即解决问题所需的时间将线性增长w.r.t.输入。

另见Algorithm function for fibonacci series

答案 1 :(得分:4)

phresnel是绝对正确的,斐波纳契函数的通常递归定义是一种非常差的实际计算方法。每次想要在结果中添加1时,很容易看到至少有一个递归调用。由于Fibonacci函数是指数函数(大约1.6 ^ n),因此必须进行指数级的递归调用。

这是一个相当快速的递归Fibonacci函数:

let fib n =
    let rec ifib i a b =
        if i = n then b
        else ifib (i + 1) b (a + b)
    in
    ifib 0 0 1

答案 2 :(得分:3)

你写过了臭名昭着的双递归Fibonacci函数。因为每次调用都有两个递归调用,所以调用总数随着递归级别的数量呈指数增长。

处理这个的常用方法是重写函数以不需要递归的方式计算(对于Fibonacci,你可以从1开始然后向上,而不是向下到a-1并且a-2,以便您在需要时已经拥有系列中较早的值),或使用memoization缓存值,这样您就不需要多次计算相同的值。一旦计算出fib 23,任何后续调用只会将其从缓存中拉出而不是再次计算。

然而,Fibonacci序列还有​​一个closed form(在该链接上搜索“封闭形式”)与Φ相关,黄金比例:

let round f =
    int_of_float (f +. 0.5)

let phi = (1. +. (sqrt 5.)) /. 2.

let fibf n =
    (phi ** n) /. (sqrt 5.)

let fib n =
    round (fibf n)

此实现直接计算Fibonacci序列的值,不进行迭代或递归。但是,它受浮点精度的限制。例如,它不适合作为迭代解决方案的替代品,该解决方案旨在继续进入bignums领域。

答案 3 :(得分:2)

为了完整起见,这里是斐波那契的对数版本:

(* exponentiation by squaring *)
let rec pow e ( * ) x = function
| 0 -> e
| 1 -> x
| n ->
  let r = pow e ( * ) x (n/2) in
  if n mod 2 = 0 then r * r else r * r * x

(* matrix product *)
let ( ** ) a b = 
  assert
    (Array.length a = Array.length a.(0)
  && Array.length b = Array.length b.(0)
  && Array.length a = Array.length b);
  let n = Array.length a in
  let c = Array.make_matrix n n 0 in
  for i = 0 to n-1 do
    for j = 0 to n-1 do
      for k = 0 to n-1 do
        c.(i).(j) <- c.(i).(j) + a.(i).(k) * b.(k).(j)
      done;
    done;
  done;
  c

let id = [|[|1; 0|]; [|0; 1|]|]

(* companion matrix for fibonacci *)
let f0 = [|[|0; 1|]; [|1; 1|]|]

let fibo n = (pow id ( ** ) f0 n).(1).(1)

它的工作原理是companion matrix并计算其力量。