CAPÍTULO 5 SUBPROGRAMAS
5.7 Recursividade
Algumas funções matemáticas clássicas podem ser estabelecidas de tal forma que as suas definições utilizem, de modo recorrente, a própria função que se está definindo. Um exemplo trivial (no bom sentido) de um caso como este é a função fatorial. Como dissemos no exemplo 2 da seção 5.5, o fatorial de um número inteiro não-negativo n é o produto de todos os números naturais de 1 até o referido n ou seja n! = 1 . 2 . 3 . ... . n, sendo igual a 1 quando n = 0 ou n = 1. Como mostrou o referido exemplo, é muito simples se escrever uma função que calcule o fatorial de n:. Basta se inicializar uma variável com 1 e, numa estrutura de repetição, calcular os produtos 1 x 2 = 2, 2 x 3 = 6; 6 x 4 = 24; 24 x 5 = 120; ...; etc., até multiplicar todos os naturais até n.
Embora o conceito anterior seja de simples compreensão, pode-se obter uma definição mais elegante para o fatorial de um inteiro não-negativo n:
n se n ou n
Desta forma, o fatorial de n é definido a partir dos fatoriais dos naturais menores que n. Isto significa que, para o cálculo do fatorial de um determinado número natural, há necessidade de que se recorra aos fatoriais dos naturais anteriores. Por exemplo,
4! = 4 . 3! = 4 . (3 . 2!) = (4 . 3) . (2 . 1!) = 4 . 3 . 2 . 1 = 24.
Uma definição com estas características é dita uma definição por recorrência ou uma definição recursiva.
Um outro exemplo de uma definição recursiva foi dada no exercício 12 da seção 4.6: a seqüência de Fibbonaci é a seqüência (an) definida por
a se n ou n
Observe que o termo de ordem n é definido a partir de termos anteriores. Isto significa que, para o cálculo de um determinado termo, há necessidade de que se recorra a valores de todos os termos anteriores.
Por exemplo, para a determinação de a5 necessitamos conhecer a4 e a3; para a determinação destes dois, necessitamos conhecer a2 e a1.
Naturalmente, uma definição recursiva deve conter uma condição que interrompa a recorrência. Esta condição é chamada condição de escape. No caso do fatorial, a condição de escape é n = 0 ou n = 1; na seqüência de Fibbonaci, a condição de escape é n = 1 ou n = 2. A expressão que realiza propriamente a recorrência pode ser chamada expressão de recorrência.
O que surpreende a todos que começam a aprendizagem de programação é que, de um modo geral, as linguagens de programação oferecem recursos para implementação de funções recursivas da mesma maneira que elas são escritas em matemática. Por exemplo, a implementação recursiva do fatorial pode ser feita em Pascal simplesmente da seguinte forma:
{Programa para gerar uma tabela de fatoriais}
program TabelaFatoriais;
var i : integer;
{Funcao recursiva para o cálculo do fatorial de um inteiro não negativo}
Function FatRec (n : integer) : longint;
begin
end;
{Programa principal}
begin
for i := 1 to 10 do
writeln(i, '! = ', FatRec(i));
end.
É interessante ter uma idéia do que acontece na recursividade. Quando se ativa uma função recursiva, cada nova chamada da mesma é empilhada na chamada pilha de recursão até que a condição de escape seja atingida. A partir daí, cada ativação pendente é desempilhada (evidentemente, na ordem inversa do empilhamento) e as operações vão sendo realizadas. No programa acima, quando i = 5, temos a seguinte seqüência de operações:
1. Após a ativação de FatRec(5) FatRec(5) n
5*FatRec(4) 5
2. Após a ativação de FatRec(4)
FatRec(5) n FatRec(4) n
5*FatRec(4) 5 4*FatRec(3) 3 3. Após a ativação de FatRec(3)
FatRec(5) n FatRec(4) n FatRec(3) n
5*FatRec(4) 5 4*FatRec(3) 3 3*FatRec(2) 2 4. Após a ativação de FatRec(2)
FatRec(5) n FatRec(4) n FatRec(3) n FatRec(2) n
5*FatRec(4) 5 4*FatRec(3) 3 3*FatRec(2) 2 2*FatRec(1) 1 5. Após a ativação de FatRec(1)
FatRec(5) n FatRec(4) n FatRec(3) n FatRec(2) n
5*FatRec(4) 5 4*FatRec(3) 3 3*FatRec(2) 2 2*1 = 2 1
FatRec(5) n FatRec(4) n FatRec(3) n
5*FatRec(4) 5 4*FatRec(3) 3 3*2 = 6 2 FatRec(5) n FatRec(4) n
5*FatRec(4) 5 4*6 = 24 3
FatRec(5) n
5*24 = 120 5
Embora a utilização da recursividade apresente a vantagem de programas mais simples, ela traz o inconveniente de sacrificar a eficiência do programa. Isto ocorre devido à necessidade de chamadas sucessivas da função e das operações de empilhamento e desempilhamento, o que demanda um tempo maior de computação e uma maior necessidade de uso de memória. Esta observação faz com que a solução não-recursiva (chamada solução iterativa) seja preferível. No capítulo 10, apresentaremos um exemplo de uma função recursiva que é tão eficiente quanto a função iterativa.
Um outro exemplo interessante de recursividade é a implementação do jogo conhecido como Torre de Hanói que foi objeto do exercício proposto 2 da seção 1.12. Para lembrar, este jogo consiste de três torres chamadas origem, destino e auxiliar e um conjunto de n discos de diâmetros diferentes, colocados na torre origem, na ordem decrescente dos seus diâmetros. O objetivo do jogo é, movendo um único disco de cada vez e não podendo colocar um disco sobre outro de diâmetro menor, transportar todos os discos para a torre destino, podendo usar a torre auxiliar como passagem intermediária dos discos.
Indicando com torre 1 torre 2 o movimento do disco que no momento está na parte superior da torre 1 para a torre 2, teríamos a seguinte solução para o caso n = 2:
1. origem auxiliar 2. origem destino 3. auxiliar destino Para n = 3, a solução seria:
1. origem destino 2. origem auxiliar 3. destino auxiliar 4. origem destino 5. auxiliar origem 6. auxiliar destino 7. origem destino
Observe que os três movimentos iniciais transferem dois discos da torre origem para a torre auxiliar, utilizando a torre destino como auxiliar; o quarto movimento transfere o maior dos discos da origem para destino e os últimos movimentos transferem os dois discos que estão na auxiliar para destino, utilizando origem como torre auxiliar.
Assim, a operação Move(3, origem, auxiliar, destino) - move três discos da origem para destino usando auxiliar como torre auxiliar - pode ser decomposta em três etapas:
1. Move(2, origem, destino, auxiliar) - move dois discos de origem para auxiliar usando destino como auxiliar;
2. Move um disco de origem para destino
3. Move(2, auxiliar, origem, destino) - move dois discos de auxiliar para destino usando origem como auxiliar.
O interessante é que é fácil mostrar que este raciocínio se generaliza para n discos, de modo que a operação Move(n, a, b, c) pode ser obtida com as seguintes operações:
1. Move(n-1, a, c, b)
2. Move um disco de a para c 3. Move(n-1, b, a, c)
O mais interessante ainda é que isto pode ser implementado em Pascal, através do seguinte programa:
{Programa que implementa o jogo Torre de Hanoi}
program TorreHanoi;
var n : integer;
{Procedimento para indicar o movimento do disco superior de uma para outra torre}
procedure MoveDisco(t1, t2 : string);
begin
writeln(t1,' ', t2);
end;
{Procedimento recursivo para a Torre de Hanoi}
procedure Hanoi(x : integer, o, a, d : string);
begin if x > 0
then begin
Hanoi(x - 1, o, d, a);
MoveDisco(o, d);
Hanoi(x - 1, a, o, d);
end;
end;
{programa principal}
begin
writeln('Digite o numero de discos ');
readln(n);
Hanoi(n, 'origem', 'auxiliar', 'destino');
end.
5.8 Exercícios propostos
1. Escreva duas funções, uma iterativa e a outra recursiva, que retornem o k-ésimo dígito (da direita para a esquerda) de um inteiro n. Por exemplo, K_esimoDigito(2845, 3) = 8.
2. O fatorial ímpar de um número n ímpar positivo é o produto de todos os números ímpares positivos menores do que ou iguais a n. Indicando o fatorial ímpar de n por n| temos, n| = 1 . 3. 5 . ... . n. Por exemplo, 7| = 1 . 3 . 5 . 7 = 105. Escreva funções iterativas e recursivas para a determinação do fatorial ímpar de um inteiro ímpar dado.
3. Como na questão anterior, o fatorial primo de um número primo positivo é o produto de todos os primos positivos menores do que ou iguais a ele, p# = 2 . 3 . 5 . 7 . ... .p. Por exemplo, 7# = 2 . 3 . 5 . 7 = 210.
Escreva um programa que determine o fatorial primo de um primo dado.
4. Escreva uma função que retorne a soma dos algarismos de um inteiro positivo dado.
5. Escreva uma função recursiva que retorne o n-ésimo termo da seqüência de Fibbonaci, n dado.
6. Escreva uma função que receba um número inteiro n e forneça o número formado pelos algarismos de n escritos na ordem inversa. Por exemplo, se o número dado for 3876, a função deve fornecer 6783.
7. Escreva uma função recursiva que retorne o máximo divisor comum de dois inteiros dados.
8. Escreva uma função recursiva que retorne o mínimo múltiplo comum de dois inteiros dados.
Observação
Para receber as respostas dos exercícios propostos, encaminhe mensagem para [email protected], assunto RESPOSTAS EXERCÍCIOS PASCAL, contendo NOME, INSTITUIÇÃO (se for o caso), CIDADE/ESTADO e CATEGORIA (docente, estudante ou auto-didata).