A idéia de recursão não é muito comum no mundo real. Então, parece um pouco confuso para os programadores iniciantes. Embora, eu acho, eles se acostumem ao conceito gradualmente. Então, qual pode ser uma boa explicação para eles entenderem a idéia facilmente?
74
Respostas:
Para explicar a recursão , uso uma combinação de explicações diferentes, geralmente para ambas tentar:
Para iniciantes, o Wolfram | Alpha o define em termos mais simples do que a Wikipedia :
Matemáticas
Se o seu aluno (ou a pessoa que você explica também, de agora em diante eu direi aluno) tem pelo menos algum conhecimento matemático, obviamente já encontrou recursão estudando séries e sua noção de recursividade e sua relação de recorrência .
Uma maneira muito boa de começar é demonstrar com uma série e dizer que é simplesmente do que se trata a recursão:
Normalmente, você recebe um "huh huh, whatev '", na melhor das hipóteses, porque eles ainda não o usam, ou mais provavelmente apenas um ronco muito profundo.
Exemplos de codificação
De resto, na verdade, é uma versão detalhada do que apresentei no Adendo da minha resposta para a pergunta que você apontou sobre ponteiros (trocadilho).
Nesta fase, meus alunos geralmente sabem como imprimir algo na tela. Supondo que estamos usando C, eles sabem como imprimir um único caractere usando
write
ouprintf
. Eles também sabem sobre os loops de controle.Eu costumo recorrer a alguns problemas de programação repetitivos e simples até que eles o entendam:
Fatorial
O fatorial é um conceito matemático muito simples de entender, e a implementação está muito próxima de sua representação matemática. No entanto, eles podem não conseguir no início.
Alfabetos
A versão em alfabeto é interessante para ensiná-los a pensar sobre a ordem de suas declarações recursivas. Como nos ponteiros, eles lançam linhas aleatoriamente para você. O objetivo é levá-los à conclusão de que um loop pode ser invertido modificando as condições OU apenas invertendo a ordem das instruções em sua função. É aí que a impressão do alfabeto ajuda, pois é algo visual para eles. Basta que eles escrevam uma função que imprima um caractere para cada chamada e se auto-recursivamente para escrever a próxima (ou anterior).
Fãs de FP, pule o fato de que imprimir coisas no fluxo de saída é um efeito colateral por enquanto ... Não vamos ficar muito irritantes na parte frontal do FP. (Mas se você usar um idioma com suporte à lista, sinta-se à vontade para concatenar uma lista a cada iteração e apenas imprimir o resultado final. Mas geralmente eu os inicio com C, que infelizmente não é o melhor para esse tipo de problemas e conceitos) .
Exponenciação
O problema da exponenciação é um pouco mais difícil ( nesta fase do aprendizado). Obviamente, o conceito é exatamente o mesmo que para um fatorial e não há complexidade adicional ... exceto que você possui vários parâmetros. E isso geralmente é suficiente para confundir as pessoas e jogá-las fora no começo.
Sua forma simples:
pode ser expresso assim pela recorrência:
Mais difíceis
Depois que esses problemas simples forem mostrados E reimplementados nos tutoriais, você poderá dar exercícios um pouco mais difíceis (mas muito clássicos):
Nota: Novamente, alguns deles realmente não são mais difíceis ... Eles apenas abordam o problema exatamente do mesmo ângulo, ou um pouco diferente. Mas a prática leva à perfeição.
Ajudantes
Uma referência
Algumas leituras nunca são demais. Bem, a princípio será, e eles se sentirão ainda mais perdidos. É o tipo de coisa que cresce em você e fica na parte de trás da sua cabeça, até que um dia você percebe que finalmente entendeu. E então você pensa nessas coisas que lê. A recursão , a recursão em Ciência da Computação e as páginas de relação de recorrência na Wikipedia serviriam por enquanto.
Nível / profundidade
Supondo que seus alunos não tenham muita experiência em codificação, forneça stubs de código. Após as primeiras tentativas, ofereça a eles uma função de impressão que possa exibir o nível de recursão. Imprimir o valor numérico do nível ajuda.
O diagrama de empilhar como gavetas
Recuar um resultado impresso (ou a saída do nível) também ajuda, pois fornece outra representação visual do que seu programa está fazendo, abrindo e fechando contextos de pilha, como gavetas ou pastas em um explorador de sistemas de arquivos.
Acrônimos Recursivos
Se seu aluno já é um pouco versado na cultura da computação, ele pode já usar alguns projetos / softwares com nomes usando siglas recursivas . Tem sido uma tradição que existe há algum tempo, especialmente em projetos GNU. Alguns exemplos incluem:
Recursivo:
Mutuamente recursivo:
Faça com que eles tentem criar seus próprios.
Da mesma forma, existem muitas ocorrências de humor recursivo, como a correção de pesquisa recursiva do Google . Para mais informações sobre recursão, leia esta resposta .
Armadilhas e Aprendizagem Adicional
Alguns problemas com os quais as pessoas geralmente enfrentam e para os quais você precisa saber respostas.
Por que, oh Deus, por que ???
Por que você faria isso? Uma razão boa, mas não óbvia, é que muitas vezes é mais simples expressar um problema dessa maneira. Um motivo não tão bom, mas óbvio, é que muitas vezes é preciso digitar menos (não faça com que se sintam muuuuitas apenas por usar recursão ...).
Alguns problemas são definitivamente mais fáceis de resolver ao usar uma abordagem recursiva. Normalmente, qualquer problema que você possa resolver usando o paradigma Divide and Conquer se ajustará a um algoritmo de recursão com várias ramificações.
O que há de novo N ??
Por que meu
n
ou (qualquer que seja o nome da sua variável) é diferente toda vez? Os iniciantes geralmente têm problemas para entender o que são variáveis e parâmetros e como as coisas nomeadasn
em seu programa podem ter valores diferentes. Então agora, se esse valor está no loop de controle ou na recursão, é ainda pior! Seja gentil e não use os mesmos nomes de variáveis em todos os lugares e deixe claro que os parâmetros são apenas variáveis .Condições finais
Como determino minha condição final? Isso é fácil, basta que eles digam os passos em voz alta. Por exemplo, para o fatorial, comece de 5, depois 4, depois ... até 0.
O diabo está nos detalhes
Não fale com coisas anteriores, como otimização de chamada de cauda . Eu sei, sei, o TCO é bom, mas eles não se importam a princípio. Dê a eles algum tempo para entender o processo de uma maneira que funcione para eles. Sinta-se à vontade para destruir seu mundo novamente mais tarde, mas faça uma pausa.
Da mesma forma, não fale diretamente da primeira aula sobre a pilha de chamadas e seu consumo de memória e ... bem ... o estouro da pilha . Costumo dar aulas particulares a alunos que me mostram palestras onde eles têm 50 slides sobre tudo o que há para saber sobre recursão quando eles mal conseguem escrever um loop corretamente nesse estágio. Esse é um bom exemplo de como uma referência ajudará mais tarde, mas agora apenas o confunde profundamente.
Mas, por favor, no devido tempo, deixe claro que existem razões para seguir a rota iterativa ou recursiva .
Recursão mútua
Vimos que as funções podem ser recursivas e até podem ter vários pontos de chamada (8 rainhas, Hanói, Fibonacci ou até mesmo um algoritmo de exploração para um limpador de minas). Mas e as chamadas recursivas mutuamente ? Comece com a matemática aqui também.
f(x) = g(x) + h(x)
ondeg(x) = f(x) + l(x)
eh
el
apenas fazer coisas.Começando com apenas séries matemáticas, é mais fácil escrever e implementar, pois o contrato é claramente definido pelas expressões. Por exemplo, as sequências femininas e masculinas de Hofstadter :
No entanto, em termos de código, é de notar que a implementação de uma solução mutuamente recursiva muitas vezes leva a codificar a duplicação e deve, antes, ser simplificados em uma única forma recursiva (Veja Peter Norvig 's Resolver cada quebra-cabeça Sudoku .
fonte
static unsigned int vote = 1;
de mim. Perdoe o humor estático, se quiser :) Esta é a melhor resposta até agora.A chamada de uma função a partir dessa mesma função.
fonte
A recursão é uma função que se chama.
É importante saber como usá-lo, quando usá-lo e como evitar um design inadequado, o que exige que você experimente por si mesmo e entenda o que acontece.
A coisa mais importante que você precisa saber é ter muito cuidado para não obter um loop que nunca termine. A resposta do pramodc84 à sua pergunta tem a seguinte falha: Ela nunca termina ...
Uma função recursiva deve sempre verificar uma condição para determinar se deve se chamar novamente ou não.
O exemplo mais clássico para usar a recursão é trabalhar com uma árvore sem limites estáticos de profundidade. Esta é uma tarefa que você deve usar recursão.
fonte
a
ainda se chama, apenas indiretamente (chamandob
).A programação recursiva é o processo de reduzir progressivamente um problema para uma versão mais fácil de resolver.
Toda função recursiva tende a:
Quando a etapa 2 é anterior a 3 e a etapa 4 é trivial (concatenação, soma ou nada), isso permite a recursão da cauda . A etapa 2 geralmente deve ocorrer após a etapa 3, pois os resultados do (s) subdomínio (s) do problema podem ser necessários para concluir a etapa atual.
Faça a travessia de uma árvore binária direta. A travessia pode ser feita em pré-encomenda, ordem ou pós-encomenda, dependendo do que for necessário.
Pré-encomenda: BAC
Em ordem: ABC
Pós-encomenda: ACB
Muitos problemas recursivos são casos específicos de uma operação de mapa ou uma dobra - entender apenas essas duas operações pode levar a um entendimento significativo de bons casos de uso para recursão.
fonte
O OP disse que a recursão não existe no mundo real, mas eu imploro para diferir.
Vamos dar uma 'operação' no mundo real de cortar uma pizza. Você tirou a pizza do forno e, para servi-la, você deve cortá-la ao meio, depois cortar as metades ao meio, e novamente cortar as metades resultantes ao meio.
A operação de cortar a pizza que você está executando repetidamente até obter o resultado desejado (o número de fatias). E por razões de argumento, digamos que uma pizza sem cortes é uma fatia em si.
Aqui está um exemplo em Ruby:
Portanto, a operação no mundo real está cortando uma pizza e a recursão está fazendo a mesma coisa repetidamente até que você tenha o que deseja.
As operações que você descobrirá que podem ser implementadas com funções recursivas são:
Eu recomendo escrever um programa para procurar um arquivo com base no nome do arquivo e tentar escrever uma função que se auto-identifique até ser encontrada, a assinatura terá a seguinte aparência:
find_file_by_name(file_name_we_are_looking_for, path_to_look_in)
Então você poderia chamar assim:
find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf
É simplesmente programar mecânica, na minha opinião, uma maneira de remover a duplicação de maneira inteligente. Você pode reescrever isso usando variáveis, mas esta é uma solução 'melhor'. Não há nada de misterioso ou difícil nisso. Você escreverá algumas funções recursivas, elas clicarão e farão outro truque mecânico em sua caixa de ferramentas de programação.
Crédito extra O
cut_pizza
exemplo acima apresentará um erro muito profundo no nível da pilha se você solicitar um número de fatias que não seja uma potência de 2 (ou seja, 2 ou 4 ou 8 ou 16). Você pode modificá-lo para que, se alguém pedir 10 fatias, ele não funcione para sempre?fonte
Ok, vou tentar manter isso simples e conciso.
Função recursiva são funções que se chamam. A função recursiva consiste em três coisas:
As melhores maneiras de escrever métodos recursivos é pensar no método que você está tentando escrever como um exemplo simples, manipulando apenas um loop do processo sobre o qual deseja iterar, depois adicionar a chamada ao próprio método e adicionar quando quiser terminar. A melhor maneira de aprender é praticar como todas as coisas.
Como este é o site dos programadores, evitarei escrever código, mas aqui está um bom link
se você entendeu essa piada, entendeu o que significa recursão.
fonte
A recursão é uma ferramenta que um programador pode usar para chamar uma chamada de função em si mesma. A sequência de Fibonacci é o exemplo de como a recursão é usada.
O código mais recursivo, se não todos, pode ser expresso como função iterativa, mas geralmente é confuso. Bons exemplos de outros programas recursivos são estruturas de dados, como árvores, árvore de pesquisa binária e até quicksort.
A recursão é usada para tornar o código menos desleixado, lembre-se de que geralmente é mais lento e requer mais memória.
fonte
Eu gosto de usar este:
Como você caminha até a loja?
Se você estiver na entrada da loja, basta passar por ela. Caso contrário, dê um passo e caminhe o resto até a loja.
É fundamental incluir três aspectos:
Na verdade, usamos muito a recursão na vida diária; nós simplesmente não pensamos dessa maneira.
fonte
for
loop bem escrito em uma função recursiva sem sentido.O melhor exemplo que eu apontaria para você é a linguagem de programação C da K&R. Nesse livro (e estou citando de memória), a entrada na página de índice para recursão (sozinha) lista a página real em que eles falam sobre recursão e a página de índice também.
fonte
Josh K já mencionou as bonecas Matroshka . Suponha que você queira aprender algo que apenas a boneca mais pequena sabe. O problema é que você realmente não pode falar diretamente com ela, porque ela originalmente vive dentro da boneca mais alta que na primeira foto é colocada à esquerda. Essa estrutura continua assim (uma boneca mora dentro da boneca mais alta) até acabar apenas com a mais alta.
Portanto, a única coisa que você pode fazer é fazer sua pergunta para a boneca mais alta. A boneca mais alta (que não sabe a resposta) precisará passar sua pergunta para a boneca mais curta (que na primeira foto está à direita). Como ela também não tem a resposta, precisa pedir a próxima boneca mais curta. Isso continuará assim até que a mensagem chegue à boneca mais curta. A boneca mais curta (que é a única que sabe a resposta secreta) passará a resposta para a próxima boneca mais alta (encontrada à esquerda), que passará para a próxima boneca mais alta ... e isso continuará até a resposta chega ao seu destino final, que é a boneca mais alta e finalmente ... você :)
É isso que a recursão realmente faz. Uma função / método se chama até obter a resposta esperada. É por isso que quando você escreve um código recursivo, é muito importante decidir quando a recursão deve terminar.
Não é a melhor explicação, mas espero que ajude.
fonte
Recursão n. - Um padrão de design de algoritmo em que uma operação é definida em termos de si mesma.
O exemplo clássico é encontrar o fatorial de um número, n !. 0! = 1, e para qualquer outro número natural N, o fatorial de N é o produto de todos os números naturais menores ou iguais a N. Portanto, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720. Esta definição básica permitiria criar uma solução iterativa simples:
No entanto, examine a operação novamente. 6! = 6 * 5 * 4 * 3 * 2 * 1. Pela mesma definição, 5! = 5 * 4 * 3 * 2 * 1, o que significa que podemos dizer 6! = 6 * (5!). Por sua vez, 5! = 5 * (4!) E assim por diante. Ao fazer isso, reduzimos o problema a uma operação executada no resultado de todas as operações anteriores. Isso acaba se reduzindo a um ponto, chamado caso base, onde o resultado é conhecido por definição. No nosso caso, 0! = 1 (na maioria dos casos, também podemos dizer que 1! = 1). Na computação, geralmente temos permissão para definir algoritmos de maneira muito semelhante, fazendo com que o método se chame e passe uma entrada menor, reduzindo assim o problema através de muitas recursões para um caso base:
Isso pode, em muitos idiomas, ser simplificado ainda mais usando o operador ternário (às vezes visto como uma função Iif em idiomas que não fornecem o operador como tal):
Vantagens:
Desvantagens:
fonte
O exemplo que eu uso é um problema que enfrentei na vida real. Você tem um contêiner (como uma mochila grande que pretende levar em uma viagem) e deseja saber o peso total. Você tem no contêiner dois ou três itens soltos e outros contêineres (por exemplo, sacos de material). O peso do contêiner total é obviamente o peso do contêiner vazio mais o peso de tudo o que está nele. Para os itens soltos, você pode apenas pesá-los, e para os sacos de material, você pode apenas pesá-los ou você pode dizer "bem, o peso de cada saco de material é o peso do contêiner vazio, mais o peso de tudo nele". E então você continua entrando em contêineres em contêineres e assim por diante até chegar a um ponto em que existem apenas itens soltos em um contêiner. Isso é recursão.
Você pode pensar que isso nunca acontece na vida real, mas imagine tentar contar ou somar os salários de pessoas de uma empresa ou divisão específica, que tem uma mistura de pessoas que apenas trabalham para a empresa, pessoas em divisões e depois em as divisões existem departamentos e assim por diante. Ou vendas em um país que possui regiões, algumas das quais com sub-regiões, etc. etc. Esse tipo de problema ocorre o tempo todo nos negócios.
fonte
A recursão pode ser usada para resolver muitos problemas de contagem. Por exemplo, digamos que você tenha um grupo de n pessoas em uma festa (n> 1) e todo mundo aperta a mão de todo mundo exatamente uma vez. Quantos apertos de mão ocorrem? Você pode saber que a solução é C (n, 2) = n (n-1) / 2, mas você pode resolver recursivamente da seguinte maneira:
Suponha que haja apenas duas pessoas. Então (por inspeção) a resposta é obviamente 1.
Suponha que você tenha três pessoas. Escolha uma pessoa e observe que ele / ela aperta a mão de duas outras pessoas. Depois disso, você deve contar apenas os apertos de mão entre as outras duas pessoas. Já fizemos isso agora e é 1. Portanto, a resposta é 2 + 1 = 3.
Suponha que você tenha n pessoas. Seguindo a mesma lógica de antes, é (n-1) + (número de apertos de mão entre n-1 pessoas). Expandindo, obtemos (n-1) + (n-2) + ... + 1.
Expressa como uma função recursiva,
f (2) = 1
f (n) = n-1 + f (n-1), n> 2
fonte
Na vida (ao contrário de um programa de computador), a recursão raramente acontece sob nosso controle direto, porque pode ser confuso fazer isso acontecer. Além disso, a percepção tende a ser sobre os efeitos colaterais, em vez de ser funcionalmente pura; portanto, se a recursão estiver acontecendo, você poderá não perceber.
A recursão acontece aqui no mundo. Muito.
Um bom exemplo é (uma versão simplificada) do ciclo da água:
Este é um ciclo que faz com que ele se repita. É recursivo.
Outro lugar onde você pode obter recursão é em inglês (e no idioma humano em geral). Você pode não reconhecê-lo no início, mas a maneira como podemos gerar uma sentença é recursiva, porque as regras nos permitem incorporar uma instância de um símbolo ao lado de outra instância do mesmo símbolo.
De The Language Instinct, de Steven Pinker:
Essa é uma frase inteira que contém outras frases inteiras:
O ato de entender a sentença completa envolve a compreensão de sentenças menores, que usam o mesmo conjunto de truques mentais para serem entendidas como sentença completa.
Para entender a recursão da perspectiva da programação, é mais fácil olhar para um problema que pode ser resolvido com recursão e entender por que deveria ser e o que isso significa que você precisa fazer.
Para o exemplo, usarei a maior função divisor comum, ou gcd, para abreviar.
Você tem seus dois números
a
eb
. Para encontrar o MDC (assumindo que nenhum é 0), você precisa verificar sea
é igualmente divisível emb
. Se for então,b
é o gcd, caso contrário, você precisa verificar o gcd deb
e o restante dea/b
.Você já deve conseguir ver que essa é uma função recursiva, pois você tem a função gcd chamando a função gcd. Apenas para martelá-lo em casa, aqui está em c # (novamente, assumindo que 0 nunca seja passado como parâmetro):
Em um programa, é importante ter uma condição de parada, caso contrário, sua função ocorrerá para sempre, o que acabará causando um estouro de pilha!
O motivo para usar a recursão aqui, em vez de um loop while ou alguma outra construção iterativa, é que, enquanto você lê o código, ele diz o que está fazendo e o que acontecerá a seguir, tornando mais fácil descobrir se está funcionando corretamente. .
fonte
Aqui está um exemplo do mundo real para recursão.
Deixe-os imaginar que têm uma coleção de quadrinhos e você misturará tudo em uma grande pilha. Cuidado - se eles realmente tiverem uma coleção, poderão matá-lo instantaneamente quando você mencionar a idéia de fazê-lo.
Agora, deixe-os ordenar essa grande pilha de quadrinhos sem classificação com a ajuda deste manual:
O mais interessante aqui é: quando eles têm problemas únicos, eles têm o "quadro de pilha" completo com as pilhas locais visíveis antes deles no chão. Dê a eles várias impressões do manual e coloque uma de cada nível de pilha com uma marca em que você está atualmente neste nível (ou seja, o estado das variáveis locais), para que você possa continuar lá em cada Concluído.
É disso que se trata basicamente a recursão: executar o mesmo processo, apenas em um nível de detalhe mais fino, quanto mais você entrar nele.
fonte
A recursão é uma maneira muito concisa de expressar algo que precisa ser repetido até que algo seja alcançado.
fonte
Não é um inglês simples, nem exemplos da vida real, mas duas maneiras de aprender a recursão ao jogar:
fonte
Uma boa explicação da recursão é literalmente "uma ação que se repete a partir de si mesma".
Considere um pintor pintando uma parede, é recursivo porque a ação é "pintar uma faixa do teto ao chão do que deslizar um pouco para a direita e (pintar uma faixa do teto ao chão do que deslizar um pouco para a direita e (pintar uma tira do teto ao chão do que deslize um pouco para a direita e (etc))) ".
Sua função paint () chama a si mesma repetidamente para compor sua função paint_wall () maior.
Espero que este pobre pintor tenha algum tipo de condição de parada :)
fonte