Чи є рекурсія швидшою, ніж циклічне?


286

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

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

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


3
Іноді ітераційна процедура або формули із закритою формою для деяких рецидивів потребують століть. Я думаю, що тільки в ті часи рекурсія швидша :) lol
Pratik Deoghare

24
Якщо говорити про себе, то я більше віддаю перевагу ітерації. ;-)
Ітератор

можливий дублікат Рекурсії чи Ітерації?
nawfal


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

Відповіді:


358

Це залежить від мови, якою ви користуєтесь. Ви написали "мова-агностик", тому я наведу кілька прикладів.

У Java, C та Python рекурсія є досить дорогою порівняно з ітерацією (загалом), оскільки вимагає виділення нового кадру стека. У деяких компіляторах C можна використовувати прапор компілятора, щоб усунути цю накладну, яка перетворює певні типи рекурсії (власне, певні типи хвостових викликів) у стрибки замість викликів функцій.

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

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

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

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

Означення списків - ще одна альтернатива, але це, як правило, лише синтаксичний цукор для функцій ітерації, рекурсії або вищого порядку.


48
Я поставив це +1, і хотів би прокоментувати, що "рекурсія" і "петлі" - це саме те, що люди називають своїм кодом. Для продуктивності важливо не те, як ви називаєте речі, а, як вони складаються / інтерпретуються. Рекурсія, за визначенням, є математичним поняттям і має мало спільного з рамками стеку та складальним обладнанням.
П Швед

1
Крім того, рекурсія, як правило, є більш природним підходом у функціональних мовах, а ітерація, як правило, більш інтуїтивно зрозуміла в імперативних мовах. Різниця в продуктивності навряд чи помітна, тому просто використовуйте те, що відчуває себе більш природним для даної мови. Наприклад, ви, ймовірно, не хотіли б використовувати ітерацію в Haskell, коли рекурсія набагато простіша.
Саша Чедигов

4
Зазвичай рекурсія складається до циклів, петлі - це конструкція нижнього рівня. Чому? Оскільки рекурсія, як правило, добре ґрунтується на якійсь структурі даних, викликаючи початкову F-алгебру і дозволяє довести деякі властивості припинення, а також індуктивні аргументи про структуру (рекурсивного) обчислення. Процес, за допомогою якого рекурсія складається до циклів, - це оптимізація хвостових викликів.
Крістофер Мічінський

Найважливіше - це операції, які не виконуються. Чим більше ви "IO", тим більше вам доведеться обробляти. Дані Un-IOing (ака індексування) - це завжди найбільше підвищення продуктивності будь-якої системи, тому що вам не потрібно в першу чергу обробляти їх.
Джефф Фішер

53

чи рекурсія завжди швидша за цикл?

Ні, ітерація завжди буде швидшою, ніж рекурсія. (в архітектурі Фон Ноймана)

Пояснення:

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

Створення псевдо-обчислювальної машини з нуля:

Задайте собі запитання : що вам потрібно для обчислення значення, тобто для дотримання алгоритму та досягнення результату?

