n-е число Фибоначчи в сублинейном времени - PullRequest
74 голосов
/ 06 октября 2009

Есть ли какой-нибудь алгоритм для вычисления n-го числа Фибоначчи за сублинейное время?

Ответы [ 14 ]

97 голосов
/ 06 октября 2009

Исходя из ссылки Пиллси на возведение в степень матрицы, такой, что для матрицы

<b>M</b> = [1 1] 
    [1 0] 

, затем

<i>fib</i>(<i>n</i>) = <b>M</b><sup>n</sup><sub>1,2</sub>

Повышение матриц до степеней с помощью повторного умножения не очень эффективно.

Два подхода к возведению в матрицу - это разделяй и властвуй, что дает M n с O ( ln n ) или собственное значение декомпозиция с постоянным временем, но может привести к ошибкам из-за ограниченной точности с плавающей запятой.

Если вы хотите, чтобы точное значение превышало точность вашей реализации с плавающей запятой, вы должны использовать подход O (ln n), основанный на этом отношении:

<b>M</b><sup><i>n</i></sup> = (<b>M</b><sup><i>n</i>/2</sup>)<sup>2</sup> if <i>n</i> even
   = <b>M</b>·<b>M</b><sup><i>n</i>-1</sup> if <i>n</i> is odd

Разложение по собственным значениям M находит две матрицы U и & Lambda; , такие что & Lambda; диагональна и

<b>M</b>  = <b>U</b> <b>&Lambda;</b> <b>U</b><sup>-1</sup> 
 <b>M</b><sup>n</sup> = ( <b>U</b> <b>&Lambda;</b> <b>U</b><sup>-1</sup>) <sup>n</sup>
    = <b>U</b> <b>&Lambda;</b> <b>U</b><sup>-1</sup> <b>U</b> <b>&Lambda;</b> <b>U</b><sup>-1</sup> <b>U</b> <b>&Lambda;</b> <b>U</b><sup>-1</sup> ... n times
    = <b>U</b> <b>&Lambda;</b> <b>&Lambda;</b> <b>&Lambda;</b> ... <b>U</b><sup>-1</sup> 
    = <b>U</b> <b>&Lambda;</b> <sup>n</sup> <b>U</b><sup>-1</sup> 
Повышение диагональной матрицы & lambda; до n th является простым делом повышения каждого элемента в & Lambda; до n th, так что это дает O (1) метод повышения M до n th. Однако значения в & Lambda; вряд ли будут целыми числами, поэтому возникнет некоторая ошибка.

Определение & Lambda; для нашей матрицы 2x2 как

<b>&Lambda;</b> = [ &lambda;<sub>1</sub> 0 ]
  = [ 0 &lambda;<sub>2</sub> ]

Чтобы найти каждый & lambda; , мы решаем

 |<b>M</b> - &lambda;<b>I</b>| = 0

что дает

 |<b>M</b> - &lambda;<b>I</b>| = -&lambda; ( 1 - &lambda; ) - 1

&lambda;&sup2; - &lambda; - 1 = 0

с использованием квадратной формулы

&lambda;    = ( -b &plusmn; &radic; ( b&sup2; - 4ac ) ) / 2a
     = ( 1 &plusmn; &radic;5 ) / 2
 { &lambda;<sub>1</sub>, &lambda;<sub>2</sub> } = { &Phi;, 1-&Phi; } where &Phi; = ( 1 + &radic;5 ) / 2

Если вы прочитали ответ Джейсона, вы можете увидеть, куда это пойдет.

Решение для собственных векторов X 1 и X 2 :

if <b>X</b><sub>1</sub> = [ <b>X</b><sub>1,1</sub>, <b>X</b><sub>1,2</sub> ]

 <b>M</b>.<b>X</b><sub>1 1</sub> = &lambda;<sub>1</sub><b>X</b><sub>1</sub>

 <b>X</b><sub>1,1</sub> + <b>X</b><sub>1,2</sub> = &lambda;<sub>1</sub> <b>X</b><sub>1,1</sub>
 <b>X</b><sub>1,1</sub>      = &lambda;<sub>1</sub> <b>X</b><sub>1,2</sub>

=>
 <b>X</b><sub>1</sub> = [ &Phi;,   1 ]
 <b>X</b><sub>2</sub> = [ 1-&Phi;, 1 ]

Эти векторы дают U :

