A recursão - como todos sabemos - é um desses problemas - que envolver sua cabeça parece alcançar um "marco" em sua viagem de programação.
Mas quando se trata de realmente usá-lo em problemas do mundo real - conhecer a mecânica da recursão NÃO é suficiente - é preciso também entender a natureza dos problemas em que a recursão é a solução mais adequada.
Então, minha pergunta é esta ...
- quais são os "padrões de problemas" que exigem a solução da recursão
- recursão é uma forma de estratégia "dividir e conquistar" ou uma forma de "reutilização de código" - ou é um padrão de design por si só
- você pode nos dar um exemplo de um problema do mundo real em que a recursão vem à mente como uma solução imediata
- ATUALIZAÇÃO -
muitas respostas estão se referindo a "problemas reais" como atravessar árvores, fatorial etc. Eu preferiria "os problemas reais REAIS" - deixe-me dar um exemplo ...
Tínhamos uma grande quantidade de texto (cerca de 30 MB de texto como uma lista vinculada structs
) e precisávamos fazer um índice para pesquisa de texto completo. Precisávamos manter o índice inteiro na memória e indexar novamente o texto a cada 10 minutos.
A cada 10 minutos, comparávamos o texto inteiro (duas listas vinculadas, linha por linha) com um pedaço de texto recém-gerado - para ver qual linha foi alterada - e então reindexamos apenas essa linha - dessa maneira poderíamos evitar ter que indexar novamente o texto INTEIRO. Lembre-se - precisávamos encontrar os pontos de diferença entre duas listas vinculadas de 30 MB.
Um dos meus colegas criou um programa fantástico que usava recursão PESADA para comparar as linhas - e depois coletar as posições em que os mandris diferiam em uma matriz - sim, eu sei que parece intrigante - como a recursão poderia ajudar aqui - mas fez.
A questão é: como ele pôde ver que esse problema poderia ser resolvido de maneira inteligente com o uso pesado de recursão?
Respostas:
Eu não diria que existe um padrão de problema para o uso da recursão. Toda função que pode ser implementada com recursão também pode ser implementada iterativamente, geralmente pressionando e popping uma pilha.
É uma questão de expressão e também de desempenho. Os algoritmos iterativos costumam ter um desempenho melhor e são mais fáceis de otimizar. No entanto, algoritmos recursivos se beneficiam de uma expressão mais clara e, portanto, geralmente são mais fáceis de ler, entender e implementar.
Algumas coisas ainda não podem ser expressas sem recursão, listas infinitas, por exemplo. As chamadas linguagens funcionais dependem fortemente da recursão, pois é seu modo natural de expressão. O ditado é: "Programação recursiva é programação funcional feita corretamente".
Eu não chamaria isso de padrão de design. É uma questão de expressão. Às vezes, uma expressão recursiva é simplesmente mais poderosa e mais expressiva e, portanto, leva a um código melhor e mais limpo.
Qualquer coisa que precise atravessar árvores será adequadamente expressa por um algoritmo recursivo.
fonte
Nem. Dividir e conquistar usa recursão. Mas a recursão não é necessariamente dividir e conquistar, pois a última significa dividir um problema em duas (ou mais) partes e resolver cada uma delas simetricamente. Na recursão, você não faz isso.
A reutilização de código é completamente independente, e um padrão de design entra em jogo em um nível muito mais alto. Por exemplo, até dividir e conquistar (também um padrão de nível superior ao da recursão) ainda não é considerado um padrão de design - é um padrão algorítmico .
Travessia de árvore. Ou, geralmente, o gráfico é transversal. Isso inclui atravessar uma estrutura de pastas.
E, claro, qualquer coisa que use dividir e conquistar ou programação dinâmica, pois ambas são naturalmente expressas em termos de recursão.
fonte
Derivado da auto-similaridade dos fractais, eu diria que a auto-igualdade ou a auto-identidade (ou como é chamada) é um padrão típico de problema para recursão. Ou seja, um problema pode ser dividido em subproblemas que têm a mesma estrutura do problema principal.
No percurso da árvore mencionado, cada subárvore é uma árvore completa em si mesma, assim como a árvore principal, e a árvore principal pode ser uma subárvore dentro de outra árvore.
Então, acho que seu colega descobriu as propriedades de auto-igualdade do seu problema de indexação. Ou ele fez o contrário e transformou o problema em uma representação auto-igual.
fonte
Bem, a recursão pode ser entendida facilmente se alguém tentar transformar loops imperativos em funções funcionais. De qualquer forma, vamos tentar responder a todas as perguntas:
Se você tiver uma estrutura ou algoritmo semelhante a uma árvore, precisará de recursão. Se o seu código imperativo lida com uma pilha, você precisará de recursão. Se uma determinada etapa do seu algoritmo depende das etapas anteriores (pense em loops), você precisa de recursão. A necessidade aqui deve ser interpretada como pode ser usada.
Nenhum. Dividir e conquistar usa recursão, mas pode ser implementado com pilhas. A reutilização de código refere-se a outra coisa. Os padrões de design são mais complicados que a simples recursão.
Análise e tudo o que lida com estruturas de árvores. Até estruturas de árvore implícitas.
fonte
Se existe uma maneira de simplificá-lo para que seja fácil, essa pode ser a pista de que a recursão poderia funcionar. Você pode classificar e procurar exemplos onde existem soluções recursivas, como Merge Sort e Binary Search, respectivamente.
Algo a ter em mente é como alguns problemas podem ser mal resolvidos com recursão como um fatorial.
Quanto a um exemplo do mundo real em que eu usaria recursão, procurar um livro de uma estante de livros pode ser feito com muita facilidade de maneira recursiva. Eu apenas olho para o livro e, se não é o que eu quero, passo para o próximo. Paro quando encontro o livro ou chego ao fim da fila. O loop na verificação de um livro e na passagem para o próximo poderia ser feito recursivamente. Talvez isso seja um exemplo muito real.
fonte
Em termos muito gerais, a recursão é necessária quando você está resolvendo um problema em que f (x) = f (g (x)) . A menos que você esteja bem com a recursão infinita, g (x) não deve ser avaliado como x .
Nenhuma das acima. É apenas uma maneira de fazer a mesma coisa repetidamente, às vezes com base em variações na entrada. O conceito existe há muito mais tempo do que padrões de design, reutilização de código ou até mesmo computadores.
Fatoriais, a seqüência de Fibonacci, a travessia de árvores e muitos outros problemas podem ser resolvidos com a recusa. A recursão no sentido de uma função se chamar não é necessariamente a melhor maneira de implementar esse tipo de coisa; existem outras maneiras de obter o mesmo efeito (por exemplo, uma pilha e um loop) que podem ser mais desejáveis.
fonte
Quando você escreve um algoritmo recursivo, geralmente traduz uma definição recursiva do problema no código. Então você nem precisa saber como será executado.
É o que acontece na programação funcional. De fato, você especifica o que (definição) e não como . Em outras palavras, você define a base e, em seguida, define seu problema no termo de um subproblema.
Por exemplo, considere o
Factorial
algoritmoEntão, quando você encontrar um problema, pense se você pode defini-lo recursivamente ou não, se você pode defini-lo recursivamente, você quase o resolveu.
No entanto, qualquer função recursiva não deve ser uma definição recursiva. Você pode definir a base e relacionar (definir) a solução do problema principal à solução (definição) de subproblemas. Mas, para essa relação, você pode precisar de um procedimento.
Um exemplo é o
MergeSort
qualmerge
poderia ser um procedimento imperativo para relacionar a definição ou solução de classificação de toda a matriz com a classificação das sub-matrizes.fonte