简体   繁体   English

如何存储非常大的斐波那契数的输出?

[英]How to store output of very large Fibonacci number?

I am making a program for nth Fibonacci number. 我正在制作第n个斐波那契数的程序。 I made the following program using recursion and memoization. 我使用递归和记忆创建了以下程序。 The main problem is that the value of n can go up to 10000 which means that the Fibonacci number of 10000 would be more than 2000 digit long. 主要问题是n的值可以达到10000,这意味着10000的斐波那契数将超过2000位数长。

With a little bit of googling, I found that i could use arrays and store every digit of the solution in an element of the array but I am still not able to figure out how to implement this approach with my program. 经过一点点的搜索,我发现我可以使用数组并将解决方案的每一位存储在数组的元素中,但是我仍然无法弄清楚如何在程序中实现这种方法。

#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;
}

How do I implement that approach or if there is another method that can be used to achieve such large values? 如何实现该方法,或者是否可以使用另一种方法来实现如此大的价值?

One thing that I think should be pointed out is there's other ways to implement fib that are much easier for something like C++ to compute 我想指出的一件事是,还有其他实现fib的方法,对于C ++这样的东西来说,计算起来要容易得多

consider the following pseudo code 考虑以下伪代码

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

This doesn't require memoisation and you don't have to be concerned about blowing up your stack with too many recursive calls. 这不需要记忆,您不必担心过多的递归调用会破坏堆栈。 Recursion is a really powerful looping construct but it's one of those fubu things that's best left to langs like Lisp, Scheme, Kotlin, Lua (and a few others) that support it so elegantly. 递归是一个非常强大的循环构造,但它是其中最好的语言之一,最好留给像Lisp,Scheme,Kotlin,Lua(以及其他一些语言)这样的语言来很好地支持它。

That's not to say tail call elimination is impossible in C++, but unless you're doing something to optimise/compile for it explicitly, I'm doubtful that whatever compiler you're using would support it by default. 这并不是说在C ++中不可能消除尾部调用,但是除非您明确地对其进行优化/编译,否则我怀疑您使用的任何编译器默认都将支持它。

As for computing the exceptionally large numbers, you'll have to either get creative doing adding The Hard Way or rely upon an arbitrary precision arithmetic library like GMP . 至于计算非常大的数字,您将不得不通过添加The Hard Way来发挥创造力,或者依赖于GMP之类的任意精度算术库。 I'm sure there's other libs for this too. 我敢肯定,还有其他的库。


Adding The Hard Way™ 添加困难之路™

Remember how you used to add big numbers when you were a little tater tot, fresh off the aluminum foil? 还记得当您有点不习惯时,刚从铝箔上拿出大笔数字吗?

5-year-old math 5岁的数学

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

Well you gotta add each column, right to left. 好吧,您必须从右到左添加每一列。 And when a column overflows into the double digits, remember to carry that 1 over to the next column. 并且当一列溢出到两位数时,请记住将那个1携带到下一列。

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

The 10,000th fibonacci number is thousands of digits long, so there's no way that's going to fit in any integer C++ provides out of the box. 第10,000个斐波纳契数是数千个数字,因此,开箱即用的C ++提供的任何整数都无法容纳。 So without relying upon a library, you could use a string or an array of single-digit numbers. 因此,无需依赖库,就可以使用字符串或单位数字的数组。 To output the final number, you'll have to convert it to a string tho. 要输出最终数字,您必须将其转换为字符串tho。

( woflram alpha: fibonacci 10000 ) woflram alpha: fibonacci 10000

fib(10000)

Doing it this way, you'll perform a couple million single-digit additions; 这样,您将执行数百万个个位数的加法运算。 it might take a while, but it should be a breeze for any modern computer to handle. 可能要花一些时间,但是对于任何现代计算机来说,这都是一件轻而易举的事情。 Time to get to work ! 该上班了!


Here's an example in of a Bignum module in JavaScript 这是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
    }
  }

We can verify that the Wolfram Alpha answer above is correct 我们可以验证上述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"

Expand the program below to run it in your browser 展开下面的程序以在浏览器中运行

 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)) 

Try not to use recursion for a simple problem like fibonacci. 尽量不要对像斐波那契这样的简单问题使用递归。 And if you'll only use it once, don't use an array to store all results. 如果只使用一次,则不要使用数组存储所有结果。 An array of 2 elements containing the 2 previous fibonacci numbers will be enough. 包含2个先前的斐波那契数的2个元素的数组就足够了。 In each step, you then only have to sum up those 2 numbers. 然后,在每个步骤中,您只需要将这两个数字相加即可。 How can you save 2 consecutive fibonacci numbers? 如何保存两个连续的斐波那契数字? Well, you know that when you have 2 consecutive integers one is even and one is odd. 好吧,您知道当您有2个连续整数时,一个为偶数,一个为奇数。 So you can use that property to know where to get/place a fibonacci number: for fib(i) , if i is even ( i%2 is 0) place it in the first element of the array (index 0), else ( i%2 is then 1) place it in the second element(index 1). 因此,您可以使用该属性来知道在哪里获得/放置斐波那契数:对于fib(i) ,如果i fib(i)i%2为0),请将其放置在数组的第一个元素(索引0)中,否则(然后i%2为1)将其放置在第二个元素(索引1)中。 Why can you just place it there? 你为什么可以把它放在那里? Well when you're calculating fib(i) , the value that is on the place fib(i) should go is fib(i-2) (because (i-2)%2 is the same as i%2 ). 好了,当您计算fib(i) ,在fib(i)应该去的位置上的值是fib(i-2) (因为(i-2)%2i%2相同)。 But you won't need fib(i-2) any more: fib(i+1) only needs fib(i-1) (that's still in the array) and fib(i) (that just got inserted in the array). 但是您不再需要fib(i-2)fib(i+1)只需要fib(i-1) (仍在数组中)和fib(i) (刚刚插入到数组中) 。
So you could replace the recursion calls with a for loop like this: 因此,您可以将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];
}

