【问题标题】:How to store output of very large Fibonacci number?如何存储非常大的斐波那契数的输出?
【发布时间】:2026-01-07 16:10:01
【问题描述】:

我正在为第 n 个斐波那契数编写程序。我使用递归和记忆化制作了以下程序。 主要问题是 n 的值可以达到 10000,这意味着 10000 的斐波那契数将超过 2000 位。

通过一些谷歌搜索,我发现我可以使用数组并将解决方案的每个数字存储在数组的元素中,但我仍然无法弄清楚如何用我的程序实现这种方法。

#include<iostream>

using namespace std;

long long int memo[101000];
long long int n;
long long int fib(long long int n)
{
    if(n==1 || n==2)
        return 1;
    if(memo[n]!=0)
        return memo[n];
    return memo[n] = fib(n-1)  + fib(n-2);
}
int main()
{
    cin>>n;
    long long int ans = fib(n);
    cout<<ans;
}

我该如何实现该方法,或者是否有其他方法可用于实现如此大的值?

【问题讨论】:

  • 我处理类似问题的方法是创建“数字”类。您的方法的最佳实现可能是“字符”的向量,并且在每个字符中存储 0-9。不是最有效的,但却是最简单的。
  • 我会使用类似GMP
  • 您要查找的术语是“bignum”。
  • @naomik 像 spoj.com 这样的网站是否允许使用 GMP?
  • @VandanBhardwaj 我不知道。我给了你一个答案,我认为这将有助于指导你。

标签: c++ recursion biginteger


【解决方案1】:

我认为应该指出的一件事是,还有其他方法可以实现 fib,这对于 C++ 之类的东西来说更容易计算

考虑下面的伪代码

function fib (n) {
  let a = 0, b = 1, _;
  while (n > 0) {
    _ = a;
    a = b;
    b = b + _;
    n = n - 1;
  }
  return a;
}

这不需要记忆,您不必担心过多的递归调用会炸毁您的堆栈。递归是一个非常强大的循环结构,但它是最好留给像 Lisp、Scheme、Kotlin、Lua(和其他一些语言)这样优雅地支持它的语言。

这并不是说在 C++ 中消除尾调用是不可能的,但除非您明确地对其进行优化/编译,否则我怀疑您使用的任何编译器是否会默认支持它。

至于计算异常大的数字,您必须要么创造性地添加 The Hard Way,要么依赖于任意精度的算术库,如 GMP。我敢肯定还有其他的库。


添加 Hard Way™

还记得你小时候是如何添加大数字的吗?刚从铝箔纸上取下来?

5 岁数学

  1259601512351095520986368
+   50695640938240596831104
---------------------------
                          ?

你必须从右到左添加每一列。当一列溢出到两位数时,记得把那个 1 带到下一列。

                 ... <-001
  1259601512351095520986368
+   50695640938240596831104
---------------------------
                  ... <-472

第 10,000 个斐波那契数有数千位数长,因此无法适应 C++ 开箱即用的任何整数。因此,无需依赖库,您就可以使用字符串或一位数的数组。要输出最终数字,您必须将其转换为字符串。

(woflram alpha: fibonacci 10000)

这样做,您将执行几百万个个位数的加法;这可能需要一段时间,但对于任何现代计算机来说都应该是轻而易举的事情。是时候开始工作了!


这是 JavaScript 中 Bignum 模块的示例

const Bignum =
  { fromInt: (n = 0) =>
      n < 10
        ? [ n ]
        : [ n % 10, ...Bignum.fromInt (n / 10 >> 0) ]

  , fromString: (s = "0") =>
      Array.from (s, Number) .reverse ()

  , toString: (b) =>
      b .reverse () .join ("")  

  , add: (b1, b2) =>
    {
      const len = Math.max (b1.length, b2.length)
      let answer = []
      let carry = 0
      for (let i = 0; i < len; i = i + 1) {
        const x = b1[i] || 0
        const y = b2[i] || 0
        const sum = x + y + carry
        answer.push (sum % 10)
        carry = sum / 10 >> 0
      }
      if (carry > 0) answer.push (carry)
      return answer
    }
  }

我们可以验证上面的 Wolfram Alpha 答案是否正确

const { fromInt, toString, add } =
  Bignum

const bigfib = (n = 0) =>
{
  let a = fromInt (0)
  let b = fromInt (1)
  let _
  while (n > 0) {
    _ = a
    a = b
    b = add (b, _)
    n = n - 1
  }
  return toString (a)
}

bigfib (10000)
// "336447 ... 366875"

展开下面的程序在浏览器中运行

const Bignum =
  { fromInt: (n = 0) =>
      n < 10
        ? [ n ]
        : [ n % 10, ...Bignum.fromInt (n / 10 >> 0) ]
        
  , fromString: (s = "0") =>
      Array.from (s) .reverse ()
      
  , toString: (b) =>
      b .reverse () .join ("")  
      
  , add: (b1, b2) =>
    {
      const len = Math.max (b1.length, b2.length)
      let answer = []
      let carry = 0
      for (let i = 0; i < len; i = i + 1) {
        const x = b1[i] || 0
        const y = b2[i] || 0
        const sum = x + y + carry
        answer.push (sum % 10)
        carry = sum / 10 >> 0
      }
      if (carry > 0) answer.push (carry)
      return answer
    }
  }
  
const { fromInt, toString, add } =
  Bignum

const bigfib = (n = 0) =>
{
  let a = fromInt (0)
  let b = fromInt (1)
  let _
  while (n > 0) {
    _ = a
    a = b
    b = add (b, _)
    n = n - 1
  }
  return toString (a)
}

