对数阶斐波那契数列求值
本文介绍 SICP 练习1.19的 python 实现。
问题描述
斐波那契数列是按一定规律展开的一列数,其中任意一个数等于其前两个数之和。数列从0, 1开始,前18项为
0
,
1
,
1
,
2
,
3
,
5
,
8
,
13
,
21
,
34
,
55
,
89
,
144
,
233
,
377
,
610
,
987
,
1597
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597
试编程求出数列中的第n项。
数学分析
要求数列的项,无非用通项公式或递推公式。存在斐波那契数列的通项公式和递推公式。
数学规律
直接利用递推公式迭代求解,这种解法的复杂度是线性的。但是,如果能用某种方法刻画递推的积累,将多次递推合并为一次递推,我们就可以少做计算。
这样的方法是存在的。为了表述方便,定义一些概念。首先,将斐波那契数列的递推过程看作变换 T T T应用到对偶 ( 1 , 0 ) (1,0) (1,0)的过程。注意,我们将数列递推看作对偶到对偶的递推,两两一组无重叠。然后,我们来考虑这递推过程能不能叠加积累。
直接观察 T T T的叠加,不易发现规律, T T T到 T 20 T^{20} T20作用在 ( a , b ) (a,b) (a,b)上的效果如下图:
查阅教材知,可以将
T
T
T看作变换族
T
p
q
T_{pq}
Tpq中
p
=
0
p=0
p=0且
q
=
1
q=1
q=1的特殊情况,其中
T
p
q
T_{pq}
Tpq是对于对偶
(
a
,
b
)
(a,b)
(a,b)按照
a
←
b
q
+
a
q
+
a
p
b
←
b
p
+
a
q
a\leftarrow bq+aq+ap\\b\leftarrow bp+aq
a←bq+aq+apb←bp+aq
规则的变换。应用变换
T
p
q
T_{pq}
Tpq两次,其效果等同于应用同样形式的一次变换
T
p
′
q
′
T_{p'q'}
Tp′q′,其中
p
′
=
p
2
+
q
2
q
′
=
q
2
+
2
p
q
p'=p^2+q^2\\q'=q^2+2pq
p′=p2+q2q′=q2+2pq
也就是说,
T
p
q
(
T
p
q
(
a
,
b
)
)
=
T
p
′
q
′
(
a
,
b
)
T_{pq}(T_{pq}(a,b))=T_{p'q'}(a,b)
Tpq(Tpq(a,b))=Tp′q′(a,b)
这个规律从两个方面给了我们快速计算的支持:
- 可以将两次变换合并为一次,且这两次变换本身可以是合并得来的
- 公式的成立不依赖具体的 ( a , b ) (a,b) (a,b),即变换的起点任意
斐波那契递推公式可以看作
T
01
T_{01}
T01,故我们的问题可以重新表述为
F
i
b
(
n
+
1
)
,
F
i
b
(
n
)
=
T
01
n
(
1
,
0
)
Fib(n+1),Fib(n)=T^n_{01}(1,0)
Fib(n+1),Fib(n)=T01n(1,0)
为了尽可能少的应用变换,我们需要将
n
n
n次变换两两合并,并改变起点来处理奇数次变换的合并。具体来说,我们要应用
(
T
p
q
)
n
⇔
(
T
p
′
q
′
)
n
2
(T_{pq})^n\Leftrightarrow (T_{p'q'})^{\frac{n}{2}}
(Tpq)n⇔(Tp′q′)2n
将变换次数减半。多次减半就可以降至对数级复杂度。
编程
scheme
#lang sicp
(define (fib n)
(fib-iter 1 0 0 1 n))
(define (fib-iter a b p q count)
(cond ((= count 0) b)
((even? count)
(fib-iter a
b
(+ (square p) (square q))
(+ (square q) (* 2 p q))
(/ count 2)))
(else (fib-iter (+ (* b q) (* a q) (* a p))
(+ (* b p) (* a q))
p
q
(- count 1)))))
(define (square x) (* x x))
python
def fast_fib(n):
a, b, p, q = 1, 0, 0, 1
while n != 0:
if n % 2 == 0:
p, q = p**2 + q**2, 2*p*q + q**2
n //= 2
else:
a, b = b*q + a*(q + p), b*p + a*q
n -= 1
return b
偶数次更新变换,奇数次更新起点,次数耗尽结束,此时已将起点带到终点。