Você está na página 1de 9

Algoritmos recursivos

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 2 com linhas trocadas
void recursiveFunction(int num)
{
if (num < 5)
{
recursiveFunction(num + 1);
printf("%d\n", num);
}
}

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;
}