Recursion is actually discommanded while programming because of the time(function calls) and ressources(stack) it consumes. 实际上,在编程时,递归实际上是不合理的,因为它消耗时间(函数调用)和资源(堆栈)。 So each time you use recursion, try to replace it with a loop and a stack with simple pop/push operations if needed to save the "current position" (in c++ one can use a vector ). 因此,每次使用递归时,如果需要保存“当前位置”(在c ++中可以使用vector ),请尝试使用简单的pop / push操作将其替换为循环和堆栈。 In the case of the fibonacci, the stack isn't even needed but if you are iterating over a tree datastructure for example you'll need a stack (depends on the implementation though). 在斐波那契的情况下,甚至不需要堆栈,但是例如,如果要遍历树数据结构 ,则需要一个堆栈(不过取决于实现)。 As I was looking for my solution, I saw @naomik provided a solution with the while loop. 在寻找解决方案时,我看到@naomik通过while循环提供了解决方案。 That one is fine too, but I prefer the array with the modulo operation (a bit shorter). 那也很好,但是我更喜欢使用模运算的数组(短一点)。

Now concerning the problem of the size long long int has, it can be solved by using external libraries that implement operations for big numbers (like the GMP library or Boost.multiprecision). 现在,关于long long int的大小问题,可以通过使用实现大数运算的外部库(例如GMP库或Boost.multiprecision)来解决。 But you could also create your own version of a BigInteger -like class from Java and implement the basic operations like the one I have. 但是,您也可以从Java创建自己的类似BigInteger的类的版本,并像我一样执行基本操作。 I've only implemented the addition in my example (try to implement the others they are quite similar). 我仅在示例中实现了加法(尝试实现它们非常相似的其他方法)。

The main idea is simple, a BigInt represents a big decimal number by cutting its little endian representation into pieces (I'll explain why little endian at the end). 主要思想很简单, BigInt通过将其小尾数表示形式切成小块来表示一个大十进制数(我将在末尾解释为什么使用小尾数)。 The length of those pieces depends on the base you choose. 这些片段的长度取决于您选择的基础。 If you want to work with decimal representations, it will only work if your base is a power of 10 : if you choose 10 as base each piece will represent one digit, if you choose 100 (= 10^2) as base each piece will represent two consecutive digits starting from the end(see little endian), if you choose 1000 as base (10^3) each piece will represent three consecutive digits, ... and so on. 如果要使用十进制表示法,则仅当您的底数是10的幂时才有效 :如果选择10作为底数,则每位将代表一位数字,如果选择100(= 10 ^ 2)作为底数,则每位将代表一位数字。代表从末尾开始的两个连续数字(请参见little endian),如果选择1000作为基数(10 ^ 3),则每段将代表三个连续的数字,等等。 Let's say that you have base 100, 12765 will then be [65, 27, 1] , 1789 will be [89, 17] , 505 will be [5, 5] (= [05,5]), ... with base 1000: 12765 would be [765, 12] , 1789 would be [789, 1] , 505 would be [505] . 比方说,你有底座100,12765届时将[65, 27, 1] ,1789年将是[89, 17] ,505将是[5, 5] (= [05,5]),...用base 1000:12765将是[765, 12] ,1789将是[789, 1] ,505将是[505] It's not the most efficient, but it is the most intuitive (I think ...) 它不是最有效的,但是却是最直观的(我认为...)
The addition is then a bit like the addition on paper we learned at school: 那么添加的内容有点像我们在学校学到的纸上添加的内容:

  1. begin with the lowest piece of the BigInt BigInt的最低部分开始
  2. add it with the corresponding piece of the other one 将其与另一个对应的部分相加
  3. the lowest piece of that sum(= the sum modulus the base) becomes the corresponding piece of the final result 该总和的最低部分(=基础的总和模)成为最终结果的对应部分
  4. the "bigger" pieces of that sum will be added ("carried") to the sum of the following pieces 该总和的“较大”部分将添加(“携带”)到以下各部分的总和中
  5. go to step 2 with next piece 下一块转到步骤2
  6. if no piece left, add the carry and the remaining bigger pieces of the other BigInt (if it has pieces left) 如果没有剩余片段,则添加进位和另一个BigInt的剩余较大片段(如果还有剩余片段)

For example: 例如:

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 is a demo where I used the class above to calculate the fibonacci of 10000 (result is too big to copy here) 是一个演示,其中我使用上面的类来计算10000的斐波那契数(结果太大,无法在此处复制)

Good luck! 祝好运!

PS: Why little endian? PS:为什么要小端? For the ease of the implementation: it allows to use push_back when adding digits and iteration while implementing the operations will start from the first piece instead of the last piece in the array. 为了简化实现:在添加数字和迭代时,它允许使用push_back ,而实现操作将从数组的第一部分开始而不是最后一部分开始。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM