【问题标题】:Create faster Fibonacci function for n > 100 in MATLAB / octave在 MATLAB / octave 中为 n > 100 创建更快的斐波那契函数
【发布时间】:2015-01-05 21:29:34
【问题描述】:

我有一个函数可以告诉我斐波那契数列中的第 n 个数字。问题是当试图在斐波那契数列中找到更大的数字时它变得非常慢有人知道我该如何解决这个问题吗?

function f = rtfib(n)
 if (n==1)
     f= 1;
 elseif (n == 2)
     f = 2;
 else
     f =rtfib(n-1) + rtfib(n-2);   
 end

结果,

tic; rtfib(20), toc
ans =  10946
Elapsed time is 0.134947 seconds.

tic; rtfib(30), toc
ans =  1346269
Elapsed time is 16.6724 seconds.

rtfib(100) 5 分钟后我什至无法获得价值

PS:我使用的是 octave 3.8.1

【问题讨论】:

  • 编写迭代解决方案,而不是递归解决方案。
  • 避免递归调用而使用数组可能会更快。
  • 如果函数必须只输出第 n 项,而不是序列的前 n 项,您可以使用递归加倍以 θ(log(n)) 复杂度来完成。 Max's answer 暗示了这样的解决方案。

标签: matlab octave fibonacci number-theory


【解决方案1】:

加快斐波那契函数的递归实现的一种简单方法是实现这一点,用其定义替换f(n-1)

f(n) = f(n-1) + f(n-2)
     = f(n-2) + f(n-3) + f(n-2)
     = 2*f(n-2) + f(n-3)

这种简单的转换大大减少了计算序列中的数字所采取的步骤。

如果我们从 OP 的代码开始,稍微更正一下:

function result = fibonacci(n)
switch n
case 0
   result = 0;
case 1
   result = 1;
case 2
   result = 1;
case 3
   result = 2;
otherwise
   result = fibonacci(n-2) + fibonacci(n-1);
end

并应用我们的转换:

function result = fibonacci_fast(n)
switch n
case 0
   result = 0;
case 1
   result = 1;
case 2
   result = 1;
case 3
   result = 2;
otherwise
   result = fibonacci_fast(n-3) + 2*fibonacci_fast(n-2);
end

然后我们看到计算系列中的第 20 个数字的速度提高了 30 倍(使用 Octave):

>> tic; for ii=1:100, fibonacci(20); end; toc
Elapsed time is 12.4393 seconds.
>> tic; for ii=1:100, fibonacci_fast(20); end; toc
Elapsed time is 0.448623 seconds.

当然,Rashid's non-recursive implementation 还要快 60 倍:0.00706792 秒。

