Generate...|

园龄:粉丝:关注:

📂算法
🔖算法
2022-11-27 10:42阅读: 65评论: 0推荐: 0

斐波那契数列的矩阵算法及 python 实现

import numpy as np
import matplotlib.pyplot as plt
from functools import reduce
from sympy import sqrt, simplify, fibonacci
import sympy

矩阵算法

斐波那契数的矩阵形式

[FnFn1]=[1110][Fn1Fn2]=[1110]n1[F1F0]

对于一个整数 n,可以写为二进制 n=i=0lgn2ini,其中 nin 的二进制的各位数,ni{0,1}。于是把上述公式写为

[FnFn1]=mn1i=0lgm[1110]2imi[F1F0]

可以先把 [1110]2i 计算出来,再按照 mi 相乘即可。

def fib1(n, dtype=sympy.Integer):
if n == 0:
return sympy.Integer(0)
n -= 1
bin_n = bin(n)[2:]
bool_n = list(map(bool, map(int, bin_n)))
bool_n.reverse()
m = [np.array([1, 1, 1, 0], dtype=dtype).reshape(2, 2)]
for _ in range(len(bool_n) - 1):
t = m[-1]
m.append(np.matmul(t, t))
needed = np.array(m)[bool_n]
result = reduce(np.matmul, needed, np.eye(2, dtype=dtype))
return result[0, 0]

采用 sympy.Integer 作为数据类型可以防止溢出。

分析时空复杂度:m 的长度为 lg(n1)+1,需要进行 lg(n1)2×2 的矩阵乘法;needed 的长度由 n-1 的二进制中的 1 的个数决定,但也不会超过 m 的长度。故时空复杂度约为 O(lg(n1)+1)=O(lgn)

但是考虑到使用大数字 sympy.Integer 进行乘法有与数字大小相关的开销。一般来说,这个开销约为 nα,其中 α 约为 1.52n 为相乘的两个数字的位数之和。

Fi=ϕiϕ^i5,其中 ϕ=1+52,ϕ^=152,考虑到 |ϕ^|5<12,得到 Fi=ϕi5+12,即斐波那契数以指数形式增长。

m[i]=[F2i+1F2iF2iF2i1]m[i]m[i]=[F2i+12+F2i2F2i+1F2i+F2iF2i1F2i+1F2i+F2iF2i1F2i2+F2i12]T(m[i]m[i])5cnα, where n=2lgF2i=2lgϕ2i5+122i+1lgϕlg55c(2i+1lgϕ)αT(n)=i=0lg(n1)1T(m[i]m[i])5clgαϕi=0lg(n1)12α(i+1)=c(2α(lg(n1)+1)1)=Θ(2αlgn)=Θ(nα)

因此实际上的时间复杂度为 O(nα),这个 α 和选择的大数字乘法算法有关。


将数字转为二进制和计算矩阵的过程融合。

def fib2(n, dtype=sympy.Integer):
m = np.array([1, 1, 1, 0], dtype=dtype).reshape(2, 2)
result = np.eye(2, dtype=dtype)
while n:
if n & 1:
result = result @ m
m = m @ m
n >>= 1
return result[1, 0]

也可以不采用矩阵的形式。这表示我们要保存上述代码中 mresult 的值。

考虑矩阵

[1110][a+baab]=[2a+ba+ba+ba]=aa+b,ba[a+baab]

所以形如 [a+baab] 的对称矩阵与 [1110] 相乘仍然是对称矩阵。

[p+qppq][p+qppq]=[(p+q)2+p2p2+2pqp2+2pqp2+q2]=pp2+2pq,qp2+q2[p+qppq][p+qppq][a+baab]=[(p+q)(a+b)+apa(p+q)+bpa(p+q)+bpap+bq]=aa(p+q)+bp,bap+bq[a+baab]

因此由于矩阵的特殊性,对于 m 我们只需要保存两个变量 p,q,对于 result 也只需要保存两个变量 a,b

初始时有 m=[1110],result=[1001],所以初始化 a=0,b=1,p=1,q=0

def fib3(n, dtype=sympy.Integer):
one, zero = dtype(1), dtype(0)
a, b, p, q = zero, one, one, zero
while n:
if n & 1:
ap = a * p
a, b = ap + a*q + b*p, ap + b*q
pp = p * p
p, q = pp + 2*p*q, pp + q*q
n >>= 1
return a

通项公式

在我前面的文章算法导论 Introduction to Algorithms #算法基础中,也证明了 Fi=ϕiϕ^i5,其中 ϕ=1+52,ϕ^=152

def fib4(n):
p = (1 + sqrt(5)) / 2
q = (1 - sqrt(5)) / 2
return simplify((p ** n - q ** n) / sqrt(5))

测试

尝试用 Jupyter Notebook 测试算法速度,其中 fibonacci()sympy 自带的斐波那契函数,三者返回的结果都为 sympy.Integer

n = list(range(8, 17))
f = [fib1, fib2, fib3, fib4, fibonacci]
t = [[] for _ in range(len(f))]
for i in n:
times = 2 ** i
for j in range(len(f)):
a = %timeit -o f[j](times)
t[j].append(a.average)
plt.figure(figsize=(12, 8))
for j in range(len(f)):
plt.subplot(2, 3, j + 1)
plt.plot(n, t[j], label=f[j].__name__)
plt.legend()
plt.show()

虽然用公式 Fi=ϕiϕ^i5 看上去简洁明了,但是由于求指数没有经过优化、比较花时间。

sympy 内置的 fibonacci() 实际上调用了 mpmath.libmp 库中的 ifib() 函数。在计算的时候使用的数据类型不是 sympy.Integer,而是其他更接近底层的数据类型,所以速度会更快。

使用 np.log2()lg,再使用 np.polyfit(n, t, 1) 进行直线拟合,得到曲线和系数

index3

得到 α1.58,符合前面的时间复杂度的计算。

如果使用 np.int32 作为数据类型,那么就没有大数字的开销了,曲线为对数。

index2

本文作者:violeshnv

本文链接:https://www.cnblogs.com/violeshnv/p/16929119.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Violeshnv  阅读(65)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 とおいよびごえ 凋叶棕
  2. 2 かぜのねいろ 凋叶棕
  3. 3 Milky Way Train 流派未階堂
  4. 4 nostalgia 流派未階堂
  5. 5 桜花繚乱 はちみつれもん
  6. 6 胡蝶之夢 はちみつれもん
  7. 7 色は散りゆく はちみつれもん
  8. 8 暮色蒼然 はちみつれもん
  9. 9 追想、桜ノ國 はちみつれもん
  10. 10 意にそぐわぬリターニー 凋叶棕
かぜのねいろ - 凋叶棕
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.