Загальний спосіб перетворення циклу (поки / для) в рекурсію або з рекурсії в цикл?


23

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

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

Взагалі кажучи, цикл може бути перетворений в рекурсивний.

наприклад:

for(int i=1;i<=100;++i){sum+=i;}

І пов'язана з цим рекурсивна:

int GetTotal(int number)
{
   if (number==1) return 1;   //The end number
   return number+GetTotal(number-1); //The inner recursive
}

І нарешті, щоб спростити це, потрібна рекурсивна хвоста:

int GetTotal (int number, int sum)
{
    if(number==1) return sum;
    return GetTotal(number-1,sum+number);
}

Однак у більшості випадків не так просто відповісти та проаналізувати. Я хочу знати:

1) Чи можемо ми отримати «загальний загальний спосіб» перетворення циклу (на / в той час як ……) в рекурсивний? І на які речі ми повинні звернути увагу, роблячи перетворення? Було б краще написати детальну інформацію з деякими зразками та своїми теоріями persudo, а також процес перетворення.

2) "Рекурсивний" має дві форми: лінійно-рекурсивний і хвостово-рекурсивний. То що краще конвертувати? Яке «правило» ми повинні освоїти?

3) Іноді нам потрібно зберігати "історію" рекурсивності, це легко зробити в контурному операторі:

наприклад:

List<string> history = new List<string>();
int sum=0;
for (int i=1;i<=100;++i)
{
   if(i==1) history.Add(i.ToString()+"'s result is:1.");
   else
   {
     StringBuilder sub = new StringBuilder();

      for(int j=1;j<=i;++j)
      {
          if(j==i) sbu.Append(j.ToString());
          else
          {
            sub.Append(j.ToString()+"+");
          }
      }
    sum +=i;
    sbu.Append("'s result is:"+sum+Environment.NewLine);
   }
}

Результат нижче:

Результат 1 - 1.

Результат 1 + 2 - 3.

Результат 1 + 2 + 3 - 6 …………

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

А як щодо "від рекурсивного алгоритму до циклу"? Чи можна їх перетворити один на одного (я думаю, що це слід робити теоретично, але я хочу більш точні речі, щоб довести свої думки) .


що означає "persudo"?
гнат

Відповіді:


30

Насправді слід спочатку розбити функцію:

Петля має кілька частин:

  1. заголовок та обробка перед циклом. Може оголосити деякі нові змінні

  2. умова, коли зупинити цикл.

  3. власне тіло петлі. Він змінює деякі змінні заголовка та / або параметри, передані в.

  4. хвіст; що відбувається після циклу та повернення результату.

Або виписати:

foo_iterative(params){
    header
    while(condition){
        loop_body
    }
    return tail
}

Використання цих блоків для здійснення рекурсивного дзвінка досить просто:

foo_recursive(params){
    header
    return foo_recursion(params, header_vars)
}

foo_recursion(params, header_vars){
    if(!condition){
        return tail
    }

    loop_body
    return foo_recursion(params, modified_header_vars)
}

Et voilà; хвостова рекурсивна версія будь-якої петлі. breaks і continues в корпусі циклу все одно доведеться замінити return tailі повернути foo_recursion(params, modified_header_vars)за потребою, але це досить просто.


Йти іншим шляхом складніше; частково тому, що може бути кілька рекурсивних дзвінків. Це означає, що кожен раз, коли ми з'являємо рамку стека, може бути кілька місць, де нам потрібно продовжити. Також можуть бути змінні, які нам потрібно зберегти через рекурсивний виклик та вихідні параметри виклику.

Ми можемо використовувати перемикач, щоб обійти це:

bar_recurse(params){
    if(baseCase){
        finalize
        return
    }
    body1
    bar_recurse(mod_params)
    body2
    bar_recurse(mod_params)
    body3
}


bar_iterative(params){
    stack.push({init, params})

    while(!stack.empty){
        stackFrame = stack.pop()

        switch(stackFrame.resumPoint){
        case init:
            if(baseCase){
                finalize
                break;
            }
            body1
            stack.push({resum1, params, variables})
            stack.push({init, modified_params})
            break;
        case resum1:
            body2
            stack.push({resum2, params, variables})
            stack.push({init, modified_params})
            break;
        case resum2:
            body3
            break;
        }
    }
}

0

Слідом за відповіддю @ratchet freak, я створив цей приклад того, як функцію Фібоначчі можна переписати на цикл у Java. Зауважте, що існує набагато простіший (і ефективний) спосіб переписати Фібоначчі з циклом на час.

class CallContext { //this class is similar to the stack frame

    Object[] args;

    List<Object> vars = new LinkedList<>();

    int resumePoint = 0;

    public CallContext(Object[] args) {
        this.args = args;
    }

}


static int fibonacci(int fibNumber) {
    Deque<CallContext> callStack = new LinkedList<>();
    callStack.add(new CallContext(new Object[]{fibNumber}));
    Object lastReturn = null; //value of last object returned (when stack frame was dropped)
    while (!callStack.isEmpty()) {
        CallContext callContext = callStack.peekLast();
        Object[] args = callContext.args;
        //actual logic starts here
        int arg = (int) args[0];
        if (arg == 0 || arg == 1) {
            lastReturn = arg;
            callStack.removeLast();
        } else {
            switch (callContext.resumePoint) {
                case 0: //calculate fib(n-1)
                    callStack.add(new CallContext(new Object[]{arg - 1}));
                    callContext.resumePoint++;
                    break;
                case 1: //calculate fib(n-2)
                    callContext.vars.add(lastReturn); //fib1
                    callStack.add(new CallContext(new Object[]{arg - 2}));
                    callContext.resumePoint++;
                    break;
                case 2: // fib(n-1) + fib(n-2)
                    callContext.vars.add(lastReturn); //fib2
                    lastReturn = (int) callContext.vars.get(0) + (int) callContext.vars.get(1);
                    callStack.removeLast();
                    break;
            }
        }
    }
    return (int) lastReturn;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.