Обчислювальна складність послідовності Фібоначчі


330

Я розумію позначення Big-O, але не знаю, як обчислити його для багатьох функцій. Зокрема, я намагався з'ясувати обчислювальну складність наївної версії послідовності Фібоначчі:

int Fibonacci(int n)
{
    if (n <= 1)
        return n;
    else
        return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Яка обчислювальна складність послідовності Фібоначчі і як вона обчислюється?



3
Дивіться розділ форми матриць тут: en.wikipedia.org/wiki/Fibach_number . зробивши цю матрицю ^ n (розумним чином), ви можете обчислити Fib (n) в O (lg n). Хитрість полягає у виконанні функції живлення. Є дуже гарна лекція в iTunesU про цю точну проблему і як вирішити в O (lg n). Курс - це ознайомлення з алгоритмами з лекції 3 MIT (його абсолютлей безкоштовно, тому перевірте, якщо вам це цікаво)
Aly

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

Дуже гарне відео тут , який говорить про як нижня межа складності (2 ^ п / 2) і верхня межа складності (2 ^ п) рекурсивної реалізації.
RBT

1
Запит на сторону: Якщо наївна реалізація серії Фібоначчі повинна бути ітеративною чи рекурсивною ?
RBT

Відповіді:


374

Ви моделюєте функцію часу для обчислення Fib(n)як суму часу для обчислення Fib(n-1)плюс час для обчислення Fib(n-2)плюс час їх складання ( O(1)). Це припускаючи, що повторне оцінювання одного й того ж Fib(n)займає однаковий час - тобто жодне запам'ятовування не використовується.

T(n<=1) = O(1)

T(n) = T(n-1) + T(n-2) + O(1)

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

Можна також намалювати дерево рекурсії, яке матиме глибину nта інтуїтивно зрозуміє, що ця функція є асимптотичною . Потім ви можете довести свою здогадку за допомогою індукції.O(2n)

База: n = 1очевидно

Припустимо , отжеT(n-1) = O(2n-1)

T(n) = T(n-1) + T(n-2) + O(1) що дорівнює

T(n) = O(2n-1) + O(2n-2) + O(1) = O(2n)

Однак, як зазначається в коментарі, це не є тісною межею. Цікавим фактом щодо цієї функції є те, що T (n) асимптотично однакова як значення, Fib(n)оскільки обидва визначаються як

f(n) = f(n-1) + f(n-2).

Листя дерева рекурсії завжди повертатимуться 1. Значення - Fib(n)це сума всіх значень, повернутих листям у дереві рекурсії, що дорівнює кількості листя. Оскільки для обчислення кожного аркуша буде потрібно O (1), T(n)дорівнює Fib(n) x O(1). Отже, жорсткою межею для цієї функції є сама послідовність Фібоначчі (~ ). Ви можете дізнатися про цю межу, використовуючи функції генерування, як я вже згадував вище.θ(1.6n)


29
Також доказ індукцією. Приємно. +1
Ендрю Роллінгз

Хоча зв’язаний не тісний.
Капітан Сегфол

@Captain Segfault: Так. Я уточнив відповідь. Ви отримаєте жорсткий зв'язок, використовуючи метод GF, як я писав вище.
Мехрдад Афшарі

Відкладіть ваш StackOverflowException як жарт. Експоненційний час сприймається досить легко при досить малих значеннях для n.
Девід Родрігес - дрибес

1
"Крім того, ви можете намалювати дерево рекурсії, яке матиме глибину n та інтуїтивно зрозуміє, що ця функція є асимптотично O (2n)." - Це абсолютно неправдиво. Складність часу становить O (gold_ratio ^ n). Він ніколи не наближається до O (2 ^ n). Якщо ви могли б дотягнутися до нескінченності, воно наблизиться до O (golden_ratio ^ n). Ось що таке асимптота, відстань між двома лініями має наближатися до 0.
bob

133

Просто запитайте себе, скільки заяв потрібно виконати, F(n)щоб виконати.

Бо F(1)відповідь є 1(перша частина умовна).

Бо F(n)відповідь така F(n-1) + F(n-2).

То яка функція відповідає цим правилам? Спробуйте n (a> 1):

a n == a (n-1) + a (n-2)

Розділіть через (n-2) :

a 2 == a + 1

Вирішіть aі ви отримаєте (1+sqrt(5))/2 = 1.6180339887, інакше відоме як золоте співвідношення .

Отже, це займає експоненціальний час.


8
Доведення за допомогою індукції. Приємно. +1
Ендрю Роллінгз

2
30 підказок за неправильну відповідь? :-) Звідси випливає, що 1 = F (1) = (1 + sqrt (5)) / 2? А як щодо іншого рішення, (1-sqrt (5)) / 2?
Carsten S

