我几天前开始学习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时整个网站冻结。
很抱歉,问题的级别太低了。
答案 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.输入。
答案 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并计算其力量。