对于n达到10^19的情况下,第N个斐波那契数是多少?

6

我正在尝试编写一个程序,以查找 1 < n < 10^19 的第n个斐波那契数。

以下是使用动态规划的代码。

memo = {}
def fib(n):
    if n in memo:
        return memo[n]
    if n <= 2:
        f = 1
    else:
        f = fib(n-1) + fib(n-2)
    memo[n]=f
    return f
print fib(input()) % 1000000007

我的代码似乎无法处理大数字。我收到了无效响应错误。有什么建议吗?

它有什么问题? - Reinstate Monica -- notmaynard
4
你的递归深度将会达到限制。https://dev59.com/X3A75IYBdhLWcg3wYYBQ - Cory Kramer
2
除了创建堆栈溢出的问题外,您可能希望仅存储最后两个斐波那契数,以便不会创建一个包含10^19个巨大整数的数组。此外,可能要查看类似gmpy2的多精度整数库。 - Linuxios
在我看来,对于斐波那契数列来说,要得到n等于10的19次方是不可能的。我所见过的最高的是2 x 10的6次方。你有任何想法,Fib(10的19次方)会有多少位数字吗?你可以用黄金分割数来近似计算。 - Stefan Gruenwald
@MehdiAlali:除非你有大约10^19个G的内存来存储Fib(10^19),否则你可能需要考虑另一种方法。尝试在整个过程中跟踪减法模1000000007,而不是实际的斐波那契数。你还可以研究Fib(2n)和Fib(2n+1)的公式,这些公式用Fib(n)和Fib(n+1)表示(例如)。 - Mark Dickinson
显示剩余3条评论
4个回答

11
当N为10^19时,如果你采用朴素的方式获取第N个斐波那契数是行不通的(至少我猜测它不会行得通)。
有一种更好的方法来做这件事。而且这种技巧适用于许多类似的序列。它被称为Fibonacci Q Matrix

enter image description here

在哪里

enter image description here

请这样想:

您有一个将向量A转换为B的矩阵:

enter image description here

填写这些条目很容易。特别之处在于,现在这是一个矩阵运算符,因此如果我们想要第1000个斐波那契数,我们只需要进行矩阵乘法即可。
您可以使用循环来完成这个任务,但是要达到10^19会花费很长时间,即使它们很小,进行10^19次矩阵乘法也需要相当长的时间。
相反,我们采用另一种快捷方式。x^N可以重写为幂的乘积,其中它们的总和为N,即:
x**100 == x**90 * x**10

所以目的是在不进行大量计算的情况下获得指数中的大数: x**2x*x 一样困难 - 它们需要相同的时间。但是,x*x*x*x 给出与 (x**2)**2 相同的答案,同时需要额外进行一次乘法。随着幂次数的增加,收益会更多。因此,如果将指数拆分为2的幂(任何幂都可以,但这是最简单的情况),
X**100 == X**64 * X**32 * X**4

X**100 == (((((X**2)**2)**2)**2)**2)**2 + ...

所以你需要做的是,计算出达到目标总功率的二次幂,然后取这些二次幂的乘积作为Q矩阵的结果。
这对我来说似乎行得通:
fib_matrix = [[1,1],
              [1,0]]

def matrix_square(A, mod):
    return mat_mult(A,A,mod)


def mat_mult(A,B, mod):
  if mod is not None:
    return [[(A[0][0]*B[0][0] + A[0][1]*B[1][0])%mod, (A[0][0]*B[0][1] + A[0][1]*B[1][1])%mod],
            [(A[1][0]*B[0][0] + A[1][1]*B[1][0])%mod, (A[1][0]*B[0][1] + A[1][1]*B[1][1])%mod]]


def matrix_pow(M, power, mod):
    #Special definition for power=0:
    if power <= 0:
      return M

    powers =  list(reversed([True if i=="1" else False for i in bin(power)[2:]])) #Order is 1,2,4,8,16,...

    matrices = [None for _ in powers]
    matrices[0] = M

    for i in range(1,len(powers)):
        matrices[i] = matrix_square(matrices[i-1], mod)


    result = None

    for matrix, power in zip(matrices, powers):
        if power:
            if result is None:
                result = matrix
            else:
                result = mat_mult(result, matrix, mod)

    return result

print matrix_pow(fib_matrix, 10**19, 1000000007)[0][1]

然后,您可以进一步采取措施 - 它只是一个2x2矩阵,因此我们可以对其进行对角化,然后得到第n个斐波那契数的公式,仅作为n的函数 - 没有递归。就像这样:
与上述类似,我们计算将我们从一步带到下一步的矩阵:

enter image description here

然后是从一个数字集合到另一个数字集合的关系:

enter image description here

我们可以在这里链式进行矩阵乘法:

enter image description here

如果没有任何限制,我们可以一直回溯到第一个斐波那契数。

enter image description here

现在游戏变成了“如何将那个矩阵乘以n次方”的问题——这正是上面代码中所做的。但是比我提出的解决方案更好的方法是,我们可以将Q矩阵分解为特征值和向量,并将其写成如下形式:

enter image description here

其中U是包含Q的特征值的酉矩阵,Λ是相应特征值的矩阵。这些特征值和特征向量为:

enter image description here

enter image description here