<b>U</b> = [ <b>X</b><sub>1,1</sub>, <b>X</b><sub>2,2</sub> ]
    [ <b>X</b><sub>1,1</sub>, <b>X</b><sub>2,2</sub> ]

  = [ &Phi;,   1-&Phi; ]
    [ 1,   1   ]

Инвертирование U с использованием

<b>A</b>   = [  a   b ]
      [  c   d ]
=>
<b>A</b><sup>-1</sup> = ( 1 / |<b>A</b>| )  [  d  -b ]
                   [ -c   a ]

так U -1 задается

<b>U</b><sup>-1</sup> = ( 1 / ( &Phi; - ( 1 - &Phi; ) )  [  1  &Phi;-1 ]
                               [ -1   &Phi;  ]
<b>U</b><sup>-1</sup> = ( &radic;5 )<sup>-1</sup>  [  1  &Phi;-1 ]
               [ -1   &Phi;  ]

Проверка работоспособности:

<b>U&Lambda;U</b><sup>-1</sup> = ( &radic;5 )<sup>-1</sup> [ &Phi;   1-&Phi; ] . [ &Phi;   0 ] . [ 1  &Phi;-1 ] 
                     [ 1   1  ]   [ 0  1-&Phi; ]   [ -1   &Phi; ]

let &Psi; = 1-&Phi;, the other eigenvalue

as &Phi; is a root of &lambda;&sup2;-&lambda;-1=0 
so  -&Psi;&Phi; = &Phi;&sup2;-&Phi; = 1
and &Psi;+&Phi; = 1

<b>U&Lambda;U</b><sup>-1</sup> = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [ &Phi;   0 ] . [  1  -&Psi; ] 
                 [ 1   1 ]   [ 0   &Psi; ]   [ -1   &Phi; ]

       = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [ &Phi;   -&Psi;&Phi; ] 
                 [ 1   1 ]   [ -&Psi;  &Psi;&Phi; ]

       = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [ &Phi;    1 ] 
                 [ 1   1 ]   [ -&Psi;  -1 ]

       = ( &radic;5 )<sup>-1</sup> [ &Phi;&sup2;-&Psi;&sup2;  &Phi;-&Psi; ] 
                  [ &Phi;-&Psi;      0 ]

       = [ &Phi;+&Psi;   1 ]    
         [ 1     0 ]

       = [ 1     1 ] 
         [ 1     0 ]

       = <b>M</b> 

Итак, проверка работоспособности выполняется.

Теперь у нас есть все, что нам нужно рассчитать M n 1,2 :

<b>M</b><sup><i>n</i></sup> = <b>U</b><b>&Lambda;</b><sup><i>n</i></sup><b>U</b><sup>-1</sup>
   = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [ &Phi;<sup><i>n</i></sup>  0 ] . [  1  -&Psi; ] 
              [ 1   1 ]   [ 0   &Psi;<sup><i>n</i></sup> ]   [ -1   &Phi; ]

   = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [  &Phi;<sup><i>n</i></sup>  -&Psi;&Phi;<sup><i>n</i></sup> ] 
              [ 1   1 ]   [ -&Psi;<sup><i>n</i></sup>   &Psi;<sup><i>n</i></sup>&Phi; ]

   = ( &radic;5 )<sup>-1</sup> [ &Phi;   &Psi; ] . [  &Phi;<sup><i>n</i></sup>   &Phi;<sup><i>n</i>-1</sup> ] 
              [ 1   1 ]   [ -&Psi;<sup><i>n</i></sup>  -&Psi;<sup><i>n</i>-1</sup> ] as &Psi;&Phi; = -1

   = ( &radic;5 )<sup>-1</sup> [ &Phi;<sup><i>n</i>+1</sup>-&Psi;<sup><i>n</i>+1</sup>      &Phi;<sup><i>n</i></sup>-&Psi;<sup><i>n</i></sup> ]
              [ &Phi;<sup><i>n</i></sup>-&Psi;<sup><i>n</i></sup>      &Phi;<sup><i>n</i>-1</sup>-&Psi;<sup><i>n</i>-1</sup> ]

так

 <i>fib</i>(<i>n</i>) = <b>M</b><sup><i>n</i></sup><sub>1,2</sub>
        = ( &Phi;<sup><i>n</i></sup> - (1-&Phi;)<sup><i>n</i></sup> ) / &radic;5

Что согласуется с формулой, приведенной в другом месте.

Вы можете вывести его из рекуррентного соотношения, но в инженерных вычислениях и симуляции вычисление собственных значений и собственных векторов больших матриц является важным видом деятельности, так как оно обеспечивает устойчивость и гармоничность систем уравнений, а также позволяет поднимать матрицы до высоких державы эффективно.

64 голосов
/ 06 октября 2009

n число Фибоначчи задается как

f(n) = Floor(phi^n / sqrt(5) + 1/2) 

где

phi = (1 + sqrt(5)) / 2

Предполагая, что примитивные математические операции (+, -, * и /) O(1), вы можете использовать этот результат для вычисления n-го числа Фибоначчи за O(log n) время O(log n) из-за возведения в степень в формуле).

