Um mtodo comum de simplificao consiste em dividir um problema em
subproblemas do mesmo tipo. Como tcnica de programao, isto se denomina diviso e conquista, e constitui a chave para o desenvolvimento de muitos algoritmos importantes, bem como um elemento fundamental do paradigma de programao dinmica. Praticamente todas as linguagens de programao usadas hoje em dia permitem a especificao direta de funes e procedimentos recursivos. Quando uma funo invocada, o computador (na maioria das linguagens sobre a maior parte das arquiteturas baseadas em pilhas) ou a implementao da linguagem registra as vrias instncias de uma funo (em muitas arquiteturas, usa-se uma pilha de chamada, embora outros mtodos possam ser usados). Reciprocamente, toda funo recursiva pode ser transformada em uma funo iterativa usando uma pilha. Toda funo que puder ser produzida por um computador pode ser escrita como funo recursiva sem o uso de iterao; reciprocamente, qualquer funo recursiva pode ser descrita atravs de iteraes sucessivas. Um exemplo simples poderia ser o seguinte: se uma palavra desconhecida vista em um livro, o leitor pode tomar nota do nmero da pgina e colocar em uma pilha (que at ento est vazia). O leitor pode consultar esta nova palavra e, enquanto l o texto, pode achar mais palavras desconhecidas e acrescentar no topo da pilha. O nmero da pgina em que estas palavras ocorrem tambm so colocados no topo da pilha. Em algum momento do texto, o leitor vai achar uma frase ou um pargrafo onde est a ltima palavra anotada e pelo contexto da frase vai descobrir o seu significado. Ento o leitor volta para a pgina anterior e continua lendo dali. Paulatinamente, remove-se seqencialmente cada anotao que est no topo da pilha. Finalmente, o leitor volta para a sua leitura j sabendo o significado da(s) palavra(s) desconhecida(s). Isto uma forma de recurso. Algumas linguagens desenvolvidas para programao lgica e programao funcional permitem recurses como nica estrutura de repetio, ou seja, no podem usar laos tais como os produzidos por comandos como for, while ou repeat. Tais linguagens geralmente fazem uma recurso em cauda to eficiente quanto a iterao, deixando osprogramadores exprimirem outras estruturas de repetio (tais como o map e o for do Scheme) em termos de recurso. A recurso est profundamente entranhada na Teoria da computao, uma vez que a equivalncia terica entre as funes -recursivas e as mquinas de Turing est na base das idias sobre a universalidade do computador moderno. Programao recursiva Em geral, uma definio recursiva definida por casos: um nmero limitado de casos base e um caso recursivo. Os casos base so geralmente situaes triviais e no envolvem recurso. Um exemplo comum usando recurso a funo para calcular o fatorial de um natural N. Nesse caso, no caso base o valor de 0! 1. No caso recursivo, dado um N > 0, o valor de N! calculado multiplicando por N o valor de (N-1)!, e assim por diante, de tal forma que N! tem como valor N * (N-1) * (N-2) * ... * (N-N)!, onde (N-N)! representa obviamente o caso base. Em termos recursivos: funo fatorial(x: inteiro): inteiro inicio se x = 0 ento fatorial <- 1 seno fatorial <- x * fatorial(x - 1) fim_se fim Aqui est a mesma funo codificada sem recurso. importante mencionar que esta soluo iterativa requer duas variveis temporrias; em geral, formulaes recursivas de algoritmos so freqentemente consideradas "mais enxutas" ou "mais elegantes" do que formulaes iterativas. funo fatorial(x: inteiro): inteiro var i, aux: inteiro inicio aux <- 1 para i de 1 at x faa aux <- aux * i fim_para fatorial <- aux fim Recurso versus Iterao No exemplo do fatorial, a implementao iterativa tende a ser ligeiramente mais rpida na prtica do que a implementao recursiva, uma vez que uma implementao recursiva precisa registrar o estado atual do processamento de maneira que ela possa continuar de onde parou aps a concluso de cada nova execuo subordinada do procedimento recursivo. Esta ao consome tempo e memria. (Note que a implementao de uma funo fatorial para nmeros naturais pequenos mais rpida quando se usa uma tabela de busca.) Existem outros tipos de problemas cujas solues so inerentemente recursivas, j que elas precisam manter registros de estados anteriores. Um exemplo o percurso de uma rvore; outros exemplos incluem a funo de Ackermann e algoritmos de diviso e conquista, tais como o Quicksort. Todos estes algoritmos podem ser implementados iterativamente com a ajuda de uma pilha, mas o uso de uma pilha, de certa forma, anula as vantagens das solues iterativas. Outra possvel motivao para se escolher um algoritmo iterativo ao invs de um algoritmo recursivo que nas linguagens de programao modernas o espao disponvel para o fluxo de controle geralmente bem menor que o espao disponvel no heap, e algoritmos recursivos tendem a necessitar de mais espao na pilha do que algoritmos iterativos. Funes recursivas Funes cujos domnios podem ser definidos recursivamente (tal como o domnio dos nmeros naturais) possuem frequentemente definies recursivas que seguem a definio recursiva do domnio (no caso dos naturais, definimos o comportamento da funo com entrada 0, e para cada entrada positiva definimos o comportamento da funo recursiva a partir de seu comportamento com entrada ). O exemplo clssico de uma funo definida recursivamente a seguinte definio da funo :
A partir desta definio, tambm chamada relao de recorrncia, calculamos da seguinte forma:
Funes recursivas em cauda As funes recursivas em cauda formam uma subclasse das funes recursivas, nas quais a chamada recursiva a ltima instruo a ser executada. Por exemplo, a funo a seguir, para localizar um valor em uma lista ligada recursiva em cauda, por que a ltima coisa que ela faz invocar a si mesma: registro noh dado: inteiro *proximo: registro noh fim_registro
*acha_valor(*cabeca: registro noh, valor: inteiro): registro noh inicio se cabeca = NULO ento acha_valor <- NULO seno se cabea.dado = valor ento acha_valor <- cabeca seno acha_valor <- acha_valor(cabeca.proximo, valor) fim_se fim Note que a funo fatorial usada como exemplo na seo anterior no recursiva em cauda, pois depois que ela recebe o resultado da chamada recursiva, ela deve multiplicar o resultado por x antes de retornar para o ponto em que ocorre a chamada. Funes com este tipo de comportamento so por vezes denominadas crescentemente recursivas. importante recordar que uma nica funo pode ter ambos os comportamentos, como ocorre na seguinte funo que conta os inteiros mpares em uma lista ligada: funo conta_impares(*cabeca: registro noh): inteiro inicio se cabeca = NULO ento conta_impares <- 0 seno se (cabeca->dado MOD 2) = 1 ento conta_impares <- conta_impares(cabeca->proximo) + 1 /* recurso */ seno conta_impares <- conta_impares(cabeca->proximo) /* recurso em cauda */ fim Um bom compilador pode traduzir cdigo recursivo em cauda para cdigo iterativo. Com tal compilador, h vantagem em usar recurso em cauda para algumas funes. Definies recursivas algumas vezes so muito mais claras do que as iterativas. Contudo, chamadas recursivas so mais custosas do que iteraes. Com recurso em cauda podemos ter cdigo recursivo legvel e uma implementao iterativa eficiente ao mesmo tempo. O mais importante na recurso em cauda que ao fazer uma chamada da funo recursiva, os valores de retorno dela no necessitam ser conservados na pilha de chamada; quando a chamada recursiva retorna, ela vai diretamente para a posio de retorno previamente registrada. Assim, os compiladores que do suporte recurso em cauda economizam espao e tempo. Recurso Indireta Funes podem ser recursivas (invocar a si prprias) indiretamente, fazendo isto atravs de outras funes: assim, "P" pode chamar "Q" que chama "R" e assim por diante, at que "P" seja novamente invocada. Um exemplo a anlise de expresses. Suponha que voc tem um analisador sinttico para cada tipo de sub-expresso, e tenha uma expresso "3 + (2 * (4 + 4))". A funo que processa expresses "+" chamaria uma segunda funo que processaria expresses "*", que, por sua vez, chamaria novamente a primeira. Recurso Aninhada Uma chamada recursiva pode receber um argumento que inclui uma outra chamada recursiva. Um exemplo a funo de Ackermann, uma funo que cresce de forma incrivelmente rpida. funo ack(n: inteiro, m: inteiro): inteiro inicio se n = 0 ento ack <- m + 1 seno se n > 0 E m = 0 ento ack <- ack(n - 1, m) seno ack <- ack(n - 1, ack(n, m - 1)) fim_se fim Este um exemplo de uma funo que muito mais fcil de escrever recursivamente: foi demonstrado que no existem definies equivalentes usando operadores aritmticos. Infelizmente uma computao recursiva direta desta funo no nem mesmo O(2n) em tempo ou espao. A recurso aninhada um tipo especial de recurso dupla, onde uma definio recursiva refere-se a si prpria mais de uma vez. Ordem de chamada de funes A ordem da chamada das funes podem mudar completamente a execuo da funo, veja o exemplo em C: Funo 1 void recursiveFunction(int num) { if (num < 5) { printf("%d\n", num); recursiveFunction(num + 1); } }
Funo que retorna a soma dos nmeros de n at 0 int soma(int n) { if (n > 0) return n + soma(n - 1); else return 0; } Diviso de nmeros com Recurso Funo para dividir nmeros utilizando somente soma e subtrao: Funo divisaoRec(inteiro num, inteiro den) retorna Inteiro Inicio Se (num < den) Ento Funo_Retorna(0) Seno Funo_Retorna(divisaoRec(num-den, den) + 1) Fim_Se Fim Usando vetores Funes recursivas tambm podem ser usadas para acessar elementos de vetores, no exemplo abaixo mostrado uma funo recursiva que retorna a soma dos elementos de um vetor. int somatoria(int vetor[], int tamanho) { if (tamanho > 0) return vetor[tamanho - 1] + somatoria(vetor, tamanho - 1); else return 0; }