Fibonacci数有很简单的递归形式,Fib(n) = Fib(n-1) + Fib(n-2), n>=3,Fib(0) = 1, Fib(1) = 1。要算第n个斐波那契数真是太简单了,编程初学者都会,但是计算方法很多,都很典型,今天来复习一下。
1. 用C语言迭代实现。
int fib(int n) {
int a, b, z, i;
a = b = 1;
for (i = 3; i <= n; i++) {
z = a + b;
a = b;
b = z;
}
return z;
}
int fib2(int n) {
if (n == 1 || n == 2) {
return 1;
} else {
return fib2(n-1) + fib2(n-2);
}
}
这是树形递归,如果对于大的n效率是很慢的,左分支和右分支会重复计算相同的内容,而且这也是自顶向下的递归,也要做2^n次递归计算。
3. 尾递归的scheme实现。
(define (fib n)
(define (iter a b count)
(if (or (= count 1) (= count 2))
a
(iter (+ a b)
a
(- count 1))))
(iter 1 1 n))
这个递归实现已经类似与第一个C版本的迭代过程了,但是在方法1中要定义一个额外的变量z来保存a,b的状态,而scheme不用,这就是没有变量没有副作用的好处。
尾递归的C实现。
int fib4(int n, int a, int b) {
if (n <= 2)
return a;
return fib4(n-1, a+b, a);
}
尾递归就是用额外的变量把线性的空间复杂度降为常量空间复杂度。
4. 借用一个数组来保存状态。这是一种自底向上的方法,没有重复计算,也没有多余的函数调用开销,在形式上也是递归的,时间和空间复杂度都是theta(n),以后遇到递归问题都要想想能不能自己建数组来保存状态。
int fib3(int n) {
int a[n], i;
a[0] = a[1] = 1;
for (i = 2; i < n; i++) {
a[i] = a[i-1] + a[i-2];
}
return a[n-1];
}
这种方法是从动态规划里面学来的,如果用递归计算太慢的话就用数组来保存状态。
5. 从SICP上学来的新算法,不是直接用递归公式编程,而是跳跃式的计算,因为规模减半了,所以这种算法时间复杂度只有theta(log n)。下面来看看scheme代码吧。
(define (fib5 n)
(define (even? n)
(= (remainder n 2) 0))
(define (fib-iter a b p q count)
(cond
((= count 0) b)
((even? count)
(fib-iter a b (+ (* p p) (* q q)) (+ (* q q) (* 2 p q))
(/ count 2)))
(else
(fib-iter (+ (* b q) (* a q) (* a p))
(+ (* b p) (* a q))
p
q
(- count 1)))))
(fib-iter 1 0 0 1 n))
详细的算法说明得看SICP练习1.19。这种算法一般人真是不容易想到。