В C #:

static double inverseSqrt5 = 1 / Math.Sqrt(5);
static double phi = (1 + Math.Sqrt(5)) / 2;
/* should use 
   const double inverseSqrt5 = 0.44721359549995793928183473374626
   const double phi = 1.6180339887498948482045868343656
*/

static int Fibonacci(int n) {
    return (int)Math.Floor(Math.Pow(phi, n) * inverseSqrt5 + 0.5);
}
54 голосов
/ 11 октября 2009

Если вы хотите точное число (которое является "bignum", а не int / float), то я боюсь, что

Это невозможно!

Как указано выше, формула для чисел Фибоначчи:

fib n = floor (phi n / √5 + 1 / 2 )

fib n ~ = ph n / √5

Сколько цифр fib n?

numDigits (fib n) = log (fib n) = log (phi n / √5) = log phi n - log √5 = n * log phi - log √5

numDigits (fib n) = n * const + const

это O ( n )

Поскольку запрошенный результат равен O ( n ), его нельзя рассчитать менее чем за O ( n ) ) время.

Если вам нужны только младшие цифры ответа, то можно рассчитать в сублинейном времени, используя метод возведения в степень матрицы.

33 голосов
/ 06 октября 2009

Об этом говорится в одном из упражнений в SICP , ответ на который описан здесь .

В императивном стиле программа будет выглядеть примерно так:

<b>Function</b> <i>Fib</i>(<i>count</i>)
    <i>a</i> ← 1
    <i>b</i> ← 0
    <i>p</i> ← 0
    <i>q</i> ← 1

    <b>While</b> <i>count</i> > 0 <b>Do</b>
        <b>If</b> Even(<i>count</i>) <b>Then</b>
             <i>p</i> ← <i>p</i>² + <i>q</i>²
             <i>q</i> ← 2<i>pq</i> + <i>q</i>²
             <i>count</i> ← <i>count</i> ÷ 2
        <b>Else</b>
             <i>a</i> ← <i>bq</i> + <i>aq</i> + <i>ap</i>
             <i>b</i> ← <i>bp</i> + <i>aq</i>
             <i>count</i> ← <i>count</i> - 1
        <b>End If</b>
    <b>End While</b>

    <b>Return</b> <i>b</i>
<b>End Function</b>
24 голосов
/ 06 октября 2009

Вы можете сделать это, возводя в степень матрицу целых чисел. Если у вас есть матрица

    / 1  1 \
M = |      |
    \ 1  0 /

, тогда (M^n)[1, 2] будет равно n -ому числу Фибоначчи, если [] - матричный индекс, а ^ - матричное возведение в степень. Для матрицы фиксированного размера возведение в степень с положительной интегральной степенью может быть выполнено за O (log n) времени так же, как с действительными числами.

РЕДАКТИРОВАТЬ: Конечно, в зависимости от типа ответа, который вы хотите, вы можете обойтись без алгоритма постоянного времени. Как и в других формулах, n число Фибоначчи растет экспоненциально с n. Даже с 64-разрядными целыми числами без знака вам потребуется таблица поиска из 94 записей, чтобы охватить весь диапазон.

ВТОРОЕ РЕДАКТИРОВАНИЕ: Выполнение сначала экспоненциальной матрицы с собственным разложением в точности эквивалентно приведенному ниже решению Дж. Дюнкерли. Собственные значения этой матрицы: (1 + sqrt(5))/2 и (1 - sqrt(5))/2.

5 голосов
/ 06 октября 2009

Википедия имеет закрытое решение http://en.wikipedia.org/wiki/Fibonacci_number

Или в c #:

    public static int Fibonacci(int N)
    {
        double sqrt5 = Math.Sqrt(5);
        double phi = (1 + sqrt5) / 2.0;
        double fn = (Math.Pow(phi, N) - Math.Pow(1 - phi, N)) / sqrt5;
        return (int)fn;
    }