【讨论】:

    【解决方案2】:

    在 Python 中实现快速斐波那契计算可以如下所示。我知道这是 Python 而不是 MATLAB/Octave,但它可能会有所帮助。

    基本上,我们不是用 O(2n) 一遍又一遍地调用同一个 Fibonacci 函数,而是用 O(n) 将 Fibonacci 序列存储在一个列表/数组中:

    #!/usr/bin/env python3.5
    
    class Fib:
        def __init__(self,n):
            self.n=n
            self.fibList=[None]*(self.n+1)
            self.populateFibList()
        def populateFibList(self):
            for i in range(len(self.fibList)):
                if i==0:
                    self.fibList[i]=0
                if i==1:
                    self.fibList[i]=1
                if i>1:
                    self.fibList[i]=self.fibList[i-1]+self.fibList[i-2]
        def getFib(self):
            print('Fibonacci sequence up to ', self.n, ' is:')
            for i in range(len(self.fibList)):
                print(i, ' : ', self.fibList[i])
            return self.fibList[self.n]
    
    def isNonnegativeInt(value):
        try:
            if int(value)>=0:#throws an exception if non-convertible to int: returns False
                return True
            else:
                return False
        except:
            return False
    
    n=input('Please enter a non-negative integer: ')
    
    while isNonnegativeInt(n)==False:
        n=input('A non-negative integer is needed: ')
    
    n=int(n) # convert string to int
    
    print('We are using ', n, 'based on what you entered')
    
    print('Fibonacci result is ', Fib(n).getFib())
    

    n=12 的输出如下:

    我测试了n=1003001000的运行时,代码真的很快,我什至不用等待输出。

    【讨论】:

      【解决方案3】:

      如果时间很重要(不是编程技术):

      function f = fib(n)
      if (n == 1)
         f = 1;
      elseif (n == 2)
         f = 2;
      else
         fOld = 2;
         fOlder = 1;
         for i = 3 : n
           f = fOld + fOlder;
           fOlder = fOld;
           fOld = f;
         end
      end
      end
      

      tic;fib(40);toc; ans = 165580141; Elapsed time is 0.000086 seconds.

      您甚至可以使用uint64n = 92 是您可以从uint64 获得的最多:

      tic;fib(92);toc; ans = 12200160415121876738; Elapsed time is 0.001409 seconds.

      因为,

      fib(93) = 19740274219868223167 > intmax('uint64') = 18446744073709551615

      编辑

      为了得到fib(n)n = 183,可以用两个uint64作为一个数

      具有求和的特殊功能,

      function [] = fib(n)
      fL = uint64(0);
      fH = uint64(0);
      MaxNum = uint64(1e19);
      if (n == 1)
         fL = 1;
      elseif (n == 2)
         fL = 2;
      else   
         fOldH = uint64(0);
         fOlderH = uint64(0);
         fOldL = uint64(2);
         fOlderL = uint64(1);
         for i = 3 : n
            [fL q] = LongSum (fOldL , fOlderL , MaxNum);
            fH = fOldH + fOlderH + q;
            fOlderL = fOldL;
            fOlderH = fOldH;
            fOldL = fL;
            fOldH = fH;
         end
       end
       sprintf('%u',fH,fL)
       end
      

      LongSum 是:

      function [s q] = LongSum (a, b, MaxNum)
      if a + b >= MaxNum
         q = 1;
         if a >= MaxNum
            s = a - MaxNum;
            s = s + b;
         elseif b >= MaxNum
            s = b - MaxNum;
            s = s + a;
         else
            s = MaxNum - a;
            s = b - s;
         end
      else
         q = 0;
         s = a + b;
      end
      

      注意LongSum 中的一些复杂问题可能看起来没有必要,但实际上并非如此!

      (与内部if 的所有处理是我想在一个命令中避免s = a + b - MaxNum,因为它可能会溢出并在s 中存储一个不相关的数字)

      结果

      tic;fib(159);toc; Elapsed time is 0.009631 seconds.

      ans = 1226132595394188293000174702095995

      tic;fib(183);toc; 经过的时间是 0.009735 秒。

      fib(183) = 127127879743834334146972278486287885163

      但是,您必须小心sprintf

      我也用三个 uint64 做到了,我可以达到,

      tic;fib(274);toc; 经过的时间是 0.032249 秒。

      ans = 1324695516964754142521850507284930515811378128425638237225

      (几乎是相同的代码,但如果您有兴趣,我可以分享)。

      请注意,根据问题,我们有 fib(1) = 1 , fib(2) = 2,虽然 fib(1) = 1 , fib(2) = 1 更常见,但前 300 个 fib 列出了 here(感谢 @Rick T)。

      【讨论】:

      • 谢谢!!!非常快!!!对于那些将来可能会使用它的人。请注意,当检查斐波那契数列maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html
      • 我认为您也可以使用this 来处理大笔款项。
      • @Amro,因为我们从fib(1) = 1fib(2) = 2 开始,(根据问题),而更常见的是fib(1) = 1fib(2) = 1
      【解决方案4】:

      如果您可以访问 MATLAB 中的符号数学工具箱,您始终可以通过justcall 使用来自MuPAD 的斐波那契函数:

      >> fib = @(n) evalin(symengine, ['numlib::fibonacci(' num2str(n) ')'])
      >> fib(274)
      ans =
      818706854228831001753880637535093596811413714795418360007
      

      速度很快:

      >> timeit(@() fib(274))
      ans =
          0.0011
      

      此外,您可以随心所欲地获得尽可能多的数字(仅受您拥有的 RAM 数量限制!),它仍然非常快:

      % see if you can beat that!
      >> tic
      >> x = fib(100000);
      >> toc               % Elapsed time is 0.004621 seconds.
      
      % result has more than 20 thousand digits!
      >> length(char(x))   % 20899
      

      这是fib(100000)的完整值:http://pastebin.com/f6KPGKBg

      【讨论】:

      【解决方案5】:

      您可以使用矩阵求幂在 O(log n) 时间内完成:

      X = [0 1
           1 1]
      

      X^n 将在右下角为您提供第 n 个斐波那契数; X^n 可以表示为多个矩阵 X^(2^i) 的乘积,因此例如 X^11 将是 X^1 * X^2 * X^8,i

      【讨论】:

        【解决方案6】:

        要达到较大的数字,您可以使用符号计算。以下适用于 Matlab R2010b。

        syms x y %// declare variables
        z = x + y;  %// define formula
        xval = '0'; %// initiallize x, y values
        yval = '1'; 
        for n = 2:300
            zval = subs(z, [x y], {xval yval}); %// update z value
            disp(['Iteration ' num2str(n) ':'])
            disp(zval)
            xval = yval; %// shift values
            yval = zval;
        end
        

        【讨论】:

        • 我很想试试这个,但是 octave 中的符号包还不能真正与 matlab 代码兼容。它给出了 syms x y 的错误
        【解决方案7】:

        似乎斐波那契数列遵循 golden ratio,正如在 here 中详细讨论的那样。

        这是在this MATLAB File-exchange code中使用的,我在这里写,只是它的本质-

        sqrt5 = sqrt(5);
        alpha = (1 + sqrt5)/2;   %// alpha = 1.618... is the golden ratio
        fibs  = round( alpha.^n ./ sqrt5 )
        

        您可以将整数输入 n 以获得 Fibonacci Series 中的 nth 数字,或者输入数组 1:n 以获得整个系列。

        请注意,此方法仅在 n = 69 之前有效。

        【讨论】:

        • 很高兴fibgolden ratio 的连接。
        • 随着n 幅度的增加,这种方法是否保持准确性?
        • @OliverCharlesworth 你说得对!谢谢你指出这一点。所以,我已经和vpa 核对过了,直到69 为止都很好。
        【解决方案8】:

        一个性能问题是您使用递归解决方案。采用迭代方法将使您不必为每个函数调用传递参数。正如 Olivier 指出的那样,它将复杂性降低到线性。

        您也可以查看here。显然有一个公式可以计算斐波那契数列的第 n 个成员。我测试了它的第 50 个元素。 对于更高的 n 值,它不是很准确。

        【讨论】:

        • 这不仅仅是保存传递的参数;它将时间复杂度从指数降低到线性。
        猜你喜欢
        • 1970-01-01
        • 2014-05-03
        • 1970-01-01
        • 2022-12-10
        • 2015-06-19
        • 2017-01-29
        • 1970-01-01
        • 2023-03-09
        • 2017-01-08
        相关资源
        最近更新 更多