Ми встановимо ієрархію понять, починаючи з нуля і визначаючи в першу чергу основні, основні поняття, потім будуємо з ними поняття другого рівня тощо.

  1. Перша концепція: комірки пам'яті, сховище, стан . Щоб зробити щось, вам потрібні місця для зберігання кінцевих та проміжних значень результатів. Припустимо, у нас є нескінченний масив "цілих" комірок, що називається " Пам'ять , М [0..Нескінченний].

  2. Інструкції: робити щось - перетворити клітинку, змінити її значення. змінити державу . Кожна цікава інструкція виконує перетворення. Основні інструкції:

    a) Встановлення та переміщення комірок пам'яті

    • зберігати значення в пам'яті, наприклад: зберігати 5 м [4]
    • скопіюйте значення в іншу позицію: наприклад: зберігати m [4] m [8]

    б) Логіка та арифметика

    • і, або, xor, ні
    • додати, sub, mul, div. наприклад, додайте m [7] m [8]
  3. Виконавчий агент : ядро в сучасному процесорі. "Агент" - це те, що може виконувати інструкції. Агент також може бути людьми за алгоритмом на папері.

  4. Порядок кроків: послідовність інструкцій : тобто: зробіть це спочатку, зробіть це після тощо. Обов'язкова послідовність інструкцій. Навіть вирази одного рядка є "обов'язковою послідовністю вказівок". Якщо у вас є вираз із конкретним "порядком оцінювання", то у вас є кроки . Це означає, що навіть один складений вираз має неявні "кроки", а також має неявну локальну змінну (назвемо це "результат"). наприклад:

    4 + 3 * 2 - 5
    (- (+ (* 3 2) 4 ) 5)
    (sub (add (mul 3 2) 4 ) 5)  
    

    Вираз, наведений вище, передбачає 3 етапи із неявною змінною "результат".

    // pseudocode
    
           1. result = (mul 3 2)
           2. result = (add 4 result)
           3. result = (sub result 5)
    

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

  5. Інструкційний покажчик : Якщо у вас є послідовність кроків, у вас є також неявний "покажчик інструкції". Покажчик інструкцій позначає наступну інструкцію та просувається після того, як інструкція буде прочитана, але перед виконанням інструкції.

    У цій псевдо-обчислювальній машині вказівник Інструкції є частиною пам'яті . (Примітка: як правило, покажчик інструкції буде "спеціальним реєстром" в ядрі процесора, але тут ми спростимо поняття і припустимо, що всі дані (включені регістри) є частиною "Пам'яті")

  6. Перейти - Після того, як у вас є впорядкована кількість кроків та покажчик інструкцій , ви можете застосувати інструкцію " зберігати ", щоб змінити значення самого вказівника на інструкції. Ми будемо називати це специфічне використання інструкції магазину новою назвою: Jump . Ми використовуємо нову назву, оскільки простіше думати про це як про нову концепцію. Змінюючи покажчик інструкцій, ми доручаємо агенту "перейти на крок x".

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

                       1. mov 1000 m[30]
                       2. sub m[30] 1
                       3. jmp-to 2  // infinite loop
    
  8. Умовне - Умовне виконання інструкцій. За допомогою пункту "умовний" ви можете умовно виконати одну з декількох інструкцій на основі поточного стану (яку можна встановити за допомогою попередньої інструкції).

  9. Правильна ітерація : Тепер за допомогою умовного пункту ми можемо уникнути нескінченного циклу інструкції про стрибок назад . Зараз у нас є умовний цикл, а потім правильна ітерація

    1. mov 1000 m[30]
    2. sub m[30] 1
    3. (if not-zero) jump 2  // jump only if the previous 
                            // sub instruction did not result in 0
    
    // this loop will be repeated 1000 times
    // here we have proper ***iteration***, a conditional loop.
    
  10. Іменування : присвоєння імен певному розташуванню пам'яті, що містить дані, або тримає крок . Це просто "зручність". Ми не додаємо нових інструкцій, маючи можливість визначати "імена" для місць розташування пам'яті. "Іменування" - це не інструкція для агента, це лише зручність для нас. Ім'я робить код (у цей момент) простішим для читання та легшим зміною.

       #define counter m[30]   // name a memory location
       mov 1000 counter
    loop:                      // name a instruction pointer location
        sub counter 1
        (if not-zero) jmp-to loop  
    
  11. Однорівнева підпрограма : Припустимо, є ряд кроків, які потрібно часто виконувати. Ви можете зберегти кроки в названій позиції в пам'яті, а потім перейти до цієї позиції, коли потрібно виконати їх (дзвінок). В кінці послідовності вам потрібно буде повернутися до точки заклику продовжити виконання. За допомогою цього механізму ви створюєте нові інструкції (підпрограми), складаючи основні інструкції.

    Впровадження: (не потрібно нових концепцій)

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

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

    Для кращої реалізації для підпрограм: Вам потрібен STACK

  12. Стек : Ви визначаєте простір пам'яті, щоб він працював як "стек", ви можете "натиснути" значення на стеку, а також "спливати" останнє "натиснене" значення. Для реалізації стека вам знадобиться покажчик стека (подібний до вказівника інструкції), який вказує на фактичну "голову" стека. Коли ви "натискаєте" значення, зменшення покажчика стека зменшується, і ви зберігаєте це значення. Коли ви "pop", ви отримуєте значення за фактичним покажчиком стека, а потім покажчик стека збільшується.

  13. Підпрограми Тепер, коли у нас є стек, ми можемо реалізувати належні підпрограми, що дозволяють вкладені виклики . Реалізація аналогічна, але замість того, щоб зберігати покажчик інструкції у заздалегідь визначеному положенні пам'яті, ми «висуваємо» значення IP у стеку . Наприкінці підпрограми ми просто «виводимо» значення зі стека, ефективно повертаючись до інструкції після початкового виклику . Ця реалізація, що має "стек", дозволяє викликати підпрограму з іншої підпрограми. За допомогою цієї реалізації ми можемо створити кілька рівнів абстракції, визначаючи нові інструкції як підпрограми, використовуючи основні інструкції або інші підпрограми як будівельні блоки.

  14. Рекурсія : Що відбувається, коли підпрограма викликає себе ?. Це називається "рекурсія".

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

    Рішення: Щоб дозволити рекурсію, підпрограми повинні зберігати локальні проміжні результати у стеці , тому для кожного рекурсивного виклику (прямого або непрямого) проміжні результати зберігаються в різних місцях пам'яті.

