如何存储非常大的Fibonacci数的输出?

时间:2016-12-21 05:19:41

标签: c++ recursion biginteger

我正在制作第n个斐波那契数的程序。我使用递归和memoization制作了以下程序。 主要问题是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;
}

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

2 个答案:

答案 0 :(得分:2)

我认为应该指出的一件事是,实现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(以及其他几个)等langs的东西之一,它们如此优雅地支持它。

在C ++中,不要说尾部调用消除是不可能的,但除非你明确地为它进行优化/编译,否则我怀疑你是不是编译了什么?使用默认支持它。

至于计算特别大的数字,你必须要么创造性地添加The Hard Way,要么依赖于像GMP这样的任意精度算术库。我也确定还有其他的库。

添加Hard Way™

还记得当你喝一点铝箔时,你曾经如何添加大数字吗?

5岁的数学

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

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

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

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

woflram alpha: fibonacci 10000

fib(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"

展开以下程序,在浏览器中运行

&#13;
&#13;
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))
&#13;
&#13;
&#13;

答案 1 :(得分:0)

尽量不要使用递归来解决像fibonacci这样的简单问题。如果您只使用一次,请不要使用数组来存储所有结果。包含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];
}

由于时间(函数调用)和它消耗的资源(堆栈),在编程时实际上会导致递归失效。因此,每次使用递归时,如果需要保存&#34;当前位置,请尝试将其替换为循环和具有简单弹出/推送操作的堆栈。 (在c ++中,可以使用vector)。在fibonacci的情况下,甚至不需要堆栈,但是如果你在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为[765, 12],1789为[789, 1],505为[505]。它不是最有效的,但它是最直观的(我认为......) 这有点像我们在学校学到的纸上添加:

  1. BigInt
  2. 的最低部分开始
  3. 将其添加到另一个
  4. 的相应部分
  5. 该总和的最低部分(=基础的总和模数)成为最终结果的对应部分
  6. &#34;更大&#34;这笔钱的一部分将被添加(&#34;携带&#34;)到以下部分的总和
  7. 转到下一篇
  8. 的第2步
  9. 如果没有剩下一件,请添加其他BigInt的托架和剩余的更大件(如果剩下件数)
  10. 例如:

    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,而实现操作将从第一个部分而不是数组中的最后一个部分开始。