Um tópico do reddit trouxe uma pergunta aparentemente interessante:
Funções recursivas de cauda podem ser convertidas trivialmente em funções iterativas. Outros, podem ser transformados usando uma pilha explícita. Toda recursão pode ser transformada em iteração?
O exemplo (contador?) Da postagem é o par:
(define (num-ways x y)
(case ((= x 0) 1)
((= y 0) 1)
(num-ways2 x y) ))
(define (num-ways2 x y)
(+ (num-ways (- x 1) y)
(num-ways x (- y 1))
language-agnostic
recursion
iteration
Tordek
fonte
fonte
choose
x = (x + y)! / (X! Y!), Que não precisa de recursão.Respostas:
Você sempre pode transformar uma função recursiva em iterativa? Sim, absolutamente, e a tese de Church-Turing prova se a memória serve. Em termos leigos, afirma que o que é computável por funções recursivas é computável por um modelo iterativo (como a máquina de Turing) e vice-versa. A tese não diz exatamente como fazer a conversão, mas diz que é definitivamente possível.
Em muitos casos, é fácil converter uma função recursiva. Knuth oferece várias técnicas em "The Art of Computer Programming". E, frequentemente, algo calculado recursivamente pode ser calculado por uma abordagem completamente diferente em menos tempo e espaço. O exemplo clássico disso são os números de Fibonacci ou suas seqüências. Você certamente encontrou esse problema em seu plano de graduação.
Por outro lado, certamente podemos imaginar um sistema de programação tão avançado que trate uma definição recursiva de uma fórmula como um convite para memorizar resultados anteriores, oferecendo assim o benefício de velocidade sem o incômodo de informar ao computador exatamente quais etapas siga no cálculo de uma fórmula com uma definição recursiva. Dijkstra quase certamente imaginou esse sistema. Ele passou muito tempo tentando separar a implementação da semântica de uma linguagem de programação. Por outro lado, suas linguagens de programação não determinísticas e de multiprocessamento estão em uma liga acima do programador profissional praticante.
Na análise final, muitas funções são simplesmente mais fáceis de entender, ler e escrever de forma recursiva. A menos que haja um motivo convincente, você provavelmente não deve (manualmente) converter essas funções em um algoritmo explicitamente iterativo. Seu computador manipulará esse trabalho corretamente.
Eu posso ver uma razão convincente. Suponha que você tenha um sistema de protótipo em uma linguagem de nível super alto, como [ vestindo roupas íntimas de amianto ] Scheme, Lisp, Haskell, OCaml, Perl ou Pascal. Suponha que as condições sejam tais que você precise de uma implementação em C ou Java. (Talvez seja política.) Então você certamente poderia ter algumas funções escritas recursivamente, mas que, traduzidas literalmente, explodiriam seu sistema de tempo de execução. Por exemplo, a recursão infinita da cauda é possível no esquema, mas o mesmo idioma causa um problema para os ambientes C existentes. Outro exemplo é o uso de funções lexicamente aninhadas e escopo estático, que Pascal suporta, mas C não.
Nessas circunstâncias, você pode tentar superar a resistência política ao idioma original. Você pode se reimplementar mal com o Lisp, como na décima lei de Greenspun. Ou você pode apenas encontrar uma abordagem completamente diferente da solução. Mas, de qualquer forma, certamente há um caminho.
fonte
Sim. Uma prova formal simples é mostrar que a recursão µ e um cálculo não recursivo, como o GOTO, são Turing completos. Como todos os cálculos completos de Turing são estritamente equivalentes em seu poder expressivo, todas as funções recursivas podem ser implementadas pelo cálculo não-recursivo de Turing-completo.
Infelizmente, não consigo encontrar uma definição formal boa do GOTO online, então aqui está uma:
Um programa GOTO é uma sequência de comandos P executados em uma máquina de registro, de modo que P seja um dos seguintes:
HALT
, o que interrompe a execuçãor = r + 1
onder
está algum registror = r – 1
onder
está algum registroGOTO x
ondex
está um rótuloIF r ≠ 0 GOTO x
onder
existe qualquer registro ex
é um rótuloNo entanto, as conversões entre funções recursivas e não recursivas nem sempre são triviais (exceto pela reimplementação manual irracional da pilha de chamadas).
Para mais informações, consulte esta resposta .
fonte
A recursão é implementada como pilhas ou construções semelhantes nos intérpretes ou compiladores reais. Portanto, você certamente pode converter uma função recursiva em uma contraparte iterativa, porque é assim que sempre é feita (se automaticamente) . Você apenas duplicará o trabalho do compilador de maneira ad-hoc e provavelmente de uma maneira muito feia e ineficiente.
fonte
Basicamente, sim, em essência o que você acaba fazendo é substituir as chamadas de método (que implicitamente colocam o estado na pilha) em pushs explícitos da pilha para lembrar onde a 'chamada anterior' chegou e depois executar o 'método chamado' em vez de.
Eu imagino que a combinação de um loop, uma pilha e uma máquina de estado possa ser usada para todos os cenários, basicamente simulando as chamadas de método. Se isso será ou não "melhor" (mais rápido ou mais eficiente em algum sentido) não é realmente possível dizer em geral.
fonte
O fluxo de execução da função recursiva pode ser representado como uma árvore.
A mesma lógica pode ser feita por um loop, que usa uma estrutura de dados para percorrer a árvore.
O primeiro percurso de profundidade pode ser feito usando uma pilha, o primeiro percurso de largura pode ser feito usando uma fila.
Então a resposta é sim. Por que: https://stackoverflow.com/a/531721/2128327 .
fonte
Sim, usando explicitamente uma pilha (mas a recursão é muito mais agradável de ler, IMHO).
fonte
Sim, sempre é possível escrever uma versão não recursiva. A solução trivial é usar uma estrutura de dados da pilha e simular a execução recursiva.
fonte
Em princípio, é sempre possível remover a recursão e substituí-la pela iteração em uma linguagem que possui um estado infinito, tanto para estruturas de dados quanto para a pilha de chamadas. Essa é uma consequência básica da tese de Church-Turing.
Dada uma linguagem de programação real, a resposta não é tão óbvia. O problema é que é bem possível ter um idioma em que a quantidade de memória que pode ser alocada no programa seja limitada, mas em que a quantidade de pilha de chamadas que pode ser usada seja ilimitada (C de 32 bits em que o endereço das variáveis da pilha não é acessível). Nesse caso, a recursão é mais poderosa simplesmente porque possui mais memória que pode usar; não há memória explicitamente alocável para emular a pilha de chamadas. Para uma discussão detalhada sobre isso, consulte esta discussão .
fonte
Todas as funções computáveis podem ser calculadas pelas Máquinas de Turing e, portanto, os sistemas recursivos e as máquinas de Turing (sistemas iterativos) são equivalentes.
fonte
Às vezes, substituir a recursão é muito mais fácil do que isso. A recursão costumava ser a coisa da moda ensinada no CS na década de 90, e muitos desenvolvedores comuns da época pensavam que se você resolvesse algo com recursão, era uma solução melhor. Então eles usariam recursão em vez de voltar para trás para ordem inversa, ou coisas tolas assim. Às vezes, remover a recursão é um tipo simples de exercício "duh, isso era óbvio".
Agora isso é menos problemático, pois a moda mudou para outras tecnologias.
fonte
A remoção da recursão é um problema complexo e é viável em circunstâncias bem definidas.
Os casos abaixo estão entre os fáceis:
fonte
Além da pilha explícita, outro padrão para converter recursão em iteração é com o uso de um trampolim.
Aqui, as funções retornam o resultado final ou um encerramento da chamada de função que, de outra forma, teria sido executada. Então, a função inicial (trampolim) continua invocando os fechamentos retornados até que o resultado final seja alcançado.
Essa abordagem funciona para funções recursivas mutuamente, mas receio que só funcione para chamadas de cauda.
http://en.wikipedia.org/wiki/Trampoline_(computadores)
fonte
Eu diria que sim - uma chamada de função nada mais é do que um goto e uma operação de pilha (grosso modo). Tudo o que você precisa fazer é imitar a pilha criada enquanto invoca funções e fazer algo semelhante ao goto (você pode imitar o gotos com idiomas que não possuem explicitamente essa palavra-chave também).
fonte
Dê uma olhada nas seguintes entradas na wikipedia, você pode usá-las como ponto de partida para encontrar uma resposta completa à sua pergunta.
Segue um parágrafo que pode lhe dar uma dica sobre por onde começar:
Veja também o último parágrafo desta entrada .
fonte
Mais detalhes: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
fonte
No entanto, recursão significa que uma função se chamará, quer você goste ou não. Quando as pessoas estão falando sobre se as coisas podem ou não ser feitas sem recursão, elas querem dizer isso e você não pode dizer "não, isso não é verdade, porque eu não concordo com a definição de recursão" como uma declaração válida.
Com isso em mente, praticamente tudo o que você diz não faz sentido. A única outra coisa que você diz que não é bobagem é a ideia de que você não pode imaginar programar sem uma pilha de chamadas. Isso é algo que foi feito há décadas até que o uso de uma pilha de chamadas se tornou popular. As versões antigas do FORTRAN não tinham uma pilha de chamadas e funcionavam muito bem.
A propósito, existem linguagens completas de Turing que implementam apenas a recursão (por exemplo, SML) como um meio de loop. Também existem linguagens completas de Turing que implementam apenas a iteração como um meio de loop (por exemplo, FORTRAN IV). A tese de Church-Turing prova que tudo o que é possível em idiomas somente com recursão pode ser feito em um idioma não recursivo e vica-versa pelo fato de que ambos têm a propriedade de garantir a integridade.
fonte
Aqui está um algoritmo iterativo:
fonte