...

досягнувши рекурсії, ми зупиняємось тут.

Висновок:

У архітектурі Фон Неймана явно "Ітерація" є більш простим / базовим поняттям, ніж "Рекурсія" . У нас є форма "Ітерація" на рівні 7, а "Рекурсія" - на рівні 14 ієрархії концепцій.

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

Хто з них "кращий"?

  • Ви повинні використовувати "ітерацію", коли ви обробляєте прості послідовні структури даних і всюди, де це буде робити "простий цикл".

  • Ви повинні використовувати "рекурсію", коли вам потрібно обробити рекурсивну структуру даних (я люблю їх називати "структурами фрактальних даних") або коли рекурсивне рішення явно більш "елегантне".

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

Нарешті, зауважте, що у вас є багато можливостей використовувати рекурсію. У вас є рекурсивні структури даних скрізь, ви дивитесь на одну: частини DOM, що підтримують те, що ви читаєте, - це RDS, вираз JSON - RDS, ієрархічна файлова система на вашому комп'ютері - RDS, тобто: у вас є кореневий каталог, що містить файли та каталоги, кожен каталог, що містить файли та каталоги, кожен із цих каталогів, що містять файли та каталоги ...


2
Ви припускаєте, що ваш прогрес 1) необхідний і 2), щоб він там зупинився, як і раніше. Але 1) це не обов'язково (наприклад, рекурсію можна перетворити на стрибок, як пояснила прийнята відповідь, тому стек не потрібен), і 2) він не повинен зупинятися на цьому (наприклад, зрештою, ви Ви отримаєте одночасну обробку, яка може потребувати блокування, якщо у вас є стан, що змінюється, коли ви впровадили на другому кроці, тому все сповільнюється; тоді як незмінне рішення, як функціональне / рекурсивне, уникне блокування, так що може бути швидше / паралельніше) .
hmijail сумує у відставці

2
"рекурсія може перетворитися на стрибок" є хибною. По-справжньому корисну рекурсію не можна перетворити на стрибок. Хвостовий виклик "рекурсія" - це особливий випадок, коли ви кодуєте "як рекурсію" щось, що може бути спрощене в цикл компілятором. Крім того, ви співвідносите "незмінне" з "рекурсією", це ортогональні поняття.
Lucio M. Tato

"По-справжньому корисну рекурсію не можна перетворити на стрибок" -> тож оптимізація хвостових викликів якось марна? Крім того, незмінні та рекурсії можуть бути ортогональними, але ви робите циклічне посилання за допомогою змінних лічильників - подивіться на свій крок 9. Мені здається, що ви думаєте, що циклічність та рекурсія - це принципово різні поняття; вони ні. stackoverflow.com/questions/2651112 / ...
hmijail оплакує resignees

@hmijail Я думаю, що краще слово, ніж «корисний» - «правда». Хвостова рекурсія не є справжньою рекурсією, оскільки вона просто використовує функцію, що викликає синтаксис, щоб замаскувати безумовне розгалуження, тобто ітерацію. Справжня рекурсія забезпечує нам стек-зворотний трек. Однак хвостова рекурсія все ще виразна, що робить її корисною. Властивості рекурсії, які полегшують чи простіше аналізувати код на предмет коректності, надаються ітераційному коду, коли він виражається за допомогою хвостових викликів. Хоча це іноді трохи компенсується додатковими ускладненнями в хвостовій версії, як додаткові параметри.
Каз

34

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

У мене був випадок, коли переписування рекурсивного алгоритму на Java робило це повільніше.

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


2
+1 для " спочатку напишіть це найприроднішим чином ", а особливо " лише оптимізуйте, якщо профілювання покаже, що це критично важливо "
TripeHound

2
+1 за підтвердження того, що апаратний стек може бути швидшим, ніж програмне забезпечення, вбудоване вручну, у групі. Ефективно показує, що всі відповіді "ні" невірні.
sh1

12

Рекурсія хвоста так само швидко, як і петля. У багатьох функціональних мовах в них реалізована хвостова рекурсія.


35
Рекурсія хвоста може бути такою ж швидкою, як циклічне, коли буде впроваджена оптимізація хвостових викликів: c2.com/cgi/wiki?TailCallOptimization
Йоахім Зауер

12

Поміркуйте, що абсолютно необхідно зробити для кожної, ітерації та рекурсії.

  • ітерація: перехід до початку циклу
  • рекурсія: перехід до початку виклику функції

Ви бачите, що тут мало місця для відмінностей.

(Я припускаю, що рекурсія є хвостовим викликом, а компілятор усвідомлює цю оптимізацію).


9

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

Рекурсивні алгоритми TL; DR мають, як правило, гірше поведінку кешу, ніж ітеративні.