1
Ні, 1 не дорівнює 1 + 1. У питанні згадується функція, яка задовольняє цим правилам.
molbdnilo

6
Відповідь неправильна. Це правильно безсимптомно. Інше рішення негативне, тому фізично не має сенсу.
Да Тенг

10
Чи може хтось пояснити, як ^ n == a ^ (n-1) + a ^ (n-2) задовольняє цим правилам? Як саме це задоволено, будь ласка, уточнюйте.
відвертий

33

Я погоджуюсь з pgaur та rickerbh, складність рекурсивно-полевого рівня - O (2 ^ n).

Я прийшов до такого ж висновку досить спрощеним, але я вважаю, що все-таки справедливі міркування.

По-перше, все полягає у з'ясуванні, скільки разів рекурсивна функція (F () відтепер викликається при обчисленні числа N-го поля). Якщо він називається один раз на число в послідовності 0 до n, тоді у нас є O (n), якщо він називається n разів для кожного числа, то ми отримуємо O (n * n) або O (n ^ 2), і так далі.

Отже, коли F () викликається числом n, кількість разів, коли F () викликається на задане число між 0 і n-1, зростає, коли ми наближаємось до 0.

Як перше враження, мені здається, що якщо ми поставимо це візуально, малюючи одиницю за раз, коли F () викликається заданим числом, мокрий набуває свого роду форму піраміди (тобто, якщо ми централізуємо одиниці по горизонталі ). Щось на зразок цього:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************

Тепер питання полягає в тому, як швидко розширюється основа цієї піраміди в міру зростання n?

Візьмемо реальний випадок, наприклад F (6)

F(6)                 *  <-- only once
F(5)                 *  <-- only once too
F(4)                 ** 
F(3)                ****
F(2)              ********
F(1)          ****************           <-- 16
F(0)  ********************************    <-- 32

Ми бачимо, що F (0) називається 32 рази, що дорівнює 2 ^ 5, що для цього зразкового випадку становить 2 ^ (n-1).

Тепер ми хочемо знати, скільки разів F (x) викликається взагалі, і ми можемо побачити, скільки разів викликається F (0) - це лише частина цього.

Якщо мисленно перемістимо всі прямі * з F (6) до F (2) у F (1), ми побачимо, що лінії F (1) і F (0) тепер рівні по довжині. Що означає, загальний раз F () викликається, коли n = 6 є 2х32 = 64 = 2 ^ 6.

Тепер, щодо складності:

O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)

3
F (3) називається лише 3 рази, а не 4 рази. Друга піраміда неправильна.
Авік

2
F (3) = 3, F (2) = 5, F (1) = 8, F (0) = 5. Я б це виправив, але не думаю, що зможу врятувати цю відповідь правою.
Бернхард Баркер

31

На MIT дуже добре обговорили цю конкретну проблему . На сторінці 5 вони зазначають, що якщо припустити, що додавання займає одну обчислювальну одиницю, час, необхідний для обчислення Fib (N), дуже тісно пов'язаний з результатом Fib (N).

Як результат, ви можете перейти безпосередньо до дуже близького наближення ряду Фібоначчі:

Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)

і, отже, скажіть, що найгірший результат наївного алгоритму - це найгірший результат

O((1/sqrt(5)) * 1.618^(N+1)) = O(1.618^(N+1))

PS: У Вікіпедії ведеться обговорення висловлення закритої форми числа N-го Фібоначчі, якщо ви хочете отримати більше інформації.


Дякуємо за посилання на курс. Дуже приємне спостереження
SwimBikeRun

16

Ви можете розширити його та мати візуалізацію

     T(n) = T(n-1) + T(n-2) <
     T(n-1) + T(n-1) 

     = 2*T(n-1)   
     = 2*2*T(n-2)
     = 2*2*2*T(n-3)
     ....
     = 2^i*T(n-i)
     ...
     ==> O(2^n)

