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

斐波那契数的矩阵形式

\[\begin{align*} \begin{bmatrix} F_n\\ F_{n-1}\\ \end{bmatrix}&= \begin{bmatrix} 1&1\\ 1&0\\ \end{bmatrix}\cdot \begin{bmatrix} F_{n-1}\\ F_{n-2}\\ \end{bmatrix}\\ &=\begin{bmatrix} 1&1\\ 1&0\\ \end{bmatrix}^{n-1}\cdot \begin{bmatrix} F_1\\ F_0\\ \end{bmatrix}\\ \end{align*} \]

对于一个整数 \(n\),可以写为二进制 \(n=\sum_{i=0}^{\lfloor\lg n\rfloor}2^in_i\),其中 \(n_i\)\(n\) 的二进制的各位数,\(n_i\in\lbrace0,1\rbrace\)。于是把上述公式写为

\[\begin{align*} \begin{bmatrix} F_n\\ F_{n-1}\\ \end{bmatrix}&\xlongequal{m\leftarrow n-1}\prod_{i=0}^{\lfloor\lg m\rfloor} \begin{bmatrix} 1&1\\ 1&0\\ \end{bmatrix}^{2^im_i}\cdot \begin{bmatrix} F_1\\ F_0\\ \end{bmatrix}\\ \end{align*} \]

可以先把 \(\begin{bmatrix}1&1\\1&0\\\end{bmatrix}^{2^i}\) 计算出来,再按照 \(m_i\) 相乘即可。

def fib1(n, dtype=sympy.Integer):
    if n == 0:
        return 0

    # n_i
    n -= 1  # 公式中的 n-1
    bin_n = bin(n)[2:]
    bool_n = list(map(bool, map(int, bin_n)))  # 转为 bool 数组,方便 numpy 索引
    bool_n.reverse()

    # [[1,1],[1,0]]^{2^i}
    m = [np.array([1, 1, 1, 0], dtype=dtype).reshape(2, 2)]
    for _ in range(len(bool_n) - 1):
        t = m[-1]
        m.append(t @ t)

    # produce
    needed = np.array(m)[bool_n]
    result = reduce(np.ndarray.__matmul__, needed, np.eye(2, dtype=dtype))  # 连续矩阵乘法
    return result[0, 0]

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

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


在我前面的文章算法导论 Introduction to Algorithms #算法基础中,也证明了 \(F_i = \frac{\phi^i-\hat\phi^i}{\sqrt5}\)$,其中 $$\phi = \frac{1+\sqrt5}{2}, \hat\phi = \frac{1-\sqrt5}{2}$

def fib2(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, 16))
f = [fib1, fib2, fibonacci]
t = [[] for _ in range(3)]

for i in n:
    times = 2 ** i
    for j in range(3):
        a = %timeit -o f[j](times)
        t[j].append(a.average)

plt.figure(figsize=(16, 3))
for j in range(3):
    plt.subplot(1, 3, j + 1)
    plt.plot(n, t[j], label=f[j].__name__)
    plt.legend()
plt.show()

index

虽然用公式 \(F_i = \frac{\phi^i-\hat\phi^i}{\sqrt5}\) 看上去简洁明了,但是由于求指数没有经过优化、比较花时间。

sympy 内置的 fibonacci() 实际上调用了 mpmath.libmp 库中的 ifib() 函数,是一个相当底层的库,所以很快。

原文地址:http://www.cnblogs.com/violeshnv/p/16928787.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性