Чи може рекурсивна функція мати ітерації / петлі?


12

Я вивчав рекурсивні функції, і, мабуть, це функції, які називають себе, і не використовують ітерації / цикли (інакше це не буде рекурсивною функцією).

Однак, переглядаючи приклади в Інтернеті (8-королева-рекурсивна проблема), я знайшов цю функцію:

private boolean placeQueen(int rows, int queens, int n) {
    boolean result = false;
    if (row < n) {
        while ((queens[row] < n - 1) && !result) {
            queens[row]++;
            if (verify(row,queens,n)) {
                ok = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

Задіяна whileпетля.

... тому я трохи загублений зараз. Чи можна використовувати петлі чи ні?


5
Чи складається вона. Так. То чому запитати?
Томас Едінг

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

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

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

@ThomasEding: Так, звичайно, вона компілює та працює. Але я зараз тільки вивчаю інженерію. На даний момент для мене важлива сувора концепція / визначення, а не те, як програмісти використовують її сьогодні. Тож я запитую, чи є в мене концепція правильною (яка, здається, ні).
Омега

Відповіді:


41

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

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

Ваша рекурсивна функція ідеальна для того, щоб проілюструвати структуру рекурсивного пошуку з зворотним відстеженням. Він починається з перевірки умови виходу row < nі переходить до прийняття пошукових рішень щодо рівня його рекурсії (тобто вибору можливої ​​позиції для королеви номер row). Після кожної ітерації робиться рекурсивний виклик, який спирається на конфігурацію, яку функція знайшла досі; врешті-решт, він «знижується», коли rowдосягає nрекурсивного дзвінка, який знаходиться на nглибині.


1
+1 для "правильних" рекурсивних функцій є умовними, безліч неправильних функцій, які не хе
Джиммі Хоффа

6
+1 "повторюється вниз" назавжди `Turtle () {Turtle ();}
Mr.Mindor

1
@ Mr.Mindor Я люблю цитату "Це черепахи на весь шлях вниз" :)
dasblinkenlight

Це змусило мене посміхнутися :-)
Мартійн Вербург

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

12

Загальна структура рекурсивної функції приблизно така:

myRecursiveFunction(inputValue)
begin
   if evaluateBaseCaseCondition(inputValue)=true then
       return baseCaseValue;
   else
       /*
       Recursive processing
       */
       recursiveResult = myRecursiveFunction(nextRecursiveValue); //nextRecursiveValue could be as simple as inputValue-1
       return recursiveResult;
   end if
end

Текст, який я позначив як /*recursive processing*/міг бути чим завгодно. Він може включати цикл, якщо проблема, що вирішується, вимагає цього, а також може включати рекурсивні виклики до myRecursiveFunction.


1
Це вводить в оману, оскільки це означає, що існує лише один рекурсивний виклик, і в значній мірі виключає випадки, коли рекурсивний виклик знаходиться сам у циклі (наприклад, обхід B-дерева).
Пітер Тейлор

@PeterTaylor: Так, я намагався зробити це просто.
FrustratedWithFormsDesigner

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

6

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


1

Рекурсивні виклики та петлі - це лише два способи / конструкції для здійснення ітеративного обчислення.

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

Багато мов дозволяють використовувати обидва механізми, і ви можете вибрати той, який вам більше підходить, і навіть змішати їх разом у вашому коді. В таких необхідних мовах, як C, C ++, Java тощо, ви зазвичай використовуєте a whileабо forцикл, коли вам не потрібен стек, а ви використовуєте рекурсивні дзвінки, коли вам потрібен стек (ви неявно використовуєте стек виконання). Haskell (функціональна мова) не пропонує структуру управління ітерацією, тому для виконання ітерації ви можете використовувати лише рекурсивні дзвінки.

У вашому прикладі (див. Мої коментарі):

// queens should have type int [] , not int.
private boolean placeQueen(int row, int [] queens, int n)
{
    boolean result = false;
    if (row < n)
    {
        // Iterate with queens[row] = 1 to n - 1.
        // After each iteration, you either have a result
        // in queens, or you have to try the next column for
        // the current row: no intermediate result.
        while ((queens[row] < n - 1) && !result)
        {
            queens[row]++;
            if (verify(row,queens,n))
            {
                // I think you have 'result' here, not 'ok'.
                // This is another loop (iterate on row).
                // The loop is implemented as a recursive call
                // and the previous values of row are stored on
                // the stack so that we can resume with the previous
                // value if the current attempt finds no solution.
                result = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

1

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

У восьми королевах рекурсивна частина пов'язана зі зберіганням даних, необхідних для відстеження зворотного зв'язку. Коли ви думаєте про рекурсію, цінно подумати про те, що натиснуто на стек. Стек може містити параметри пропускання значень і локальні змінні, які відіграють ключову роль в алгоритмі, або іноді речі, які не є настільки актуальними, як адреса повернення, або в цьому випадку передане значення з кількістю використовуваних маток, але не змінено алгоритмом.

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


0

Частина "створити меншу версію проблеми" може мати петлі. Поки метод називає себе, передаючи в якості параметра меншу версію проблеми, метод є рекурсивним. Звичайно, умова виходу, коли найменша можлива версія проблеми вирішена і метод повертає значення, повинен бути наданий, щоб уникнути умови переповнення стека.

Метод у вашому запитанні є рекурсивним.


0

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


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