Maneira geral de converter um loop (while / for) em recursão ou de uma recursão para um loop?


23

Esse problema está focado principalmente no algoritmo, talvez algo abstrato e mais acadêmico.

O exemplo está oferecendo um pensamento, quero uma maneira genérica; portanto, o exemplo é usado apenas para nos tornar mais claros sobre seus pensamentos.

De um modo geral, um loop pode ser convertido em recursivo.

por exemplo:

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

E a recursiva relacionada é:

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

E, finalmente, para simplificar isso, é necessário um recursivo de cauda:

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

No entanto, a maioria dos casos não é tão fácil de responder e analisar. O que eu quero saber é:

1) Podemos obter uma "maneira geral comum" de converter um loop (por / enquanto ...) em um recursivo? E em que tipos de coisas devemos prestar atenção ao fazer a conversão? Seria melhor escrever informações detalhadas com algumas amostras e suas teorias de persudo, além do processo de conversão.

2) "Recursivo" tem duas formas: Linearmente recursivo e Tail-Recursivo. Então, qual é o melhor para converter? Que "regra" devemos dominar?

3) Às vezes, precisamos manter o "histórico" do recursivo, isso é facilmente feito em uma declaração de loop:

por exemplo:

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

O resultado abaixo é:

O resultado de 1 é 1.

O resultado de 1 + 2 é 3.

O resultado de 1 + 2 + 3 é 6 …………

No entanto, acho difícil manter o histórico de forma recursiva, porque um algoritmo recursivo se concentra em obter o último resultado e fazer um retorno de chamada. Portanto, tudo isso é feito através da pilha mantida pela linguagem de programação que atribui memória automaticamente na forma de pilha. E como podemos "manualmente" remover cada um dos "valores da pilha" e retornar vários valores por meio de um algoritmo recursivo?

E o que dizer "de um algoritmo recursivo a um loop"? Eles podem ser convertidos um para o outro (acho que deve ser feito teoricamente, mas quero coisas mais precisas para provar meus pensamentos) .


o que significa "persudo"?
Gnat

Respostas:


30

Na verdade, você deve dividir a função primeiro:

Um loop tem algumas partes:

  1. o cabeçalho e o processamento antes do loop. Pode declarar algumas novas variáveis

  2. a condição, quando parar o loop.

  3. o corpo do loop real. Altera algumas das variáveis ​​do cabeçalho e / ou os parâmetros passados.

  4. a calda; o que acontece após o loop e retornar o resultado.

Ou para escrever:

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

Usar esses blocos para fazer uma chamada recursiva é bem direto:

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à; uma versão recursiva final de qualquer loop. breaks e continues no corpo do loop ainda terão que ser substituídos return taile retornados foo_recursion(params, modified_header_vars)conforme necessário, mas isso é bastante simples.


Indo para o outro lado é mais complicado; em parte porque pode haver várias chamadas recursivas. Isso significa que cada vez que colocamos um quadro de pilha, pode haver vários locais onde precisamos continuar. Também pode haver variáveis ​​que precisamos salvar na chamada recursiva e nos parâmetros originais da chamada.

Podemos usar uma opção para solucionar isso:

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

Seguindo a resposta do @ratchet freak, criei este exemplo de como a função Fibonacci pode ser reescrita para um loop while em Java. Observe que há uma maneira muito mais simples (e eficiente) de reescrever o Fibonacci com um loop 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;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.