n-те число Фібоначчі в підлінійний час


76

Чи існує якийсь алгоритм для обчислення n-го числа Фібоначчі за підлінійний час?


4
Можна стверджувати, що це пов'язано з алгоритмами, оскільки OP робить невизначене посилання на алгоритмічну складність ... Хоча мені все одно цікаво, який алгоритм.
Метью Шарлі,

2
Дві відповіді нижче мають правильну формулу. Про те, чи пов'язане це питання з програмуванням: це частина інформатики. Апарат, що використовується для отримання формули, відомий як "генеруючі функції" і відіграє важливу роль в аналізі алгоритмів.
azheglov

1
@azheglov: Хоча генерування функцій корисно, вони не потрібні для отримання виразу замкнутої форми для послідовності Фібоначчі.
jason

7
У вас є проблема, яку ви хочете вирішити з будь-якої причини, і ви хочете зробити це ефективно. Іноді необхідним розумінням буде нова реалізація, іноді алгоритм, а іноді математика. Не потрібно засуджувати ситуацію як "не пов'язану з програмуванням" кожного разу, коли це трапляється.
ShreevatsaR

7
Розмір результату лінійний по n. Тому такого алгоритму немає. Звичайно, це не втрачає жодної приємної відповіді нижче, що обчислює числа Фібоначчі за допомогою арифметичних операцій O (log n).
Accipitridae

Відповіді:


66

Число 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);
}

7
@Json Я вас не підтримав, але інші, можливо, роблять це, тому що ваша відповідь свідчить про те, що N-те число Фібоначчі можна обчислити за час O (log n), що є хибним. Ваш код обчислює наближення. Ваш код повинен мати принаймні O (n) у довільній точності, оскільки довжина відповіді - O (n).
PeterAllenWebb

10
@PeterAllenWebb: Наведена формула не є наближенням. N-те число Фібоначчі дорівнює підлозі phi^n / sqrt(5) + 1/2where phi = (1 + sqrt(5)) / 2. Це факт. По-друге, я розумію думку, яку роблять інші щодо довжини відповіді, O(n)але я додав зауваження до своєї відповіді, припускаючи, що примітивні математичні операції займають постійний час (я знаю, що це не так, якщо ви не обмежите вхідні дані). Моя думка полягає в тому, що ми можемо знайти n-те число Фібоначчі в O(log n)арифметичних операціях.
jason

4
@Jason: Якщо припустити, що додавання до степеня O (1), це також робить весь алгоритм O (1). Це було б непогано, однак, степенування не є O (1), як і інші примітивні математичні операції. Тож коротше, формула приємна, але вона не обчислює результат за підлінійний час.
yairchu