1
Я розумію перший рядок. Але чому <в кінці є менше, ніж персонаж ? Як ви потрапили T(n-1) + T(n-1)?
Куазі Ірфан

@QuaziIrfan: D це стрілка. -> [(не менше). Вибачте за плутанину щодо останнього рядка]. Для першого рядка добре ... T(n-1) > T(n-2)Так що я можу змінити T(n-2)і поставити T(n-1). Я отримаю лише більш високу межу, яка все ще дієT(n-1) + T(n-2)
Тоні Танноус

10

Вона обмежена на нижньому кінці 2^(n/2)і на верхньому кінці на 2 ^ n (як зазначено в інших коментарях). І цікавим фактом цієї рекурсивної реалізації є те, що вона має щільну асимптотичну межу самого Fib (n). Ці факти можна підсумувати:

T(n) = Ω(2^(n/2))  (lower bound)
T(n) = O(2^n)   (upper bound)
T(n) = Θ(Fib(n)) (tight bound)

Щільну межу можна додатково зменшити, використовуючи її закриту форму, якщо хочете.


10

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

IN | OUT | TOT | LEAF | INT
 1 |   1 |   1 |   1  |   0
 2 |   1 |   1 |   1  |   0
 3 |   2 |   3 |   2  |   1
 4 |   3 |   5 |   3  |   2
 5 |   5 |   9 |   5  |   4
 6 |   8 |  15 |   8  |   7
 7 |  13 |  25 |  13  |  12
 8 |  21 |  41 |  21  |  20
 9 |  34 |  67 |  34  |  33
10 |  55 | 109 |  55  |  54

Що одразу вискакує - це кількість листових вузлів fib(n). Що потрібно ще кілька ітерацій, щоб помітити, це кількість внутрішніх вузлів fib(n) - 1. Тому загальна кількість вузлів становить 2 * fib(n) - 1.

Оскільки ви класифікуєте коефіцієнти при класифікації обчислювальної складності, остаточна відповідь є θ(fib(n)).


(Ні, я не намалював на своїй дошці повне дерево дзвінків у 10 глибин. Просто 5 глибин.);)
benkc

Приємно, мені було цікаво, скільки додаткових доповнень рекурсивно зробив Fib. Це не просто додавання 1до одного Fib(n)часу акумулятора , а цікаве, що це все-таки саме θ(Fib(n)).
Пітер Кордес

Зауважте, що деякі (більшість) рекурсивних реалізацій витрачають час на додавання 0, однак: базові випадки рекурсії є 0і 1, оскільки вони є Fib(n-1) + Fib(n-2). Тож, ймовірно, відповідь, отримана лише 3 * Fib(n) - 2з цього посилання, є більш точною для загальної кількості вузлів 2 * Fib(n) - 1.
Пітер Кордес

Я не можу отримати однакові результати на листкових вузлах. Починаючи з 0: F (0) -> 1 лист (сам); F (1) -> 1 лист (сам); F (2) -> 2 листя (F (1) і F (0)); F (3) -> 3 листя; F (5) -> 8 листочків; пр.
alexlomba87

9

Часову складність рекурсивного алгоритму можна краще оцінити, намалювавши дерево рекурсії. У цьому випадку відношення повторення для малювання дерева рекурсії було б T (n) = T (n-1) + T (n-2) + O (1) кожен крок займає O (1), що означає постійний час, оскільки він робить лише одне порівняння для перевірки значення n в, якщо блок. Дерево рекурсії виглядатиме так

          n
   (n-1)      (n-2)
(n-2)(n-3) (n-3)(n-4) ...so on

Тут давайте скажемо, що кожен рівень верхнього дерева позначається i, отже,

i
0                        n
1            (n-1)                 (n-2)
2        (n-2)    (n-3)      (n-3)     (n-4)
3   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)

дозвольмо сказати, що за певним значенням i, дерево закінчується, що у випадку, коли ni = 1, отже, i = n-1, значить, висота дерева n-1. Тепер давайте подивимося, скільки роботи виконується для кожного з n шарів дерева. Зауважте, що кожен крок займає час O (1), як зазначено у відношенні рецидивів.

