Общий способ преобразования цикла (while / for) в рекурсию или из рекурсии в цикл?

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) Можем ли мы получить «общий общий способ» преобразования цикла (для / в то время как ……) в рекурсивный? И на какие вещи мы должны обращать внимание при обращении? Было бы лучше написать подробную информацию с некоторыми образцами и вашими теориями персудо, а также процессом конверсии.

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 …………

Однако я думаю, что трудно сохранять историю в рекурсивном режиме, потому что алгоритм на основе рекурсии фокусируется на получении последнего результата и выполнении обратного вызова. Таким образом, все это выполняется через стек, поддерживаемый языком программирования, который автоматически выделяет память в виде стека. И как мы можем «вручную» извлечь каждое из «значений стека» и вернуть несколько значений через рекурсивный алгоритм?

А как насчет "от рекурсивного алгоритма до цикла"? Могут ли они быть обращены друг к другу (я думаю, что это должно быть сделано теоретически, но я хочу более точные вещи, чтобы доказать свои мысли) .

xqMogvKW
источник
что значит "персудо"?
комнат

Ответы:

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)
}

И вуаля; хвостовая рекурсивная версия любого цикла. 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, я создал этот пример того, как функция Фибоначчи может быть переписана в цикл while в Java. Обратите внимание, что существует гораздо более простой (и эффективный) способ переписать Фибоначчи с помощью цикла while.

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;
}
Yamcha
источник