12
@Jason: Формула не є апроксимацією, але код є апроксимацією (за винятком уявної реалізації C #, в якій Math.Pow (...) має нескінченну точність, в такому випадку кодом є O (n)).
ShreevatsaR

14
@ Джейсон: Ні. Запустіть свій код на n = 1000 (для якого число Фібоначчі 43466 ... 849228875 має мізерні 209 цифр) і скажіть мені, чи правильно ви все ввели . Щоб Math.Floor отримав правильну цілочисельну частину, ці багато цифр повинні бути точно розраховані Math.Pow. Насправді, при моїй реалізації C ++, навіть 16-значний F_ {74} = 130496954492865 обчислюється неправильно, навіть незважаючи на те, що ціле число 130496954492865 може бути представлене точно (з довгим довгим), і я буду здивований, якщо C # отримує набагато більше цифр ніж це.
ShreevatsaR

100

Випливаючи з посилання Пілсі на піднесення степенів до матриці, такого як для матриці

М = [1 1]
    [1 0] 

тоді

fib ( n ) = M n 1,2

Підвищення матриць до степенів за допомогою багаторазового множення не дуже ефективно.

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

Якщо ви хочете, щоб точне значення було більшим за точність вашої реалізації з плаваючою комою, вам доведеться використовувати підхід O (ln n), заснований на цьому відношенні:

M n = ( M n / 2 ) 2, якщо n парне
   = M · M n -1, якщо n непарне

Власне розкладання на M знаходить дві матриці U та Л такий , що Λ є діагональним і

 M   = U  Λ  U -1  
 M n = ( U  Λ  U -1 ) n 
    = U  Λ  U -1  U  Λ  U -1  U  Λ  U -1 ... n разів
    = U  Λ  Λ  Λ ... U -1  
    = U  Λ  n  U -1 
Підняти діагональну матрицю Λ до n- ї ступеня - це просте питання підняття кожного елемента в Λ до n- ої, отже, це дає O (1) метод підняття M до n- ї ступеня. Однак значення в Λ , швидше за все, не будуть цілими числами, тому трапиться певна помилка.

Визначення Λ для нашої матриці 2x2 як

Λ = [λ 1 0]
  = [0 λ 2 ]

Щоб знайти кожен λ , вирішуємо

| M - λ I | = 0

який дає

| M - λ I | = -λ (1 - λ) - 1

λ² - λ - 1 = 0

за допомогою квадратної формули

λ = (-b ± √ (b² - 4ac)) / 2a
     = (1 ± √5) / 2
 {λ 1 , λ 2 } = {Φ, 1-Φ} де Φ = (1 + √5) / 2

Якщо ви прочитали відповідь Джейсона, ви можете зрозуміти, куди це піде.

Розв'язування власних векторів X 1 і X 2 :

якщо X 1 = [ X 1,1 , X 1,2 ]

 M . X 1 1 = λ 1 X 1

 X 1,1 + X 1,2 = λ 1  X 1,1 
 X 1,1       = λ 1  X 1,2

=>
 X 1 = [Φ, 1]
  X 2 = [1-Φ, 1]

Ці вектори дають U :

U = [ X 1,1 , X 2,2 ]
    [ X 1,1 , X 2,2 ]

  = [Φ, 1-Φ]
    [1, 1]

Інвертування U за допомогою

A    = [ab]
      [cd]
=>
A -1 = (1 / | A |) [d -b]
                   [-ca]

отже U -1 задано як

U -1 = (1 / (Φ - (1 - Φ)) [1 Φ-1]
                               [-1 Φ]
U -1 = (√5) -1   [1 Φ-1]
               [-1 Φ]

Перевірка осудності:

UΛU -1 = (√5) -1 [Φ 1-Φ]. [Φ 0]. [1 Φ-1]
                     [1 1] [0 1-Φ] [-1 Φ]

нехай Ψ = 1-Φ, інше власне значення

оскільки Φ є коренем з λ²-λ-1 = 0 
так -ΨΦ = Φ²-Φ = 1
і Ψ + Φ = 1

UΛU -1 = (√5) -1 [Φ Ψ]. [Φ 0]. [1 -Ψ]
                 [1 1] [0 Ψ] [-1 Φ]

       = (√5) -1 [Φ Ψ]. [Φ -ΨΦ]
                 [1 1] [-Ψ ΨΦ]

       = (√5) -1 [Φ Ψ]. [Φ 1]
                 [1 1] [-Ψ -1]

       = (√5) -1 [Φ²-Ψ² Φ-Ψ]
                  [Φ-Ψ 0]

       = [Φ + Ψ 1]    
         [1 0]

       = [1 1] 
         [1 0]

       = М 

Отже, перевірка осудності зберігається.

Тепер у нас є все необхідне для обчислення M n 1,2 :

M n = U Λ n U -1 
   = (√5) -1 [Φ Ψ]. [Φ n   0]. [1 -Ψ]
              [1 1] [0 Ψ n ] [-1 Φ]

   = (√5) -1 [Φ Ψ]. [Φ n   -ΨΦ n ]
              [1 1] [-Ψ n    Ψ n Φ]

   = (√5) -1 [Φ Ψ]. [Φ n    Φ n -1 ]
              [1 1] [-Ψ nn -1 ] як ΨΦ = -1

   = (√5) -1n +1n +1       Φ nn ]
              [Φ nn       Φ n -1n -1 ]

так

 fib ( n ) = M n 1,2 
        = (Φ n - (1-Φ) n ) / √5

Що узгоджується з формулою, наведеною в іншому місці.

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


+1 - чудові речі, як завжди. Що ти використовував для набору тексту? LaTeX?
duffymo

Він копіюється з книги Алгебри Гілберта Стренга або з іншої гарної книги Лінійної алгебри.
alinsoar

1
@alinsoar це не було "вставлено копію", а зроблено як вправу перевірити, чи я все ще пам'ятаю свою посилання, з деяким посиланням на примітки до курсу Відкритого університету та Вікіпедію.
Піт Кіркхем

Я пройшов курс L Algebra з Гілбертом Стренгом, і там він був ідентичним. Цілком так, проблема вираження рекурсії за допомогою розкладання матриці є класичною і її можна знайти в будь-якому хорошому підручнику / курсі.
alinsoar

56

Якщо вам потрібна точна цифра (яка є "bignum", а не int / float), то я боюся, що

Це неможливо!

Як зазначено вище, формула чисел Фібоначчі:

FIB п = підлогу (фі п / √5 + 1 / 2 )

fib n ~ = phi 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 ).