2^0=1                        n
2^1=2            (n-1)                 (n-2)
2^2=4        (n-2)    (n-3)      (n-3)     (n-4)
2^3=8   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)    ..so on
2^i for ith level

оскільки i = n-1 - висота дерева, робота буде виконуватися на кожному рівні

i work
1 2^1
2 2^2
3 2^3..so on

Отже, сумарна робота, що склалася, складе суму роботи, виконаної на кожному рівні, отже, це буде 2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^ (n-1), оскільки i = n-1. За геометричним рядом ця сума дорівнює 2 ^ n, Отже, тут загальна складність часу O (2 ^ n)


2

Ну, на мою думку, це так, O(2^n)як у цій функції лише рекурсія забирає чималий час (ділити і перемагати). Ми бачимо, що вищевказана функція триватиме на дереві до тих пір, поки листя не наблизиться, коли ми досягнемо рівня, F(n-(n-1))тобто F(1). Отже, тут, коли ми записуємо складність часу, що виникає на кожній глибині дерева, ряд підсумовування:

1+2+4+.......(n-1)
= 1((2^n)-1)/(2-1)
=2^n -1

це наказ 2^n [ O(2^n) ].


1

Наївна рекурсійна версія Фібоначчі є експоненціальною за конструкцією через повторення в обчисленні:

У корені ви обчислюєте:

F (n) залежить від F (n-1) і F (n-2)

F (n-1) знову залежить від F (n-2) і F (n-3)

F (n-2) знову залежить від F (n-3) і F (n-4)

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

T (n) = T (n-1) + T (n-2) + C, з постійною C

T (n-1) = T (n-2) + T (n-3)> T (n-2) тоді

T (n)> 2 * T (n-2)

...

T (n)> 2 ^ (n / 2) * T (1) = O (2 ^ (n / 2))

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

Крім того, ви можете знайти оптимізовані версії Фібоначчі, використовуючи таке динамічне програмування:

static int fib(int n)
{
    /* memory */
    int f[] = new int[n+1];
    int i;

    /* Init */
    f[0] = 0;
    f[1] = 1;

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

    return f[n];
}

Це оптимізовано і робить лише n кроків, але також є експоненціальним.

Функції витрат визначаються від розміру вводу до кількості кроків для вирішення проблеми. Коли ви бачите динамічну версію Фібоначчі ( n кроків для обчислення таблиці) або найпростіший алгоритм, щоб дізнатися, чи є число простим ( sqrt (n) для аналізу дійсних дільників числа). ви можете подумати, що ці алгоритми є O (n) або O (sqrt (n)), але це просто не відповідає дійсності з наступної причини: Вхід до вашого алгоритму - це число: n , використовуючи двійкові позначення, розмір введення для ціле число n - log2 (n), тоді робиться змінна зміна

m = log2(n) // your real input size

нехай дізнається кількість кроків як функція від розміру вводу

m = log2(n)
2^m = 2^log2(n) = n

то вартість вашого алгоритму як функції від розміру вводу становить:

T(m) = n steps = 2^m steps

і саме тому вартість є експоненціальною.


1

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

Великий O - це O (Z ^ n), де Z - золоте відношення або приблизно 1,62.

І числа Леонардо, і числа Фібоначчі наближаються до цього співвідношення в міру збільшення n.

На відміну від інших питань Big O, немає змін у вході, і алгоритм, і алгоритм реалізації чітко визначені.

Немає потреби в складній математиці. Просто накресліть схему викликів функцій нижче та пристосуйте функцію до цифр.

Або якщо ви знайомі із золотим співвідношенням, то визнаєте його таким.

Ця відповідь правильніше, ніж прийнята відповідь, яка стверджує, що вона наблизиться до f (n) = 2 ^ n. Це ніколи не буде. Він наблизиться до f (n) = golden_ratio ^ n.

2 (2 -> 1, 0)

4 (3 -> 2, 1) (2 -> 1, 0)

8 (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)


14 (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)

            (3 -> 2, 1) (2 -> 1, 0)

22 (6 -> 5, 4)
            (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

                        (3 -> 2, 1) (2 -> 1, 0)

            (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

1
Чи можете ви надати якесь джерело для цього твердження про золоте співвідношення?
Ніко Хааз

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