我使用Racket(Scheme / Lisp的衍生物),我写了这个使用累加器的Fibonacci算法:
(define (fibonacci* n)
(local (; NaturalNumber NaturalNumber NaturalNumber -> NaturalNumber
; Add accumulators for current and previous fibonacci numbers
(define (fibonacci-acc x current previous)
(if (= x n) current
(fibonacci-acc (add1 x) (+ current previous) current))))
(fibonacci-acc 0 0 1)))
对于不使用累加器的函数,这是一个很大的改进,如下所示:
(define (fibonacci n)
(if (<= n 1) n
(+ (fibonacci (- n 1))
(fibonacci (- n 2)))))
但是如何设置递推方程来计算 更高效的方式呢?
答案 0 :(得分:4)
设T(n)为(fib n)
计算fib
的时间:
(define (fib n)
(if (<= n 1)
n
(+ (fib (- n 1))
(fib (- n 2)))))
由于fib
的正文具有条件(if (<= n 1) ...)
,我们需要考虑两种情况。
如果n <= 1,则评估表达式n
。它是一个变量引用,需要恒定的时间。我们将时间设置为1(时间单位)。
简而言之,我们有:
T(0) = 1
T(1) = 1
对于大于1的n,将计算表达式(+ (fib (- n 1)) (fib (- n 2)))))
。评估(fib (- n 1))
所花费的时间是T精确T(n-1)的定义。同样地,计算(fib (- n 2))))
需要时间T(n-2)。然后添加两个子表达式的结果(+ ...)
。添加两个fixnums(63位数)所需的时间与变量引用的时间大致相同。所以我们设定了添加到1的时间。我们一起得到了:
T(n) = 1 + T(n-1) + T(n-2) for n>1
这三个递推方程是:
T(0) = 1
T(1) = 1
T(n) = 1 + T(n-1) + T(n-2) for n>1
有关T:
的分析,请参阅以下文件的第8页http://users.cecs.anu.edu.au/~peter/seminars/RunningTimeInduction
通过归纳证明T(n)<=2^n
。
答案 1 :(得分:3)
第二个函数多次计算同一个函数,因此它的复杂性是指数式的(O(2^n)
,你可以得到一个更好的约束,但它在那个球场中。)
f(5)
/ \
f(3) f(4)
/ | / \
f(1) f(2) f(2) f(3)
/ \ | \
f(0) f(1) f(1) f(2)
/ \
f(0) f(1)
正如您通过绘制递归树所看到的,即使是小值,也会经常重新计算多个值。
要看到它真的是指数级的,想象f(6)
的树:它将包含这棵树,因为它调用f(5)
和f(4)
的树,所以它将是一棵树几乎是这个尺寸的两倍。
使用累加器的解决方案通过自下而上找到所需的斐波纳契数来避免这种情况,因此只计算绝对必要的数量。这使得O(n)
得到n
- 斐波纳契数。
要了解第一个算法的效率有多高,请看一下线性函数与指数函数相比如何增长:
n | 2^n
===========
1 | 2
2 | 4
3 | 8
4 | 16
5 | 32
6 | 64
所以基本上,线性确实快得多,因为你已经通过实验建立了。
答案 2 :(得分:2)
嗯,这很容易,如果你计算新数字,你只需要两个你已经知道的小数字。
每个新数字都是以恒定时间计算的。
因此,第n个斐波那契数的复杂度为O(n) - 线性。