次の関数は、末尾再帰と 2 乗によってフィボナッチ数列を計算します。
(defun fib1 (n &optional (a 1) (b 0) (p 0) (q 1))
(cond ((zerop n) b)
((evenp n)
(fib1 (/ n 2)
a
b
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q))))
(t
(fib1 (1- n)
(+ (* b q) (* a (+ p q)))
(+ (* b p) (* a q))
p
q))))
基本的に、すべての奇数入力を偶数入力に減らし、すべての偶数入力を半分に減らします。例えば、
F(21)
= F(21 1 0 0 1)
= F(20 1 1 0 1)
= F(10 1 1 1 1)
= F(5 1 1 2 3)
= F(4 8 5 2 3)
= F(2 8 5 13 21)
= F(1 8 5 610 987)
= F(0 17711 10946 610 987)
= 10946
これを見たとき、偶数と奇数を組み合わせたほうがいいのではないかと思ったので(奇数マイナス1 =偶数なので)、
(defun fib2 (n &optional (a 1) (b 0) (p 0) (q 1))
(if (zerop n) b
(fib2 (ash n -1)
(if (evenp n) a (+ (* b q) (* a (+ p q))))
(if (evenp n) b (+ (* b p) (* a q)))
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q)))))
上記の方程式が次のようになるため、これにより高速になることを願っています
F(21)
= F(21 1 0 0 1)
= F(10 1 1 1 1)
= F(5 1 1 2 3)
= F(2 8 5 13 21)
= F(1 8 5 610 987)
= F(0 17711 10946 1346269 2178309)
= 10946
ただし、次のコードで Fib(1000000) に必要な時間を確認すると、はるかに遅いことが判明しました (Clozure CL、CLisp、Lispworks などでは約 50% 時間がかかります)。画面を数字でいっぱいにしたい。)
(time (progn (fib1 1000000)()))
(time (progn (fib2 1000000)()))
fib2 が fib1 よりも多くの eventp を実行する可能性があることしかわかりませんが、なぜそれほど遅いのでしょうか?
編集: nm は正しいと思います。数式の 2 番目のグループを編集しました。たとえば、上記の F(21) の例では、fib2 は実際には p と q で F(31) と F(32) を計算しますが、これは使用されません。したがって、F(1000000) では、fib2 は F(1048575) と F(1048576) を計算します。
遅延評価は揺るぎません。これは非常に良い点です。Common Lisp では、"and" や "or" などの一部のマクロだけが遅延評価されるのでしょうか?
次の変更された fib2 (n>0 に対して定義) は、実際にはより高速に実行されます。
(defun fib2 (n &optional (a 1) (b 0) (p 0) (q 1))
(if (= n 1) (+ (* b p) (* a q))
(fib2 (ash n -1)
(if (evenp n) a (+ (* b q) (* a (+ p q))))
(if (evenp n) b (+ (* b p) (* a q)))
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q)))))