Якщо вам потрібні лише нижчі цифри відповіді, тоді можна обчислити за підлінійний час, використовуючи метод піднесення степенів до матриці.


2
@yairchu: Дозвольте мені переформулювати це, якщо я правильно його розумію. Теоретично обчислення fib_n вимагає обчислення n цифр, тому для будь-якого довільного n це займе час O (n). Однак якщо fib_n <sizeof (long long), тоді ми можемо обчислити fib_n за час O (log n), оскільки архітектура машини забезпечує паралельний механізм встановлення бітів. (Наприклад, int i = -1; потрібно встановити 32-біт, але на 32-бітній машині всі 32 біти можна встановити в постійний час.
Суміт

7
@Sumit: Якщо ви хочете підтримувати лише результати, які вміщуються в 32-біт, тоді ви також можете мати таблицю пошуку для цих перших 48 результатів серії. Це очевидно O (1), але: Аналіз великого O для обмеженого N нерозумно, оскільки ви завжди можете включити що-небудь у постійний коефіцієнт. Тож моя відповідь стосується необмеженого введення.
yairchu

1
@yairchu: Чи не могли б ви продемонструвати свою логіку для відомого прикладу, наприклад O(n*log n)для сортування на основі порівняння послідовності nчисел, де кожне число має O(log n)цифри?
jfs

1
Це правильно чи неправильно, залежно від того, що ви маєте на увазі "час". Для сортування (або пошуку хеш-таблиці) "час" означає кількість порівнянь. У питанні це могло означати арифметичні дії. У цій відповіді це означає щось на зразок цифрових операцій.
Пол Хенкін

4
Цілі числа дійсно матимуть скінченне подання в базовому sqrt (2), але це буде просто нуль на непарних цифрах, тобто еквівалент базі 2. Якщо будь-яка з непарних цифр у базовому sqrt (2) не дорівнює нулю, у вас є ірраціональне число . Один із випадків, коли вам може знадобитися базовий фі в АЦП при перетворенні безперервних сигналів в аналогові. Afaik це "промислове" застосування базового фі, де воно використовується для зменшення грубого зернистості при округленні сигналу. Хоча особисто я використовував базові кодування phi і fibonacci як зручний для нотації спосіб роботи з будь-якими поданнями Фібоначчі про будь-яку групу кіс.
saolof

34

Про це стосується одна з вправ у SICP , на яку є відповідь, описана тут.

В імперативному стилі програма виглядала б приблизно так

Функція  Fib ( кол )
     a ← 1
     b ← 0
     p ← 0
     q ← 1

    Тоді як  count > 0 Do 
        If Even ( count ) Тоді 
             pp ² + q ²
              q ← 2 pq + q ²
              countcount ÷ 2
         Інакше 
             abq + aq + ap 
             bbp + aq 
             countcount - 1
         End If 
    Кінець поки

    Повернення  b 
Функція завершення

ось реалізація в Python (для використання з twistedфреймворком).
jfs

"If Even (count)
then

@MonirulIslamMilon if even(count)правильний. Послідовність починається з нуля (нульове число Фібоначчі дорівнює нулю): 0,1,1,2,3,5,8,13, ...
jfs

Посилання на книгу зараз: mitpress.mit.edu/sites/default/files/sicp/full-text/book/…
Lee D

Пізній коментар, але змінні p та a замінюються перед використанням для обчислення q та b. Щоб уникнути цієї проблеми, попередньо обчисліть умови та змініть порядок призначення p та q: | qq = q · q | q = 2 · p · q + qq | p = p · p + qq | ... | aq = a · q | a = b · q + aq + a · p | b = b · p + aq | .
rcgldr

24

Ви можете зробити це, також підставивши матрицю цілих чисел. Якщо у вас є матриця

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

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

РЕДАГУВАТИ: Звичайно, залежно від типу відповіді, яку ви хочете, можливо, вам вдасться уникнути за допомогою алгоритму постійного часу. Як показують інші формули, число nФібоначчі зростає в геометричній прогресії n. Навіть із 64-розрядними цілими числами без підпису вам знадобиться лише таблиця підстановки з 94 записами, щоб охопити весь діапазон.

ДРУГЕ РЕДАГУВАННЯ: Спершу виконати експоненцію матриці з власним розкладом - це рівнозначно розв’язанню JDunkerly нижче. Власні значення цієї матриці - це (1 + sqrt(5))/2і (1 - sqrt(5))/2.


3
Використовуйте власне розкладання M, щоб ефективно розрахувати M ^ n.
Піт Кіркхем,

1
Запропонований метод підходить для обчислень у цілих числах (можливо, з довгою арифметикою). Підхід із власним розкладанням не цікавий: якщо вам не потрібні цілочисельні обчислення, тоді скористайтеся формулою з відповіді Джейсона.
Костянтин Тензін

1
@Konstantin Формула з відповіді Джейсона - результат результату власного розкладання, тому ви суперечите собі.
Піт Кіркхем,

@Pete Kirkham Цю формулу можна отримати кількома методами: рівняння характеристик, власне розкладання, доведення за допомогою індукції. Я не впевнений, що власне розкладання є найпростішим. У будь-якому випадку він добре відомий, і користуватися ним простіше відразу
Костянтин Тензін

5

У Вікіпедії є рішення із закритою формою 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;
    }

