Recursividade e Iteração
Factorial, Fibonacci e Maior Divisor Comum
DI/FCT/UNL
1º Semestre 2004/2005
1
Programas e Funções
• Como visto em exemplos anteriores, um programa pode ser
considerado como o encadeamento de diversas funções, isto
é, no corpo de uma função são chamadas outras funções.
• Um programa pode pois ser estruturado de forma a ser
composto por várias funções, tal como é feio na
“matemática”. Por exemplo,
tg(x) = sin(x) / cos(x).
• Em algumas linguagens de programação, nomeadamente
naquelas em que as funções só podem retornar um valor, em
vez de funções são usados procedimentos, mas a filosofia de
encadeamento é semelhante.
• De notar que o programa principal, pode ele próprio ser visto
como uma função.
2
Funções Recursivas
• Um caso particular ocorre quando as funções se chamam a si
próprias, isto é, quando as funções são recursivas.
• Talvez o exemplo mais simples seja o da função factorial, que
pode ser definida (incompletamente) como
fact(n) = n * fact(n-1)
• Nestas condições, tal como nos ciclos, levanta-se o problema
da terminação. Se uma função se chama a si própria, existe o
risco de a computação se tornar infinita (entrar em ciclo
fechado ou “loop”).
• É pois condição necessária para evitar estes ciclos infinitos
que sejam definidas e testadas em primeiro lugar as condições
de fim da recursividade.
3
Funções Recursivas
• Em geral, a recursividade é feita com base num conjunto
recursivo (indutivo), definido através de cláusulas
– de base; um ou vários elementos de base (que fecham a recursão)
– de recursão: uma definição recursiva que permite a obtenção de
elementos a partir de outros elementos.
• Num grande número de casos, o conjunto recursivo utilizado
é o conjunto dos numeros inteiros, em que
– 1 (ou 0) é um número inteiro (cláusula de base)
– Se i é inteiro, i+1 também é inteiro (cláusula de recursão)
• Tendo em conta esta estrutura recursiva, podemos definir
(correctamente) a função factorial tendo em conta a cláusula
de base e a cláusula recursiva
4
Função Factorial
• A função factorial pode ser definida (em pseudo-código) como
função fact(n)
se n = 0 então
% elemento base
fact  1
senão
% definição recursiva
fact  n * fact(n-1)
fimse;
fimfunção;
• Em Octave, a definição é semelhante
function f = fact(n);
if n == 0
f = 1
else
f = n * fact(n-1)
endif;
endfunction;
% elemento base
% definição recursiva
5
Limites à Recursividade
• De notar que para prevenir os ciclos infinitos, o Octave tem
uma variável prédefinida, max_recursion_depth, que limita o
número de chamadas recursivas de uma função.
• Por omissão (“default”), o valor da variável é 256, pelo que
não se pode calcular fact(260) sem alterar o valor da variável.
• Existem várias outras funções recursivas em que pode existir
mais do que um elemento base ou em que o conjunto recursivo
é mais difícil de definir. Em qualquer caso é essencial definir a
condição de terminação.
• Para exemplificar estas funções, estudamos de seguida
– a determinação dos números de Fibonacci e
– o cálculo do maior divisor comum entre dois números.
6
Função Fibonacci (Recursiva)
• Fibonacci (matemático da Renascença italiana) estabeleceu uma
série curiosa de números para modelar o número de casais de
coelhos em sucessivas gerações. Em cada geração, o número
pode ser obtido através dos das 2 gerações anteriores através de
fib(n) = fib(n-1) + fib(n-2)
• Assumindo que nas primeiras duas gerações só existe um casal
de coelhos, os sucessivos números de Fibonacci são
1,1,2,3,5,8,13,21,34,55,89, ...
• Estes números ocorrem em vários processos biológicos (e não
só), por exemplo, no número de pétalas de algumas flores.
• A sua determinação recursiva impõe o cálculo directo do valor
para 2 elementos de base (a 1ª e a 2ª geração).
7
Função Fibonacci (Recursiva)
• Em Octave, a definição do números de Fibonacci pode ser feita
muito facilmente como
function f = fib(n);
if n == 1
% 1º elemento base
f = 1;
elseif n == 2
% 2º elemento base
f = 1;
else
% definição recursiva
f = fib(n-1) * fib(n-2);
endif;
endfunction;
• Como é fácil de constatar, se o número n inicial for maior ou
igual a 1, a recursão termina. No entanto, se se chamar fib(0)
a computação não termina (em Octave termina quando o
número de chamadas recursivas exceder o valor da variável
max_recursion_depth)!
8
Função Máximo Divisor Comum (Recursiva)
• Como se sabe, o máximo divisor comum (mdc) entre dois
números m e n é também um divisor da sua diferença, m-n.
Por exemplo, o mdc de 60 e 36 é 12, que divide 24 = 60-36.
• Por outro lado, o mdc dos dois números m e n é ainda o mdc
do menor número (n) com a diferença (m-n). Se houvesse um
divisor comum maior, ele seria igualmente divisor de n,
contrariamente à hipótese.
• Assim, pode determinar-se o mdc de dois números através da
determinação do mdc de números cada vez menores. A
computação termina quando os números forem iguais, caso em
que o mdc é esse número.
• Por exemplo: 60-36 =24 ; 36-24=12; 24-12=12; 12 = 12 !
9
Função Máximo Divisor Comum (Recursiva)
• Em Octave, pode determinar-se o maior divisor comum de dois
números com a função seguinte
function d =
if m == n
d = m;
else
if m-n
d =
else
d =
endif;
endif;
endfunction;
mdc(m,n);
>= n
mdc(m-n,n);
mdc(n,m-n);
• De notar que na chamada, o 1º argumento (m) é sempre maior
ou igual que o 2º (n), de forma a garantir que m-n seja positivo!
10
Recursão e Iteração
• Em geral, uma função ou procedimento definidos
recursivamente podem ser também definidos de uma forma
iterativa (através de ciclos).
• Em geral, a definição recursiva é mais “declarativa” na medida
em que explicita o que se pretende obter e não a forma como se
obtém (ou seja, um determinado programa que é usado).
• Por outro lado, uma definição iterativa, embora não permita
uma compreensão tão imediata, é geralmente mais eficiente, já
que as instruções de programação de baixo nível para a
iteração são mais eficientes que as de chamadas de funções.
• No entanto, estas diferenças são geralmente pouco
importantes, excepto em casos de recursão múltipla, em que a
ineficiência pode ser “catastrófica”.
11
Recursão e Iteração
function f = fib(n); % versão recursiva
if n <= 2
f = 1;
else
f = fib(n-1) + fib(n-2);
endif;
endfunction;
function f = fib(n) % versão iterativa
if n <= 2
f = 1;
else
f1 = 1; f2 = 1;
for i = 3:n
f = f1+f2;
f1 = f2;
f2 = f;
endfor;
endif;
endfunction;
12
Recursão e Iteração
• Esta é a situação da função de Fibonacci, em que o seu valor
depende de 2 chamadas recursivas. Como as chamadas são
independentes, a mesma função acaba por ser recalculada
várias (muitas !) vezes.
7
5
6
5
4
3
2
1
3
3
2
4
4
2
1
2
3
2
1
2
3
2
2
1
1
Na realidade, o número de funções chamadas é o
próprio número de fibonacci (7:1, 6:1, 5:2, 4:3, 3:5,
2:8) que aumenta exponencialmente com o valor de n.
13
Recursão e Iteração
• Para se ter uma ideia do aumento, podemos notar que
apesar de inicialmente pequenos
n
fib(n)
1
1
2
1
3
2
4
3
5
5
6
8
7
13
8
21
9
34
10
55
11
89
os números tornam-se rapidamente “enormes”
n
fib(n)
n
fib(n)
26
27
28
29
30
31
32
121 393 196 418 317 811 514 229 832 040 1 346 269 2 178 309
45
46
47
48
49
50
1 134 903 170 1 836 311 903 2 971 215 073 4 807 526 976 7 778 742 049 12 586 269 025
tornando proibitiva a sua computação recursiva (normal).
14
Memorização
• Por vezes é possível aliar a declaratividade da versão recursiva,
com a eficiência da versão iterativa, através da memorização
dos valores já computados.
• Por exemplo se se pretenderem os primeiros 100 números de
fibonacci, pode criar-se uma tabela, fib_m, com os valores já
computados.
– Se fib_m(n) ainda não contiver o valor de fib(n), então
determina-se fib(n) e escreve-se em fib_m(n).
– Caso contrário, apenas se retorna o valor de fib_m(n).
• Para que este esquema seja posível, é conveniente que a tabela
fib_m seja visível por todas as instâncias da função fib(n). Para
evitar passá-la como parâmetro deve declarar-se como uma
variável global.
15
Variáveis Globais em Octave
• Em Octave, para que uma variável seja global, ela deve ser
declarada no programa principal com a declaração global. No
caso actual, se se pretende um vector linha com 100 elementos
inicializados a zero, devemos declarar a variável global
global fib_m = zeros(1,100)
• Uma vez declarada uma variável global, ela só pode ser
eliminada através da declaração clear. Se pretendermos um
vector com 200 elementos deveremos fazer
clear fib_m
global fib_m = zeros(1,200)
• Para que na função a variável, fib_m considerada seja a
variável global e não uma variável local, a variável deve ser
identificada novamente como global.
16
Memorização
function f = fib(n);
% versão recursiva com memória
global fib_m;
if fib_m(n) > 0
f = fib_m(n);
else
if n <= 2
f = 1;
else
fib_m(n) = fib_mem(n-1)+fib_mem(n-2);
f = fib_m(n);
endif;
endif;
endfunction;
17
Download

Factorial, Fibonacci e Maior Divisor Comum