Як визначити час виконання подвійної рекурсивної функції?


15

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

Наприклад (у псевдокоді):

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

Або щось у цьому напрямку.

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


2
Це домашнє завдання?
Бернард

5
Ні, це літня пора, і я люблю вчитися. Я думаю, що вперед, а не дозволяти моєму мозку перетворитися на кашку.
if_zero_equals_one

11
Гаразд, зрозумів. Для тих, хто голосує, перенести це на переповнення стека: це тут на тему, а поза темою - на переповнення стека. Programmers.SE призначений для концептуальних питань, дошки-y; Переповнення стека призначене для впровадження, питання "кодування-поки-я-кодування".

3
Дякую, це причина, що я зробив це в першу чергу. Крім того, краще знати, як ловити рибу, ніж отримувати рибу.
if_zero_equals_one

1
У цьому конкретному випадку це як правило нескінченна рекурсія, оскільки b (a (0)) викликає нескінченно багато інших b (a (0)) доданків. Було б інакше, якби це була математична формула. Якби ваша установка була іншою, вона б працювала інакше. Як і в математиці, в cs деякі проблеми мають рішення, деякі - ні, деякі - легкі, деякі - ні. Є багато взаємно-рекурсивних випадків, коли рішення існує. Іноді, щоб не підірвати стек, потрібно було б використовувати батутний візерунок.
Робота

Відповіді:


11

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

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

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

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

def recursive (n):
    if 0 == n%2:
        return 1 + recursive(n/2)
    elif 1 == n:
        return 0
    else:
        return recursive(3*n + 1)

Це в даний час невідомо , буде чи завжди добре визначена ця функція, НЕ кажучи вже про його виконання є.


5

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


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

1
Я не впевнений, що в загальному випадку є рішення. Для того, щоб Big-O мав сенс, ви повинні знати, чи алгоритм колись зупиниться. Існують деякі рекурсивні алгоритми, де потрібно запустити обчислення, перш ніж знати, скільки часу це займе (наприклад, визначити, чи належить точка до набору Мандлброта чи ні).
jimreed

Не завжди, aлише дзвінки, bякщо число, передане, становить> = 0. Але так, існує нескінченна петля.
btilly

1
@btilly приклад змінили після того, як я опублікував свою відповідь.
jimreed

1
@jimreed: І це було знову змінено. Я б видалив свій коментар, якби міг.
btilly

4

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

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

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

T_a(x) = if x ≤ 0 then 1 else T_b(x-1) + T_a(x-1)
T_b(x) = if x ≤ -5 then 1 else T_b(T_a(x-1))

Що далі? Тепер у вас є математична задача: вам потрібно вирішити ці функціональні рівняння. Підхід, який часто працює, полягає в тому, щоб перетворити ці рівняння на цілі функції на рівняння на аналітичних функціях та використовувати обчислення для їх вирішення, інтерпретуючи функції T_aта T_bяк генеруючі функції .

Щодо генерації функцій та інших дискретних тем з математики, я рекомендую книгу " Конкретна математика " Рональда Грема, Дональда Кнута та Орена Паташника.


1

Як зазначали інші, аналіз рекурсії може отримати дуже важкий результат дуже швидко. Ось ще один приклад такої речі: http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_F Female_and_Male_ наслідки важко обчислити відповідь та час їх виконання. Це пов’язано з цими взаємно-рекурсивними функціями, що мають "складну форму".

Як би то не було, давайте розглянемо цей простий приклад:

http://pramode.net/clojure/2010/05/08/clojure-trampoline/

(declare funa funb)
(defn funa [n]
  (if (= n 0)
    0
    (funb (dec n))))
(defn funb [n]
  (if (= n 0)
    0
    (funa (dec n))))

Почнемо з спроби обчислити funa(m), m > 0:

funa(m) = funb(m - 1) = funa(m - 2) = ... funa(0) or funb(0) = 0 either way.

Час виконання:

R(funa(m)) = 1 + R(funb(m - 1)) = 2 + R(funa(m - 2)) = ... m + R(funa(0)) or m + R(funb(0)) = m + 1 steps either way

Тепер виберемо ще один, трохи складніший приклад:

Натхненний http://planetmath.org/encyclopedia/MutualRecursion.html , який добре читається сам по собі, давайте подивимось на: "" "Числа Фібоначчі можна інтерпретувати за допомогою взаємної рекурсії: F (0) = 1 і G (0 ) = 1, при F (n + 1) = F (n) + G (n) і G (n + 1) = F (n). ""

Отже, який час виконання F? Ми підемо іншим шляхом.
Ну, R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
Тепер R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
Не важко зрозуміти, що R (F (m)) = F (m) - наприклад, кількість викликів функції, необхідних для обчислення числа Фібоначчі в індексі i, дорівнює значенню числа Фібоначчі за індексом i. Це передбачало, що додавання двох чисел разом набагато швидше, ніж виклик функції. Якби це не було, то це було б правдою: R (F (1)) = R (F (0)) + 1 + R (G (0)), і аналіз цього був би складнішим, можливо, без легкого рішення закритої форми.

Закриту форму послідовності Фібоначчі не обов'язково легко винаходити, не кажучи вже про деякі складніші приклади.


0

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

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

bприпиняється лише y <= -5тому, що якщо ви підключите будь-яке інше значення, то у вас буде термін форми b(a(y-1)). Якщо ви зробите трохи більше розширення, ви побачите, що термін форми b(a(y-1))врешті-решт призводить до терміна, b(1010)який призводить до терміна, b(a(1009))який знову призводить до терміна b(1010). Це означає, що ви не можете підключити будь-яке значення до aцього, яке не задовольняє, x <= -4оскільки якщо ви закінчитеся з нескінченним циклом, де значення для обчислення залежить від значення, яке потрібно обчислити. Тому по суті цей приклад має постійний час роботи.

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


-5

Виконання як у Big-O?

Це легко: O (N) - якщо припустити, що існує умова припинення.

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

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


2
Ви визначаєте продуктивність Big-O, приймаючи найвищий порядок будь-якого викликаного методу і множуючи його на порядок виклику методу. Однак, як тільки ви почнете говорити про експоненціальну та фактографічну ефективність, ви можете ігнорувати поліноміальну ефективність. Я вважаю, що те ж саме має місце і при порівнянні експоненціальної та факторіальної: факторна перемога. Мені ніколи не доводилося аналізувати систему, яка була і експоненціальною, і факторіальною.
Анон

5
Це неправильно. Рекурсивними формами обчислення n-го числа Фібоначчі і швидкості є O(2^n)і O(n*log(n)), відповідно.
unpythonic

1
Не роблячи фантазії, я хотів би направити вас на amazon.com/Introduction-Algorithms-Second-Thomas-Cormen/dp/… і спробувати поглянути на цей веб-сайт cstheory.stackexchange.com .
Брайан Харрінгтон

4
Чому люди проголосували за цю жахливо неправильну відповідь? Виклик методу займає час, пропорційний часу, який займає метод. У цьому випадку метод aвикликає bта bдзвінки, aтому ви не можете просто припустити, що будь-який метод потребує часу O(1).
btilly

2
@Anon - Плакат вимагав довільно подвійної рекурсивної функції, а не лише тієї, що показана вище. Я наводив два приклади простої рекурсії, які не відповідають вашим поясненням. Це тривіальне перетворення старих стандартів у "подвійну рекурсивну" форму, таку, яка була експоненціальною (відповідає вашим застереженням) та такою, яка не є (не охоплюється).
unpythonic
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.