2
Ви можете уникнути необхідності обчислювати два експоненти, використовуючи той факт, що |1 - phi|^n / sqrt(5) < 1/2коли nє цілим невід’ємним числом.
jason

Не знав, що коригування завжди використовувало іншу форму, але це приємна оптимізація
JDunkerley,

1
Наближення результату правильним рішенням передбачає множення матриць.
cerkiewny

4

Для справді великих ця рекурсивна функція працює. Він використовує такі рівняння:

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 . Потрібно трохи редагувати, щоб перевести його у потрібний формат, але це не надто складно.

Тоді ви можете знайти будь-яке число Фібоначчі, використовуючи цю функцію (в С):

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 000-го числа Фібоначчі тощо.


Цей код не такий ефективний. Уявіть, що масив fibs [] має розмір лише 10, і ви викликаєте Fib (101). Fib (101) викликає Fib (51) та Fib (50). Fib (51) викликає Fib (26) та Fib (25). Fib (50) викликає Fib (25) та Fib (24). Тож Fib (25) називали двічі, що марно. Навіть із волокнами до 500, у вас буде така сама проблема з Fib (100000).
Еял,

3

Ось моя рекурсивна версія, яка повторює журнал (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)]

Спрощення правої сторони веде до парного випадку.


Тут я хочу підкреслити, що ви хочете обчислити F (2n) і F (2n + 1) у функції F (n) і F (n-1). Ви не вказали, що хочете робити.
alinsoar

1

за допомогою 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

1

Арифметика з фіксованою точкою неточна. Код С # Джейсона дає неправильну відповідь на n = 71 (308061521170130 замість 308061521170129) і далі.

Для правильної відповіді використовуйте обчислювальну систему алгебри. Sympy - це така бібліотека для Python. Інтерактивна консоль знаходиться за адресою http://live.sympy.org/ . Скопіюйте та вставте цю функцію

phi = (1 + sqrt(5)) / 2
def f(n):
    return floor(phi**n / sqrt(5) + 1/2)

