Um dos tópicos que parece surgir regularmente nas listas de discussão e discussões on-line é o mérito (ou a falta dele) de se formar em Ciência da Computação. Um argumento que parece surgir repetidamente para a parte negativa é que eles estão codificando há alguns anos e nunca usaram recursão.
Então a questão é:
- O que é recursão?
- Quando eu usaria recursão?
- Por que as pessoas não usam recursão?
recursion
computer-science
Mike Minutillo
fonte
fonte
Respostas:
Existem várias boas explicações sobre recursão neste tópico, esta resposta é sobre por que você não deve usá-lo na maioria dos idiomas. , ruby, Java e C #) iteração é muito preferível a recursividade.
Para ver o porquê, siga as etapas que os idiomas acima usam para chamar uma função:
A execução de todas essas etapas leva tempo, geralmente um pouco mais do que é necessário para percorrer um loop. No entanto, o verdadeiro problema está na etapa 1. Quando muitos programas são iniciados, eles alocam um único pedaço de memória para sua pilha e, quando ficam sem essa memória (geralmente, mas nem sempre devido à recursão), o programa trava devido a um estouro de pilha .
Portanto, nesses idiomas, a recursão é mais lenta e o torna vulnerável a falhas. Ainda existem alguns argumentos para usá-lo. Em geral, o código escrito recursivamente é mais curto e um pouco mais elegante, depois que você souber lê-lo.
Existe uma técnica que os implementadores de linguagem podem usar chamada otimização de chamada de cauda que pode eliminar algumas classes de estouro de pilha. De maneira sucinta: se a expressão de retorno de uma função é simplesmente o resultado de uma chamada de função, você não precisa adicionar um novo nível à pilha, pode reutilizar o atual para a função que está sendo chamada. Lamentavelmente, poucas implementações de linguagem imperativas têm otimização de chamada de cauda incorporada.
* Eu amo recursão. Minha linguagem estática favorita não usa loops, a recursão é a única maneira de fazer algo repetidamente. Eu simplesmente não acho que a recursão seja geralmente uma boa idéia em idiomas que não são ajustados para isso.
** A propósito, Mario, o nome típico para a sua função ArrangeString é "join", e eu ficaria surpreso se o seu idioma de escolha ainda não tiver uma implementação.
fonte
Exemplo simples de recursão em inglês.
fonte
No sentido mais básico da ciência da computação, a recursão é uma função que se autodenomina. Digamos que você tenha uma estrutura de lista vinculada:
E você deseja descobrir quanto tempo uma lista vinculada pode fazer isso com recursão:
(Isso também pode ser feito com um loop for, mas é útil como ilustração do conceito)
fonte
length(list->next)
ainda precisa retornar paralength(list)
que o último possa adicionar 1 ao resultado. Fomos escritos para passar o comprimento até agora, só então poderíamos esquecer que o chamador existia. Likeint length(const Node* list, int count=0) { return (!list) ? count : length(list->next, count + 1); }
.Sempre que uma função se chama, criando um loop, isso é recursão. Como em qualquer coisa, existem bons usos e maus usos para recursão.
O exemplo mais simples é a recursão final, onde a última linha da função é uma chamada para si mesma:
No entanto, este é um exemplo inútil, quase inútil, porque pode ser facilmente substituído por uma iteração mais eficiente. Afinal, a recursão sofre de sobrecarga de chamada de função, que no exemplo acima pode ser substancial em comparação com a operação dentro da própria função.
Portanto, todo o motivo para fazer recursão, em vez de iteração, deve ser aproveitar a pilha de chamadas para fazer algumas coisas inteligentes. Por exemplo, se você chamar uma função várias vezes com parâmetros diferentes dentro do mesmo loop, é uma maneira de realizar ramificações . Um exemplo clássico é o triângulo de Sierpinski .
Você pode desenhar um desses de maneira muito simples com recursão, onde a pilha de chamadas se ramifica em 3 direções:
Se você tentar fazer a mesma coisa com a iteração, acho que precisará de muito mais código para realizar.
Outros casos de uso comuns podem incluir a passagem de hierarquias, por exemplo, rastreadores de sites, comparações de diretórios etc.
Conclusão
Em termos práticos, a recursão faz mais sentido sempre que você precisar de ramificações iterativas.
fonte
A recursão é um método de resolver problemas com base na divisão e conquista da mentalidade. A idéia básica é que você pegue o problema original e o divida em instâncias menores (resolvidas com mais facilidade), resolva essas instâncias menores (geralmente usando o mesmo algoritmo novamente) e depois remonte-as na solução final.
O exemplo canônico é uma rotina para gerar o fatorial de n. O fatorial de n é calculado multiplicando todos os números entre 1 e n. Uma solução iterativa em C # é assim:
Não há nada de surpreendente na solução iterativa e deve fazer sentido para qualquer pessoa familiarizada com C #.
A solução recursiva é encontrada reconhecendo que o enésimo fator é n * Fato (n-1). Ou, em outras palavras, se você souber qual é um número fatorial específico, poderá calcular o próximo. Aqui está a solução recursiva em C #:
A primeira parte dessa função é conhecida como Caso Base (ou, às vezes, Cláusula Guard) e é o que impede que o algoritmo seja executado para sempre. Ele apenas retorna o valor 1 sempre que a função é chamada com um valor de 1 ou menos. A segunda parte é mais interessante e é conhecida como Etapa Recursiva . Aqui, chamamos o mesmo método com um parâmetro ligeiramente modificado (o diminuímos por 1) e, em seguida, multiplicamos o resultado com nossa cópia de n.
Quando encontrado pela primeira vez, isso pode ser um pouco confuso, por isso é instrutivo examinar como funciona quando executado. Imagine que chamamos FactRec (5). Entramos na rotina, não somos apanhados pelo caso base e, assim, acabamos assim:
Se digitarmos novamente o método com o parâmetro 4, novamente não seremos interrompidos pela cláusula guard e, portanto, terminaremos em:
Se substituirmos esse valor de retorno pelo valor de retorno acima, obtemos
Isso deve lhe dar uma pista de como a solução final é alcançada, para acelerar o processo e mostrar cada etapa no caminho:
Essa substituição final acontece quando o caso base é acionado. Neste ponto, temos uma fórmula algrebraica simples para resolver, que equivale diretamente à definição de fatoriais em primeiro lugar.
É instrutivo observar que toda chamada no método resulta em um caso base sendo acionado ou em uma chamada para o mesmo método em que os parâmetros estão mais próximos de um caso base (geralmente chamado de chamada recursiva). Se não for esse o caso, o método será executado para sempre.
fonte
FactRec()
deve ser multiplicadon
antes de retornar.A recursão está resolvendo um problema com uma função que se chama. Um bom exemplo disso é uma função fatorial. Fatorial é um problema de matemática em que fatorial de 5, por exemplo, é 5 * 4 * 3 * 2 * 1. Essa função resolve isso em C # para números inteiros positivos (não testados - pode haver um erro).
fonte
Recursão refere-se a um método que resolve um problema, resolvendo uma versão menor do problema e, em seguida, usando esse resultado mais algum outro cálculo para formular a resposta ao problema original. Muitas vezes, no processo de solução da versão menor, o método resolve uma versão ainda menor do problema, e assim por diante, até chegar a um "caso base" que é trivial de resolver.
Por exemplo, para calcular um fatorial para o número
X
, pode-se representá-lo comoX times the factorial of X-1
. Assim, o método "se repete" para encontrar o fatorial deX-1
, e depois multiplica o que quer que seja,X
para dar uma resposta final. Obviamente, para encontrar o fatorial deX-1
, ele primeiro calculará o fatorial deX-2
, e assim por diante. O caso base seria quandoX
é 0 ou 1; nesse caso, ele sabe retornar1
desde então0! = 1! = 1
.fonte
Considere um problema antigo e bem conhecido :
A definição de gcd é surpreendentemente simples:
onde mod é o operador modulo (ou seja, o restante após a divisão inteira).
Em inglês, essa definição diz que o maior divisor comum de qualquer número e zero é esse número, e o maior divisor comum de dois números m e n é o maior divisor comum de n e o restante depois de dividir m por n .
Se você quiser saber por que isso funciona, consulte o artigo da Wikipedia sobre o algoritmo euclidiano .
Vamos calcular o gcd (10, 8) como um exemplo. Cada passo é igual ao anterior:
Na primeira etapa, 8 não é igual a zero; portanto, a segunda parte da definição se aplica. 10 mod 8 = 2 porque 8 entra em 10 uma vez com o restante 2. Na etapa 3, a segunda parte se aplica novamente, mas desta vez 8 mod 2 = 0 porque 2 divide 8 sem o restante. Na etapa 5, o segundo argumento é 0, então a resposta é 2.
Você notou que o mcd aparece nos lados esquerdo e direito do sinal de igual? Um matemático diria que essa definição é recursiva porque a expressão que você está definindo se repete dentro da definição.
Definições recursivas tendem a ser elegantes. Por exemplo, uma definição recursiva para a soma de uma lista é
onde
head
é o primeiro elemento de uma lista etail
o restante da lista. Observe que sesum
repete dentro de sua definição no final.Talvez você prefira o valor máximo em uma lista:
Você pode definir a multiplicação de números inteiros não negativos recursivamente para transformá-lo em uma série de adições:
Se esse pouco sobre transformar a multiplicação em uma série de adições não fizer sentido, tente expandir alguns exemplos simples para ver como funciona.
A classificação de mesclagem tem uma definição recursiva adorável:
Definições recursivas estão por toda parte, se você souber o que procurar. Observe como todas essas definições têm casos base muito simples, por exemplo , gcd (m, 0) = m. Os casos recursivos diminuem o problema para chegar às respostas fáceis.
Com esse entendimento, agora você pode apreciar os outros algoritmos no artigo da Wikipedia sobre recursão !
fonte
O exemplo canônico é o fatorial que se parece com:
Em geral, a recursão não é necessariamente rápida (a sobrecarga de chamada de função tende a ser alta porque as funções recursivas tendem a ser pequenas, veja acima) e podem sofrer de alguns problemas (a pilha excede alguém?). Alguns dizem que tendem a ser difíceis de acertar em casos não triviais, mas eu realmente não acredito nisso. Em algumas situações, a recursão faz mais sentido e é a maneira mais elegante e clara de escrever uma função específica. Note-se que alguns idiomas favorecem soluções recursivas e as otimizam muito mais (LISP vem à mente).
fonte
Uma função recursiva é aquela que se chama. O motivo mais comum que encontrei para usá-lo é atravessar uma estrutura em árvore. Por exemplo, se eu tiver um TreeView com caixas de seleção (pense na instalação de um novo programa, na página "escolha recursos para instalar"), talvez eu queira um botão "verificar tudo", que seria algo como isto (pseudocódigo):
Portanto, você pode ver que o checkRecursively primeiro verifica o nó pelo qual ele é passado e depois se chama para cada um dos filhos desse nó.
Você precisa ter um pouco de cuidado com a recursão. Se você entrar em um loop recursivo infinito, receberá uma exceção de estouro de pilha :)
Não consigo pensar em uma razão pela qual as pessoas não devem usá-lo, quando apropriado. É útil em algumas circunstâncias, e não em outras.
Eu acho que, por ser uma técnica interessante, alguns codificadores talvez acabem usando-a com mais frequência do que deveriam, sem justificativa real. Isso deu à recursão um nome ruim em alguns círculos.
fonte
Recursão é uma expressão que se refere direta ou indiretamente a si mesma.
Considere acrônimos recursivos como um exemplo simples:
Mais exemplos na Wikipedia
fonte
A recursão funciona melhor com o que eu gosto de chamar de "problemas fractais", em que você está lidando com uma coisa grande feita de versões menores dessa coisa grande, cada uma das quais é uma versão ainda menor da coisa grande, e assim por diante. Se você precisar percorrer ou pesquisar algo como uma árvore ou estruturas idênticas aninhadas, terá um problema que pode ser um bom candidato à recursão.
As pessoas evitam a recursão por vários motivos:
A maioria das pessoas (inclusive eu) se preocupa com a programação procedural ou orientada a objetos, em oposição à programação funcional. Para essas pessoas, a abordagem iterativa (normalmente usando loops) parece mais natural.
Aqueles de nós que cortam nossos dentes de programação em programação procedural ou orientada a objetos costumam ser instruídos a evitar recursões porque são propensas a erros.
Muitas vezes nos dizem que a recursão é lenta. Chamar e retornar de uma rotina repetidamente envolve muito empilhar e estourar, o que é mais lento que o loop. Eu acho que algumas linguagens lidam com isso melhor do que outras, e essas linguagens provavelmente não são aquelas em que o paradigma dominante é processual ou orientado a objetos.
Para pelo menos algumas linguagens de programação que eu usei, lembro-me de ouvir recomendações para não usar recursão se ela ultrapassar uma certa profundidade, porque sua pilha não é tão profunda.
fonte
Uma declaração recursiva é aquela em que você define o processo do que fazer em seguida como uma combinação das entradas e o que você já fez.
Por exemplo, considere fatorial:
Mas é fácil ver o fatorial (6) também é:
Então geralmente:
Obviamente, o mais complicado da recursão é que, se você deseja definir as coisas em termos do que já fez, é preciso que haja um lugar para começar.
Neste exemplo, apenas criamos um caso especial definindo fatorial (1) = 1.
Agora vemos de baixo para cima:
Como definimos fatorial (1) = 1, chegamos ao "fundo".
De um modo geral, os procedimentos recursivos têm duas partes:
1) A parte recursiva, que define algum procedimento em termos de novas entradas combinadas com o que você "já fez" através do mesmo procedimento. (ie
factorial(n) = n*factorial(n-1)
)2) Uma peça base, que garante que o processo não se repita para sempre, dando a ele um lugar para começar (ou seja
factorial(1) = 1
)No começo, pode ser um pouco confuso, mas veja alguns exemplos e tudo deve se encaixar. Se você deseja uma compreensão muito mais profunda do conceito, estude a indução matemática. Além disso, esteja ciente de que alguns idiomas otimizam para chamadas recursivas, enquanto outros não. É muito fácil criar funções recursivas incrivelmente lentas, se você não tomar cuidado, mas também existem técnicas para torná-las com desempenho na maioria dos casos.
Espero que isto ajude...
fonte
Eu gosto desta definição:
na recursão, uma rotina resolve uma pequena parte de um problema, divide o problema em partes menores e, em seguida, se autodenomina para resolver cada uma das partes menores.
Também gosto da discussão de Steve McConnells sobre recursão no Code Complete, onde ele critica os exemplos usados nos livros de ciência da computação sobre recursão.
Eu pensei que este era um ponto muito interessante a ser levantado e pode ser uma razão pela qual a recursão é muitas vezes incompreendida.
Edição: Esta não foi uma escavação na resposta de Dav - eu não tinha visto essa resposta quando publiquei este
fonte
1.) Um método é recursivo se puder se chamar; diretamente:
ou indiretamente:
2.) Quando usar recursão
3.) As pessoas usam recursão somente quando é muito complexo escrever código iterativo. Por exemplo, técnicas de passagem em árvore, como pré-encomenda e pós-encomenda, podem ser tornadas iterativas e recursivas. Mas geralmente usamos recursivos devido à sua simplicidade.
fonte
Aqui está um exemplo simples: quantos elementos em um conjunto. (existem maneiras melhores de contar as coisas, mas este é um bom exemplo recursivo simples.)
Primeiro, precisamos de duas regras:
Suponha que você tenha um conjunto como este: [xxx]. vamos contar quantos itens existem.
Podemos representar isso como:
Ao aplicar uma solução recursiva, você geralmente tem pelo menos 2 regras:
Se traduzirmos o acima em pseudocódigo, obteremos:
Existem exemplos muito mais úteis (atravessando uma árvore, por exemplo) que eu tenho certeza que outras pessoas abordarão.
fonte
Bem, essa é uma definição bastante decente que você tem. E a Wikipedia também tem uma boa definição. Então, adicionarei outra definição (provavelmente pior) para você.
Quando as pessoas se referem à "recursão", geralmente estão falando sobre uma função que escreveram, que se chama repetidamente até terminar o trabalho. A recursão pode ser útil ao percorrer hierarquias nas estruturas de dados.
fonte
Um exemplo: Uma definição recursiva de uma escada é: Uma escada consiste em: - uma única etapa e uma escada (recursão) - ou apenas uma única etapa (terminação)
fonte
Para recorrer a um problema resolvido: não faça nada, está feito.
Para se recuperar em um problema em aberto: execute o próximo passo e depois o restante.
fonte
Em inglês simples: suponha que você possa fazer 3 coisas:
Você tem muitas maçãs à sua frente em uma mesa e deseja saber quantas maçãs existem.
O processo de repetir a mesma coisa até você terminar é chamado de recursão.
Espero que esta seja a resposta "em inglês simples" que você está procurando!
fonte
Uma função recursiva é uma função que contém uma chamada para si mesma. Uma estrutura recursiva é uma estrutura que contém uma instância de si mesma. Você pode combinar os dois como uma classe recursiva. A parte principal de um item recursivo é que ele contém uma instância / chamada de si mesmo.
Considere dois espelhos um de frente para o outro. Vimos o puro efeito infinito que eles produzem. Cada reflexão é uma instância de um espelho, que está contida em outra instância de um espelho, etc. O espelho que contém um reflexo de si mesmo é recursão.
Uma árvore de pesquisa binária é um bom exemplo de programação de recursão. A estrutura é recursiva com cada nó contendo 2 instâncias de um nó. As funções para trabalhar em uma árvore de pesquisa binária também são recursivas.
fonte
Esta é uma pergunta antiga, mas quero adicionar uma resposta do ponto de vista logístico (ou seja, não do ponto de vista de correção do algoritmo ou do ponto de vista do desempenho).
Eu uso Java para o trabalho, e Java não suporta função aninhada. Como tal, se eu quiser fazer recursão, talvez seja necessário definir uma função externa (que existe apenas porque meu código colide com a regra burocrática de Java) ou talvez precise refatorar o código completamente (o que eu realmente odeio).
Portanto, evito frequentemente a recursão e uso a operação de pilha, porque a recursão em si é essencialmente uma operação de pilha.
fonte
Você deseja usá-lo sempre que tiver uma estrutura em árvore. É muito útil na leitura de XML.
fonte
A recursão aplicada à programação basicamente chama uma função de dentro de sua própria definição (dentro de si), com parâmetros diferentes para realizar uma tarefa.
fonte
"Se eu tenho um martelo, faça tudo parecer um prego."
A recursão é uma estratégia de solução de problemas para grandes problemas, onde a cada passo apenas "transforma duas pequenas coisas em uma coisa maior", cada vez com o mesmo martelo.
Exemplo
Suponha que sua mesa esteja coberta com uma bagunça desorganizada de 1024 papéis. Como você cria uma pilha de papéis organizada e limpa usando a recursão?
Observe que isso é bastante intuitivo, além de contar tudo (o que não é estritamente necessário). Na verdade, talvez você não vá até as pilhas de 1 folha, mas poderia e ainda funcionaria. A parte importante é o martelo: com os braços, você sempre pode colocar uma pilha em cima da outra para criar uma pilha maior, e não importa (dentro da razão) o tamanho de qualquer pilha.
fonte
Recursão é o processo em que uma chamada de método é capaz de executar uma determinada tarefa. Reduz a redundância de código. A maioria das funções ou métodos recursivos deve ter uma condição para interromper a chamada recussiva, ou seja, interromper a chamada própria se uma condição for atendida - isso impede a criação de um loop infinito. Nem todas as funções são adequadas para serem usadas recursivamente.
fonte
ei, desculpe se minha opinião concorda com alguém, só estou tentando explicar a recursão em inglês puro.
suponha que você tenha três gerentes - Jack, John e Morgan. Jack gerencia 2 programadores, John-3 e Morgan-5., você dará a cada gerente 300 $ e quer saber quanto custaria. A resposta é óbvia - mas e se dois funcionários da Morgan também forem gerentes?
Aí vem a recursão. você começa do topo da hierarquia. o custo de verão é 0 $. você começa com Jack. Depois, verifique se ele tem algum gerente como funcionário. se você encontrar algum deles, verifique se eles têm gerentes como funcionários e assim por diante. Adicione 300 $ ao custo de verão sempre que encontrar um gerente. Quando você terminar com Jack, vá até John, seus funcionários e depois para Morgan.
Você nunca saberá quantos ciclos realizará antes de obter uma resposta, apesar de saber quantos gerentes possui e quantos Budget pode gastar.
A recursão é uma árvore, com galhos e folhas, chamadas pais e filhos, respectivamente. Quando você usa um algoritmo de recursão, mais ou menos conscientemente está construindo uma árvore a partir dos dados.
fonte
Em inglês simples, recursão significa repetir algumas vezes.
Na programação, um exemplo é chamar a função em si.
Veja o seguinte exemplo de cálculo de fatorial de um número:
fonte
Qualquer algoritmo exibe recursão estrutural em um tipo de dados se basicamente consiste em uma instrução switch com um caso para cada caso do tipo de dados.
por exemplo, quando você está trabalhando em um tipo
um algoritmo recursivo estrutural teria a forma
Essa é realmente a maneira mais óbvia de escrever qualquer algoritmo que funcione em uma estrutura de dados.
agora, quando você olha para os números inteiros (bem, os números naturais) conforme definidos usando os axiomas do Peano
você vê que um algoritmo recursivo estrutural em números inteiros se parece com isso
a função fatorial muito conhecida é sobre o exemplo mais trivial dessa forma.
fonte
A função chama a si mesma ou usa sua própria definição.
fonte