Превратить рекурсивную функцию в итеративную

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

#include <iostream>
#include <stack>
using namespace std;

int n = 100;
int iMax = 2;

void fRecursive(int x)
{
    int u = 2 * x + 1;
    cout << u - 10 << ": ";

    if (u > n)
        return;

    for (int i = 0; i < iMax; ++i)
    {
        cout << u << "-" << i << "  "; 
        fRecursive(u);
        cout << "break" << endl;
    }
}

void fIterative(int x0)
{
    std::stack<int> myStack;
    myStack.push(x0);

    while (!myStack.empty())
    {
        int curx = myStack.top();
        myStack.pop();

        int u = 2 * curx + 1;
        cout << u - 10 << ": ";
        if (u > n)
            continue;

        for (int i = 0; i < iMax; ++i)
        {
            myStack.push(u);
            cout << u << "-" << i << "  ";

            cout << "break\n";
        }
    }
}

int main()
{
    int x0 = 10;

    cout << "Start Recursion" << endl;
    fRecursive(x0);
    cout << "End Recursion" << endl << endl;
    
    cout << "Start Iterative" << endl;
    fIterative(x0);
    cout << "End Iterative" << endl << endl;
    
    return 0;
}

Мне удалось поменять рекурсивные функции на итеративные, когда после вызова рекурсивной функции нет блока кода, но не удалось это сделать, когда эта функция есть.

Обычно я не «конвертирую» алгоритмы. Я понимаю, что алгоритм пытается решить, и реализую новый код. Автоматизированного способа не существует, и, возможно, использование стека тоже не так уж и хорошо. Вот что вы можете прочитать: baeldung.com/cs/factorial-recursion-to-iteration

Pepijn Kramer 18.06.2024 10:16

Способ кодирования итеративной версии может быть не намного более эффективным, чем рекурсивный (если вообще будет). Это, конечно, более непрозрачно. Вычисление полинома — более частый и конкретный пример, когда итеративные методы побеждают рекурсивные.

Martin Brown 18.06.2024 10:40

@PepijnKramer Ну, есть «автоматический»/«механический» способ, поскольку вы всегда можете заменить стек вызовов на std::vector (или какой-то реальный контейнер стека) и, по сути, сделать то же самое, что и рекурсивная реализация, эмулируя рекурсия. Тем не менее, я определенно согласен с той частью, где это нежизнеспособное решение, и что все это следует пересмотреть - или оставить как рекурсивный, если нет хорошего решения и нет реальной выгоды от того, чтобы сделать его нерекурсивным (например, только имеет небольшой стек и сталкивается с переполнением).

Christian Stieber 18.06.2024 10:43

Мое правило: максимальная глубина рекурсии для рекурсивной функции должна быть O(log n). Если глубина равна O(log n), как в этом примере, обычно лучше использовать рекурсивный метод. Однако наличие глубины O(n) вызвало бы проблемы: оно быстро рухнет с (не очень) большим n. И этот тип рекурсивной функции можно очень легко преобразовать в цикл, который легче понять, легче отлаживать, безопаснее и быстрее.

prapin 18.06.2024 14:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В качестве общего решения вы можете имитировать помещение состояния в стек. Соответствующий контекст, который вам нужен, также включает значение i, поэтому вы можете складывать пары x и i. В некоторых практических сценариях (например, в этом игрушечном примере) вы можете найти способы сделать это без этого дополнительного элемента в стеке, но я считаю, что для более общего подхода вы бы хотели, чтобы это i (или что-то еще, с чем вы выполняете итерацию) складывалось как хорошо.

Вот как это будет выглядеть для вашего примера:

#include <tuple>

void fIterative(int x) {
    std::stack<std::pair<int, int>> myStack;

    while (true) {
        int u = 2 * x + 1, i = 0;
        std::cout << u - 10 << ": ";
        myStack.push({u, 0});
        
        while (true) {
            std::tie(x, i) = myStack.top(); // or [x, i] = ... C++17
            myStack.pop();
            if (i < iMax && x <= n) {
                break;
            }
            if (myStack.empty()) {
                return;
            }
            std::cout << "break" << std::endl;
        }        
        std::cout << x << "-" << i << "  "; 
        myStack.push({x, i+1});
    }
}

В качестве примечания: я обычно не использую std::pair или std::tuple — я предпочитаю классы с участниками, имеющими описательные имена, а не «первый» и «второй». Ваш случай еще хуже, поскольку оба имеют один и тот же тип, поэтому компилятор ничего не сможет сделать, если вы случайно перепутаете использование «первого» и «второго».

Christian Stieber 18.06.2024 13:48

Другие вопросы по теме