然后,您可以利用这种分解风格的标准优势之一,将其提升到幂次方时,相邻的U矩阵及其逆矩阵组合在一起形成幺正矩阵,留下单个U及其逆矩阵在两端,并在中间形成一系列对角矩阵,对这些矩阵进行乘方运算非常容易:

enter image description here enter image description here enter image description here

现在,我们已经拥有所有需要的内容来仅通过一个公式写出第 n 个斐波那契数,无需使用递归。不过我会在明天/本周晚些时候完成它...


如果你是认真地在做这个的话,那么你应该对矩阵进行对角化处理 - 这样你就可以轻松地将其乘以任意次幂了。 - will
嘿@will,这个对于斐波那契数列帮助很大。但是有点离题,但我希望你能帮忙 - 我有一个整数序列,其中2n和2n + 1项具有自定义定义的公式。你知道我是否可以像斐波那契数列一样以类似的方式解决问题,并为自定义序列制作类似的Q矩阵吗?谢谢! - alecxe
1
递归关系是什么?如果偏移量固定(即它是一个常数递归序列),那么您总是可以构造这个矩阵(它的大小只是变化)。如果它是相对的(即第4个是第4/2=2和4/2+1=3的函数,第20个是第10个和第11个的函数等),那么您不能 - 但仍然有更容易获得解决方案的方法 - 发布问题。 - will
供阅读者参考,如果您选择采用对角化方法,那么您可以直接得出第n个斐波那契数的解析非递归公式。 - will

9

在O(n)的效率下,你永远无法达到目标。虽然这不是特定的与代码相关,但Dijkstra's note "In honor of Fibonacci"描述了一种以O(log(n))的效率找到F(n)的方法。

F(2n-1) = F(n-1)^2 + F(n)^2

F(2n) = (2*F(n-1)+F(n))*F(n)

你不仅可以实现这个方法,而且还可以通过递归来实现。


1
+1,尽管这个公式对于直接计算n达到10^19F(n)仍然是无望的。(在这里没有任何公式可以奏效:结果太大而无法存储。)但与模1000000007的约简相结合,这将奏效。 - Mark Dickinson
@Mark Dickinson:在log(n)的复杂度下,我认为这个公式大约需要50次迭代才能得出结果,是因为需要计算太多的附属值吗? - user447688
@JohnPirie:我想他只是在指出Fib(10 ^ 19)〜2.2041233236015342e + 2089876402499787337,所以除非我们进行缩减,否则我们就完了。 :-) - DSM
@DSM:啊,那么一个简单的估计就会同样有效;谢谢。 - user447688
@JohnPirie:是的,就像DSM所说的那样。虽然OP没有直接说明,但看起来他/她实际上想要的是F(n)1000000007取模后的结果,而不是F(n)本身。(听起来像是一个典型的Project-Euler风格的挑战问题,而不是现实世界中的计算。) - Mark Dickinson

2

Python默认的递归限制是1000(通常情况下)。要查找您系统上的确切限制,请执行以下操作:

>>> import sys
>>> sys.getrecursionlimit()

首先,如果你想要递归编写代码,并且你使用的是Python 3.2及以上版本(从你的print语句来看,好像不是),那么你可以这样使用@functools.lru_cache(maxsize=128, typed=False)

import functools

@functools.lru_cache()
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n-1) + fib(n-2)

尽管如此,对于大数字来说这仍然不够快。更好的方法是编写迭代解决方案,并且你只需要在任何时候“记忆化”最后2个数字。

当然,你可以使用矩阵形式以获得更好的性能。

最终,对于n达到10**19这么大的数字,你很难在Python中编写任何运行而不会导致OverflowError的代码。


2
原帖描述得不是很清楚,但我相信原帖中的“% 1000000007”提示我们只需要对答案取模1000000007。矩阵形式(或者您喜欢的约简公式)可能仍然是必要的,因为您无法对上限进行约10^19次迭代。 - DSM
@DSM,你可以通过避免进行迭代来实现。有一种更高效的方法来计算斐波那契数列。 - will
@will:我不确定你的意思,因为我刚刚说过迭代是不可能的。使用矩阵乘法或等效的约简公式(就像我刚刚做的那样——我看到John Pirie刚刚发布了),我可以在大约190纳秒内得到正确的答案。 - DSM
@DSM 我刚打算写一个类似这样的答案 :-/ - will
@DSM,我没有仔细阅读你写的内容。我同意你的观点。 - will
显示剩余4条评论

2

我认为你无法使用这种方法达到1E19,但是以下是避免双重溢出和递归深度限制的方法:

import decimal
import operator


def decimal_range(start, stop, step=1):
    """Provides an alternative to `xrange` for very high numbers."""
    proceed = operator.lt
    while proceed(start, stop):
        yield start
        start += step


def fib(n):
    """
    Computes Fibonacci numbers using decimal.Decimal for high 
    precision and without recursion
    """
    a, b = decimal.Decimal(0), decimal.Decimal(1)
    for i in decimal_range(0, n):
        a, b = b, a + b
    return a

在我的电脑上,计算1E6需要26.5秒,但我不能保证结果的准确性:

In [26]: %time f2(n)
CPU times: user 26.4 s, sys: 130 ms, total: 26.5 s
Wall time: 26.5 s
Out[26]: Decimal('1.953282128707757731632014830E+208987')

迭代器取自这个SO线程,只做了最小的修改,而fib函数可以在另一个线程中找到


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接