console.log (bigfib (10000))

【讨论】:

    【解决方案2】:

    尽量不要将递归用于像斐波那契这样的简单问题。如果您只使用一次,请不要使用数组来存储所有结果。包含 2 个先前斐波那契数的 2 个元素的数组就足够了。在每个步骤中,您只需将这两个数字相加即可。如何保存 2 个连续的斐波那契数?好吧,你知道当你有 2 个连续的整数时,一个是偶数,一个是奇数。因此,您可以使用该属性知道在哪里获取/放置斐波那契数:对于 fib(i),如果 i 是偶数(i%2 为 0),则将其放在数组的第一个元素(索引 0)中,否则(i%2 然后是 1)将它放在第二个元素(索引 1)中。为什么你可以把它放在那里?那么当你计算fib(i) 时,fib(i) 的值应该是fib(i-2)(因为(i-2)%2i%2 相同)。但是您不再需要fib(i-2)fib(i+1) 只需要fib(i-1)(仍在数组中)和fib(i)(刚刚插入数组中)。
    所以你可以像这样用for loop 替换递归调用:

    int fibonacci(int n){
        if( n <= 0){
            return 0;
        }
    
        int previous[] = {0, 1};      // start with fib(0) and fib(1)
        for(int i = 2; i <= n; ++i){
            // modulo can be implemented with bit operations(much faster): i % 2 = i & 1
            previous[i&1] += previous[(i-1)&1];   //shorter way to say: previous[i&1] = previous[i&1] + previous[(i-1)&1]
        }
        //Result is in previous[n&1]
        return previous[n&1];
    }
    

    由于时间(函数调用)和资源(堆栈)消耗,递归实际上在编程时被取消了。因此,每次使用递归时,如果需要保存“当前位置”,请尝试用循环和堆栈替换它,并使用简单的弹出/推送操作(在 c++ 中可以使用vector)。在斐波那契的情况下,甚至不需要堆栈,但是如果您正在迭代 tree datastructure 例如,您将需要一个堆栈(尽管取决于实现)。当我在寻找我的解决方案时,我看到@naomik 提供了一个带有while 循环的解决方案。那个也不错,但我更喜欢带模运算的数组(短一点)。

    现在关于long long int 的大小问题,可以通过使用实现大数字运算的外部库(如GMP library 或Boost.multiprecision)来解决。但是您也可以从 Java 创建您自己版本的 BigInteger 类类,并像我所拥有的那样实现基本操作。我只在我的示例中实现了添加(尝试实现它们非常相似的其他)。

    主要思想很简单,BigInt 通过将其little endian 表示切割成碎片来表示一个大十进制数(我将在最后解释为什么小端序)。这些碎片的长度取决于您选择的底座。 如果你想使用十进制表示,只有当你的基数是 10 的幂时才有效:如果你选择 10 作为基数,每块将代表一个数字,如果你选择 100 (= 10^ 2)作为基数,每片将代表从末尾开始的两个连续数字(参见小端序),如果您选择 1000 作为基数(10^3),每片将代表三个连续数字,......等等。假设您的基数为 100,那么 12765 将是 [65, 27, 1],1789 将是 [89, 17],505 将是 [5, 5] (= [05,5]),...基数为 1000:12765 将是 @987654350 @,1789 将是 [789, 1],505 将是 [505]。它不是最有效的,但它是最直观的(我认为...)
    那么加法就有点像我们在学校学过的纸上加法:

    1. BigInt 的最低部分开始
    2. 将它与另一个的相应部分添加
    3. 该总和的最低部分(= 基数的总和模数)成为最终结果的相应部分
    4. 该总和的“较大”部分将被添加(“携带”)到以下部分的总和中
    5. 下一块进入第 2 步
    6. 如果没有剩余部分,则添加另一个BigInt 的进位和剩余较大部分(如果剩余部分)

    例如:

    9542 + 1097855 = [42, 95] + [55, 78, 09, 1]
        lowest piece = 42 and 55 --> 42 + 55 = 97 = [97]
                    ---> lowest piece of result = 97 (no carry, carry = 0)
        2nd piece = 95 and 78 --> (95+78) + 0 = 173 = [73, 1]
                    ---> 2nd piece of final result = 73
                    ---> remaining: [1] = 1 = carry (will be added to sum of following pieces)
        no piece left in first `BigInt`! 
         --> add carry ( [1] ) and  remaining pieces from second `BigInt`( [9, 1] )  to final result 
         --> first additional piece: 9 + 1 = 10 = [10]  (no carry) 
         --> second additional piece: 1 + 0 = 1 = [1]   (no carry)
    ==> 9542 + 1 097 855 = [42, 95] + [55, 78, 09, 1] = [97, 73, 10, 1] = 1 107 397
    

    Here 是一个演示,我使用上面的类来计算 10000 的斐波那契(结果太大,此处无法复制)

    祝你好运!

    PS:为什么是小端?为了便于实现:它允许在添加数字和迭代时使用push_back,同时实现操作将从数组中的第一个部分而不是最后一个部分开始。

    【讨论】:

    • “由于时间(函数调用)和资源(堆栈)消耗,递归实际上在编程时被取消了”我不同意。 OP 显示的递归形式“显然”是正确的。很难看出您的代码是正确的。以尽可能简单的形式编写代码 - 然后优化探查器告诉您需要的地方和位置。如果没有递归,我当然永远不会启动树行走算法。我可能迭代地做斐波那契。
    • 我尝试添加更多解释。我希望它带来更多的清晰。感谢您的评论。