Тоді обчисліть

>>> f(10)
55

>>> f(71)
308061521170129

Ви можете спробувати перевірити phi.


1

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

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 значень Фібоначчі (процесор Intel Xeon E5-2680 @ 2,70 ГГц, 16 ГБ оперативної пам'яті, Windows 10-64 бітна ОС) зайняли близько 0,7 секунди


Однак це в лінійному часі, питання конкретно задає питання про те, як досягти підлінійного часу (що можливо, використовуючи своєрідне рішення замкнутої форми).
Ромео Валентин

0

дивіться тут алгоритм поділу та завоювання

Посилання має псевдокод для піднесення степенів до матриці, згаданий у деяких інших відповідях на це питання.


0

Ви можете скористатися дивним рівнянням квадратного кореня, щоб отримати точну відповідь. Причина полягає в тому, що $ \ sqrt (5) $ випадає в кінці, вам просто потрібно відстежувати коефіцієнти за власним форматом множення.

def rootiply(a1,b1,a2,b2,c):
    ''' multipy a1+b1*sqrt(c) and a2+b2*sqrt(c)... return a,b'''
    return a1*a2 + b1*b2*c, a1*b2 + a2*b1

def rootipower(a,b,c,n):
    ''' raise a + b * sqrt(c) to the nth power... returns the new a,b and c of the result in the same format'''
    ar,br = 1,0
    while n != 0:
        if n%2:
            ar,br = rootiply(ar,br,a,b,c)
        a,b = rootiply(a,b,a,b,c)
        n /= 2
    return ar,br

def fib(k):
    ''' the kth fibonacci number'''
    a1,b1 = rootipower(1,1,5,k)
    a2,b2 = rootipower(1,-1,5,k)
    a = a1-a2
    b = b1-b2
    a,b = rootiply(0,1,a,b,5)
    # b should be 0!
    assert b == 0
    return a/2**k/5

if __name__ == "__main__":
    assert rootipower(1,2,3,3) == (37,30) # 1+2sqrt(3) **3 => 13 + 4sqrt(3) => 39 + 30sqrt(3)
    assert fib(10)==55

0

Ось однорядковий рядок, який обчислює F (n), використовуючи цілі числа розміром O (n), в арифметичних операціях O (log n):

for i in range(1, 50):
    print(i, pow(2<<i, i, (4<<2*i)-(2<<i)-1)//(2<<i))

Використання цілих чисел розміру O (n) є розумним, оскільки це порівнянно з розміром відповіді.

Щоб зрозуміти це, нехай phi - золотий перетин (найбільше рішення x ^ 2 = x + 1), а F (n) - n-те число Фібоначчі, де F (0) = 0, F (1) = F (2) = 1

Тепер phi ^ n = F (n-1) + F (n) phi.

Доведення за допомогою індукції: phi ^ 1 = 0 + 1 * phi = F (0) + F (1) phi. І якщо phi ^ n = F (n-1) + F (n) phi, то phi ^ (n + 1) = F (n-1) phi + F (n) phi ^ 2 = F (n-1) phi + F (n) (phi + 1) = F (n) + (F (n) + F (n-1)) phi = F (n) + F (n + 1) phi. Єдиним хитрим кроком у цьому розрахунку є той, який замінює phi ^ 2 на (1 + phi), що випливає, оскільки phi - це золотий перетин.

Також числа форми (a + b * phi), де a, b - цілі числа, закриваються при множенні.

Доказ: (p0 + p1 * phi) (q0 + q1 * phi) = p0q0 + (p0q1 + q1p0) phi + p1q1 * phi ^ 2 = p0q0 + (p0q1 + q1p0) phi + p1q1 * (phi + 1) = ( p0q0 + p1q1) + (p0q1 + q1p0 + p1q1) * фі.

Використовуючи це подання, можна обчислити phi ^ n в O (log n) цілочисельних операціях, використовуючи піднесення до степенів шляхом квадратування. Результатом буде F (n-1) + F (n) phi, з якого можна прочитати n-те число Фібоначчі.

def mul(p, q):
    return p[0]*q[0]+p[1]*q[1], p[0]*q[1]+p[1]*q[0]+p[1]*q[1]

def pow(p, n):
    r=1,0
    while n:
        if n&1: r=mul(r, p)
        p=mul(p, p)
        n=n>>1
    return r

for i in range(1, 50):
    print(i, pow((0, 1), i)[1])

Зауважте, що більшість цього коду є стандартною функцією піднесення до квадратури.

Щоб дістатися до однокласника, який починає цю відповідь, можна зауважити, що, представляючи phi досить великим цілим числом X, можна виконати його (a+b*phi)(c+d*phi)як цілочисельну операцію (a+bX)(c+dX) modulo (X^2-X-1). Тоді powфункцію можна замінити на стандартну powфункцію Python (яка зручно включає третій аргумент, zякий обчислює результат за модулем z. XВибраним є 2<<i.


0

Я натрапив на деякі методи розрахунку Фібоначчі з ефективною часовою складністю, ось деякі з них -

Метод 1 - Динамічне програмування Зараз тут підструктура є загальновідомою, отже я прямо перейду до рішення -

static int fib(int n) 
{ 
int f[] = new int[n+2]; // 1 extra to handle case, n = 0 
int i; 

f[0] = 0; 
f[1] = 1; 

for (i = 2; i <= n; i++) 
{ 
    f[i] = f[i-1] + f[i-2]; 
} 

return f[n]; 
}

Версію, оптимізовану для простору, можна зробити наступним чином -

static int fib(int n) 
 { 
    int a = 0, b = 1, c; 
    if (n == 0) 
        return a; 
    for (int i = 2; i <= n; i++) 
    { 
        c = a + b; 
        a = b; 
        b = c; 
    } 
    return b; 
} 

Спосіб 2- (Використання потужності матриці {{1,1}, {1,0}})

Це O (n), яке спирається на той факт, що якщо n разів помножити матрицю M = {{1,1}, {1,0}} на себе (іншими словами, обчислити потужність (M, n)), то ми отримуємо (n + 1) число Фібоначчі як елемент у рядку та стовпці (0, 0) в результуючій матриці. Це рішення мало б O (n) час.

Матричне подання дає наступний замкнутий вираз для чисел Фібоначчі: фібоначіматриця

static int fib(int n) 
{ 
int F[][] = new int[][]{{1,1},{1,0}}; 
if (n == 0) 
    return 0; 
power(F, n-1); 

return F[0][0]; 
} 

/*multiplies 2 matrices F and M of size 2*2, and 
puts the multiplication result back to F[][] */
static void multiply(int F[][], int M[][]) 
{ 
int x = F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
int y = F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
int z = F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
int w = F[1][0]*M[0][1] + F[1][1]*M[1][1]; 

F[0][0] = x; 
F[0][1] = y; 
F[1][0] = z; 
F[1][1] = w; 
} 

/*function that calculates F[][] raise to the power n and puts the 
result in F[][]*/
static void power(int F[][], int n) 
{ 
int i; 
int M[][] = new int[][]{{1,1},{1,0}}; 

// n - 1 times multiply the matrix to {{1,0},{0,1}} 
for (i = 2; i <= n; i++) 
    multiply(F, M); 
} 

Це можна оптимізувати для роботи в O (Logn) часовій складності. Ми можемо зробити рекурсивне множення, щоб отримати потужність (M, n) у попередньому методі.

static int fib(int n) 
{ 
int F[][] = new int[][]{{1,1},{1,0}}; 
if (n == 0) 
    return 0; 
power(F, n-1); 

return F[0][0]; 
} 

static void multiply(int F[][], int M[][]) 
{ 
int x =  F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
int y =  F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
int z =  F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
int w =  F[1][0]*M[0][1] + F[1][1]*M[1][1]; 

F[0][0] = x; 
F[0][1] = y; 
F[1][0] = z; 
F[1][1] = w; 
} 

static void power(int F[][], int n) 
{ 
if( n == 0 || n == 1) 
  return; 
int M[][] = new int[][]{{1,1},{1,0}}; 

power(F, n/2); 
multiply(F, F); 

if (n%2 != 0) 
   multiply(F, M); 
} 

Метод 3 (O (log n) Time) Нижче наведено ще одну цікаву формулу рецидиву, яку можна використовувати для пошуку n-го числа Фібоначчі за O (log n) часу.

Якщо n рівне, тоді k = n / 2: F (n) = [2 * F (k-1) + F (k)] * F (k)

Якщо n непарна, то k = (n + 1) / 2 F (n) = F (k) * F (k) + F (k-1) * F (k-1) Як працює ця формула? Формулу можна отримати з наведеного вище матричного рівняння. фібонациматрикс

Беручи визначник з обох сторін, отримуємо (-1) n = Fn + 1Fn-1 - Fn2 Більше того, оскільки AnAm = An + m для будь-якої квадратної матриці A, можна отримати наступні тотожності (вони отримані з двох різних коефіцієнтів матричний продукт)

FmFn + Fm-1Fn-1 = Fm + n-1

Поставивши n = n + 1,

FmFn + 1 + Fm-1Fn = Fm + n

Поставивши m = n

F2n-1 = Fn2 + Fn-12

F2n = (Fn-1 + Fn + 1) Fn = (2Fn-1 + Fn) Fn (Джерело: Wiki)

Щоб довести формулу, яку потрібно довести, нам просто потрібно зробити наступне. Якщо n парне, ми можемо поставити k = n / 2 Якщо n непарне, ми можемо поставити k = (n + 1) / 2

public static int fib(int n) 
{ 

    if (n == 0) 
        return 0; 

    if (n == 1 || n == 2) 
        return (f[n] = 1); 

    // If fib(n) is already computed 
    if (f[n] != 0) 
        return f[n]; 

    int k = (n & 1) == 1? (n + 1) / 2 
                        : n / 2; 

    // Applyting above formula [See value 
    // n&1 is 1 if n is odd, else 0. 
    f[n] = (n & 1) == 1? (fib(k) * fib(k) +  
                    fib(k - 1) * fib(k - 1)) 
                   : (2 * fib(k - 1) + fib(k))  
                   * fib(k); 

    return f[n]; 
} 

Метод 4 - Використання формули У цьому методі ми безпосередньо реалізуємо формулу для n-го члена ряду Фібоначчі. Час O (1) Простір O (1) Fn = {[(√5 + 1) / 2] ^ n} / √5

static int fib(int n) { 
double phi = (1 + Math.sqrt(5)) / 2; 
return (int) Math.round(Math.pow(phi, n)  
                    / Math.sqrt(5)); 
} 

Посилання: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html


0

Спочатку слід зазначити, що числа Фібоначчі (F(n))дуже швидко зростають nі не можуть бути представлені в 64-бітах для nрозміру більше 93. Тож програма для їх обчислення для таких nпотреб використовує додаткові механізми для роботи з цими великими числами. Тепер, враховуючи лише підрахунок (великої кількості) операцій, алгоритм їх послідовного обчислення вимагатиме лінійної кількості операцій.

Ми можемо скористатися наведеною нижче інформацією про числа Фібоначчі:

F(2m) = 2*F(m)*F(m+1) − (F(m))^2

F(2m+1) = (F(m))^2 + (F(m+1))^2

(такий символ, як A ^ 2, позначає квадрат A).

Отже, якщо ми знаємо F(m)і F(m+1), ми можемо безпосередньо обчислити F(2m)і F(2m+1).

Розглянемо двійкове представлення n. Зверніть увагу, що, починаючи з x = 1, ми можемо зробити це x = nшляхом ітеративного подвоєння та, можливо, додавання 1 до x. Це можна зробити, перебираючи біти n, і перевіряючи, чи воно дорівнює 0 або 1.

Ідея полягає в тому, що ми можемо підтримувати F(x)синхронізацію з x. У кожній такій ітерації, коли ми подвоюємо xі, можливо, додаємо 1 x, ми також можемо обчислити нове значення, F(x)використовуючи попереднє значення F(x)та F(x+1), з наведеними вище рівняннями.

Оскільки кількість ітерацій буде логарифмічним n, загальна (велика кількість) операцій також є логарифмічною n.

Для отримання додаткової інформації див. Розділ "Покращений алгоритм" цієї статті .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.