基于 Donald Knuth 的 Fibonacci-by-matrix 乘法方法,由
Mostowski Collapse,但更明确。
算法可以在 module 文件和 unit tests 文件中找到on github:
该原理基于 Donald Knuth 提供的矩阵恒等式(在 Donald E. Knuth. The Art of Computer Programming. Volume 1. Fundamental
算法,第二版第80页)
对于 n >= 1 我们有(对于 n=0,单位矩阵出现在右侧,但不清楚 fib(-1) 是什么):
n
[ fib(n+1) fib(n) ] [ 1 1 ]
[ ] = [ ]
[ fib(n) fib(n-1) ] [ 1 0 ]
但是如果我们使用常量 fib(0) 和 fib(1) 而不假设它们的值分别为 0 和 1(我们可能正在使用特殊的斐波那契数列),那么我们必须规定对于 n >= 1:
n-1
[ fib(n+1) fib(n) ] [ fib(2) fib(1) ] [ 1 1 ]
[ ] = [ ] * [ ]
[ fib(n) fib(n-1) ] [ fib(1) fib(0) ] [ 1 0 ]
我们将分别计算右侧的“幂矩阵”并显式乘以“斐波那契起始矩阵”,因此:
const(fib0,0).
const(fib1,1).
fib_matrixmult(N,F) :-
N>=1,
!,
Pow is N-1,
const(fib0,Fib0),
const(fib1,Fib1),
Fib2 is Fib0+Fib1,
matrixpow(
Pow,
[[1,1],[1,0]],
PowMx),
matrixmult(
[[Fib2,Fib1],[Fib1,Fib0]],
PowMx,
[[_,F],[F,_]]).
fib_matrixmult(0,Fib0) :-
const(fib0,Fib0).
matrixpow(Pow, Mx, Result) :-
matrixpow_2(Pow, Mx, [[1,0],[0,1]], Result).
matrixpow_2(Pow, Mx, Accum, Result) :-
Pow > 0,
Pow mod 2 =:= 1,
!,
matrixmult(Mx, Accum, NewAccum),
Powm is Pow-1,
matrixpow_2(Powm, Mx, NewAccum, Result).
matrixpow_2(Pow, Mx, Accum, Result) :-
Pow > 0,
Pow mod 2 =:= 0,
!,
HalfPow is Pow div 2,
matrixmult(Mx, Mx, MxSq),
matrixpow_2(HalfPow, MxSq, Accum, Result).
matrixpow_2(0, _, Accum, Accum).
matrixmult([[A11,A12],[A21,A22]],
[[B11,B12],[B21,B22]],
[[C11,C12],[C21,C22]]) :-
C11 is A11*B11+A12*B21,
C12 is A11*B12+A12*B22,
C21 is A21*B11+A22*B21,
C22 is A21*B12+A22*B22.
如果您的起始矩阵确定为[[1,1],[1,0]],您可以将主谓词中matrixpow/3 后跟matrixmult/3 的两个操作折叠成对matrixpow/3 的单个调用。
上述算法计算“太多”,因为斐波那契数列矩阵中的两个值可以从另外两个推导出来。我们可以摆脱这种冗余。 Mostowski Collapse 提出了一个紧凑的算法来做到这一点。为便于理解,扩展如下:
我们的想法是摆脱matrixmult/3中的冗余操作,利用我们所有的矩阵都是对称的并且实际上成立的事实
斐波那契数列
[ fib(n+1) fib(n) ]
[ ]
[ fib(n) fib(n-1) ]
所以,如果我们将矩阵 A 和 B 相乘得到 C,我们总是有这种形式的东西(即使在 B 是
单位矩阵):
[ A1+A2 A1 ] [ B1+B2 B1 ] [ C1+C2 C1 ]
[ ] * [ ] = [ ]
[ A1 A2 ] [ B1 B2 ] [ C1 C2 ]
我们可以只保留每个矩阵的第二列而不会丢失
信息。这些向量之间的运算不是一些
像乘法这样的标准运算,让我们用⨝标记它:
[ A1 ] [ B1 ] [ C1 ]
[ ] ⨝ [ ] = [ ]
[ A2 ] [ B2 ] [ C2 ]
地点:
C1 = B1*(A1+A2) + B2*A1 or A1*(B1+B2) + A2*B1
C2 = A1*B1 + A2*B2
fib_matrixmult_streamlined(N,F) :-
N>=1,
!,
Pow is N-1,
const(fib0,Fib0),
const(fib1,Fib1),
matrixpow_streamlined(
Pow,
v(1,0),
PowVec),
matrixmult_streamlined(
v(Fib1,Fib0),
PowVec,
v(F,_)).
fib_matrixmult_streamlined(0,Fib0) :-
const(fib0,Fib0).
matrixpow_streamlined(Pow, Vec, Result) :-
matrixpow_streamlined_2(Pow, Vec, v(0,1), Result).
matrixpow_streamlined_2(Pow, Vec, Accum, Result) :-
Pow > 0,
Pow mod 2 =:= 1,
!,
matrixmult_streamlined(Vec, Accum, NewAccum),
Powm is Pow-1,
matrixpow_streamlined_2(Powm, Vec, NewAccum, Result).
matrixpow_streamlined_2(Pow, Vec, Accum, Result) :-
Pow > 0,
Pow mod 2 =:= 0,
!,
HalfPow is Pow div 2,
matrixmult_streamlined(Vec, Vec, VecVec),
matrixpow_streamlined_2(HalfPow, VecVec, Accum, Result).
matrixpow_streamlined_2(0, _, Accum, Accum).
matrixmult_streamlined(v(A1,A2),v(B1,B2),v(C1,C2)) :-
C1 is A1*(B1+B2) + A2*B1,
C2 is A1*B1 + A2*B2.