3 голосов
/ 10 июня 2014

Вот моя рекурсивная версия, которая рекурсирует log (n) раз. Я думаю, что проще всего читать в рекурсивной форме:

def my_fib(x):
  if x < 2:
    return x
  else:
    return my_fib_helper(x)[0]

def my_fib_helper(x):
  if x == 1:
    return (1, 0)
  if x % 2 == 1:
    (p,q) = my_fib_helper(x-1)
    return (p+q,p)
  else:
    (p,q) = my_fib_helper(x/2)
    return (p*p+2*p*q,p*p+q*q)

Это работает, потому что вы можете вычислить fib(n),fib(n-1), используя fib(n-1),fib(n-2), если n нечетно, а если n четно, вы можете вычислить fib(n),fib(n-1), используя fib(n/2),fib(n/2-1).

Базовый случай и нечетный случай просты. Чтобы вывести четный случай, начните с a, b, c как последовательных значений Фибоначчи (например, 8,5,3) и запишите их в матрицу с a = b + c. Примечание:

[1 1] * [a b]  =  [a+b a]
[1 0]   [b c]     [a   b]

Из этого мы видим, что матрица из первых трех чисел Фибоначчи, умноженная на матрицу из любых трех последовательных чисел Фибоначчи, равна следующей. Итак, мы знаем, что:

      n
[1 1]   =  [fib(n+1) fib(n)  ]
[1 0]      [fib(n)   fib(n-1)]

Итак:

      2n                        2
[1 1]    =  [fib(n+1) fib(n)  ]
[1 0]       [fib(n)   fib(n-1)]

Упрощение правой части приводит к четному случаю.

3 голосов
/ 27 декабря 2013

Для действительно больших, эта рекурсивная функция работает. Используются следующие уравнения:

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

Вам нужна библиотека, которая позволяет работать с большими целыми числами. Я использую библиотеку BigInteger от https://mattmccutchen.net/bigint/.

Начните с массива чисел Фибоначчи. Используйте fibs [0] = 0, fibs [1] = 1, fibs [2] = 1, fibs [3] = 2, fibs [4] = 3 и т. Д. В этом примере я использую массив первых 501 (считая 0). Вы можете найти первые 500 ненулевых чисел Фибоначчи здесь: http://home.hiwaay.net/~jalison/Fib500.html. Требуется небольшое редактирование, чтобы перевести его в правильный формат, но это не так уж сложно.

Тогда вы можете найти любое число Фибоначчи, используя эту функцию (в C):

BigUnsigned GetFib(int numfib)
{
int n;
BigUnsigned x, y, fib;  

if (numfib < 501) // Just get the Fibonacci number from the fibs array
    {
       fib=(stringToBigUnsigned(fibs[numfib]));
    }
else if (numfib%2) // numfib is odd
    {
       n=(numfib+1)/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=((x*x)+(y*y));
    }
else // numfib is even
    {
       n=numfib/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=(((big2*x)+y)*y);
   }
return(fib);
}

Я проверил это для 25-тысячного числа Фибоначчи и тому подобного.

1 голос
/ 28 февраля 2019

Помимо точной настройки с помощью математических подходов, одним из лучших оптимальных решений (я считаю) является использование словаря, чтобы избежать повторяющихся вычислений.

import time

_dict = {1:1, 2:1}

def F(n, _dict):
    if n in _dict.keys():
        return _dict[n]
    else:
        result = F(n-1, _dict) + F(n-2, _dict)
        _dict.update({n:result})
        return result

start = time.time()

for n in range(1,100000):
    result = F(n, _dict) 

finish = time.time()

print(str(finish - start))

Мы начинаем с тривиального словаря (первые два значения последовательности Фибоначчи) и постоянно добавляем значения Фибоначчи в словарь.

Первые 100000 значений Фибоначчи заняли около 0,7 с (процессор Intel Xeon E5-2680 с частотой 2,70 ГГц, 16 ГБ ОЗУ, ОС Windows 10-64 бит)

1 голос
/ 17 июля 2010

с использованием R

l1 <- (1+sqrt(5))/2
l2 <- (1-sqrt(5))/2

P <- matrix(c(0,1,1,0),nrow=2) #permutation matrix
S <- matrix(c(l1,1,l2,1),nrow=2)
L <- matrix(c(l1,0,0,l2),nrow=2)
C <- c(-1/(l2-l1),1/(l2-l1))

k<-20 ; (S %*% L^k %*% C)[2]
[1] 6765
...