6

Більшість відповідей тут неправильні . Правильна відповідь - це залежить . Наприклад, ось дві функції С, які проходять через дерево. Спочатку рекурсивний:

static
void mm_scan_black(mm_rc *m, ptr p) {
    SET_COL(p, COL_BLACK);
    P_FOR_EACH_CHILD(p, {
        INC_RC(p_child);
        if (GET_COL(p_child) != COL_BLACK) {
            mm_scan_black(m, p_child);
        }
    });
}

І ось та сама функція, реалізована за допомогою ітерації:

static
void mm_scan_black(mm_rc *m, ptr p) {
    stack *st = m->black_stack;
    SET_COL(p, COL_BLACK);
    st_push(st, p);
    while (st->used != 0) {
        p = st_pop(st);
        P_FOR_EACH_CHILD(p, {
            INC_RC(p_child);
            if (GET_COL(p_child) != COL_BLACK) {
                SET_COL(p_child, COL_BLACK);
                st_push(st, p_child);
            }
        });
    }
}

Не важливо розуміти деталі коду. Просто pце вузли, і це P_FOR_EACH_CHILDробить ходьба. В ітераційній версії нам потрібен явний стек, stна який вузли натискають, а потім вискакують та маніпулюють.

Рекурсивна функція працює набагато швидше, ніж ітеративна. Причина полягає в тому, що в останньому для кожного елемента потрібна CALLфункція a, st_pushа потім інша st_pop.

У першому у вас є лише рекурсивний CALLдля кожного вузла.

Крім того, доступ до змінних на стакані виклику неймовірно швидкий. Це означає, що ви читаєте з пам'яті, яка, ймовірно, завжди знаходиться у найпотаємнішому кеші. З іншого боку, явний стек повинен бути підкріплений malloc: пам’яттю ed з тієї купи, яка набагато повільніше отримує доступ.

З ретельною оптимізацією, такими як вбудовування st_pushта st_popя можу досягти приблизно паритету з рекурсивним підходом. Але, принаймні, на моєму комп’ютері вартість доступу до купи пам'яті більша, ніж вартість рекурсивного дзвінка.

Але це обговорення здебільшого суперечливе, оскільки рекурсивна ходьба по дереву неправильна . Якщо у вас досить велике дерево, у вас не вистачить простору столових викликів, тому необхідно використовувати ітеративний алгоритм.


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

1
Робили попереднє замовлення бінарного дерева 7 вузлів 10 ^ 8 разів. Рекурсія 25ns. Явний стек (зв'язаний з перевіркою чи ні - не має великої різниці) ~ 15ns. Рекурсії потрібно зробити більше (зареєструвати збереження та відновлення + (як правило) більш жорсткі вирівнювання кадру), а також просто натискання та стрибки. (І погіршується з PLT у динамічно пов'язаних libs.) Вам не потрібно купувати явний стек. Ви можете зробити перешкоду, перший кадр якої знаходиться у звичайному стеку викликів, щоб ви не жертвували місцем кешу для найбільш поширеного випадку, коли ви не перевищуєте перший блок.
PSkocik

3

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

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

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

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


Ось чому кілька випадків рекурсії часто оптимізуються компіляторами мовами, де часто використовується рекурсія. Наприклад, у F #, окрім повної підтримки хвостових рекурсивних функцій з кодом .tail, ви часто бачите рекурсивну функцію, складену як цикл.
em70

Так. Хвостова рекурсія іноді може бути найкращою з обох світів - функціонально «відповідний» спосіб реалізації рекурсивного завдання та ефективність використання циклу.
Бурштин

1
Це, взагалі, не правильно. У деяких середовищах мутація (яка взаємодіє з GC) дорожча за хвостову рекурсію, яка перетворюється на простіший цикл у виході, який не використовує додатковий кадр стека.
Дітріх Епп

2

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


1

Функціональне програмування більше стосується " що ", а не " як" ".

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

Найважливіше з точки зору програміста - це читабельність та ремонтопридатність, а не оптимізація насамперед. Знову ж таки, "передчасна оптимізація - корінь усього зла".


0

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


0

За теорією це одне і те ж. Рекурсія і цикл з однаковою складністю O () працюватимуть з однаковою теоретичною швидкістю, але, звичайно, реальна швидкість залежить від мови, компілятора та процесора. Приклад з потужністю числа може бути закодований ітераційним способом з O (ln (n)):

  int power(int t, int k) {
  int res = 1;
  while (k) {
    if (k & 1) res *= t;
    t *= t;
    k >>= 1;
  }
  return res;
  }

1
Великий О "пропорційний". Так є і те O(n), і інше , але для одного може знадобитися xчас довше, ніж інший n.
ctrl-alt-delor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.