Funções, Execução Condicional,
Recursividade e Iteração
Jorge Cruz
DI/FCT/UNL
Programação para as Ciências Experimentais
1º Semestre 2005/2006
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
1
Programas e Funções
• Como foi visto no exemplo anterior, 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 é feito na
“matemática”. Por exemplo,
tg(x) = sin(x) / cos(x).
• Em algumas linguagens de programação, 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.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteraçã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.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
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
n
se n  0
de base e a cláusula recursiva:

fact( n )  
n * fact( n  1 ) se n  0
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
4
Execução Condicional
• Quando queremos, como no caso do factorial, que um
programa execute uma de duas alternativas, dependendo do
valor de uma condição, deve ser usada a instrução “se”:
se Condição então
% alternativa a executar quando a condição for verdadeira
senão
% alternativa a executar quando a condição for falsa
fim se;
• Em Condição deve estar uma expressão cujo resultado ou é
verdadeiro ou é falso (para que, em tempo de execução, se
possa decidir qual a alternativa a executar)
• As expressões cujo resultado pode ser verdadeiro ou falso
chamam-se Expressões Booleanas.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
5
Expressões Booleanas
• Expressões booleanas podem ser construidas recursivamente a
partir de outras mais simples com os operadores booleanos de
– Conjunção, “e” ou “and”, expressa como & em OCTAVE
– Disjunção, “ou” ou “or”, expressa como | em OCTAVE
– Negação, “não” ou “not”, expressa como ! em OCTAVE
• As variáveis booleanas podem tomar os valores verdade ou falso.
Em OCTAVE, que só considera variáveis “numéricas”, 0
corresponde a falso e qualquer outro valor a verdade!
• De notar que uma variável booleana pode ser atribuído o valor
booleano de uma comparação numérica. Por exemplo:
encontrada = (x > xmin & y > ymax)
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
6
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;
21 Outubro 2005
% elemento base
% definição recursiva
Funções, Execução Condicional, Recursividade e Iteração
7
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.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
8
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).
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
9
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)!
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
10
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 !
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
11
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!
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
12
Recursividade para Resolução de Problemas
• A recursividade pode ser usada na resolução de problemas,
difíceis de resolver por outras técnicas de programação.
• Tal é o caso das “Torres de Hanoi”: dadas três torres (estacas)
pretende-se passar uma “pirâmide” de peças ordenadas de
uma torre para outra, movendo-se uma peça de cada vez, para
o topo de uma torre encimada por uma peça menor.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
13
Torres de Hanoi - Recursividade
•
•
Apesar de aparentemente complicado, este problema tem
uma solução recursiva simples.
Para passar n peças de uma torre (A) para outra (C)
1. Passar n-1 peças da torre inicial (A) para a torre livre (B)
2. Mover a última peça, para a torre final (C)
3. Passar as n-1 peças da torre B para a torre final (C).
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
14
Torres de Hanoi: Movimentos Necessários
• Baseados nesta resolução recursiva podemos
– Determinar o número de movimentos necessários
– Determinar os movimentos necessários
• O número de movimentos necessários é bastante simples de
determinar, na versão recursiva
hanoi_count(n) = hanoi_count(n-1)+1+ hanoi_count(n-1)
• Neste caso, pode-se evitar a dupla recursividade (ver adiante)
de uma forma muito simples
hanoi_count(n) = 2*hanoi_count(n-1) + 1
• Finalmente, há que especificar a condição de paragem (n=1)
hanoi_count(1) = 1
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
15
Torres de Hanoi: Movimentos Necessários
• Um programa Octave para resolver o programa é imediato
function c = hanoi_count(n)
if n == 1
c = 1;
else
c = 2*n_hanoi_count(n-1)+1;
end;
endfunction;
• De notar o aumento exponencial do número de movimentos
necessários
n
1 2 3 4 5 6 7 8 9 10 11 12 13
14
15
count 1 3 7 15 31 63 127 255 511 1 023 2 047 4 095 8 191 16 383 32 767
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
16
Torres de Hanoi
• O problema propriamente dito pode ser resolvido com base
num vector T, com 3 números, correspondentes ao número de
peças em cada uma das torres.
T = [3,1,1]
• Notar que não é necessário indicar o tamanho de cada peça,
porque o algoritmo nunca coloca uma peça sobre uma menor!
• O movimento de uma só peça da torre A para a torre B, usado
no corpo da recursão e na sua terminação, pode ser feito com
uma função auxiliar, move_hanoi(T,A,B), especificada da
forma óbvia (tira uma peça de A e aumenta em B).
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
17
Torres de Hanoi
function T = hanoi(T,N,A,B) % move N peças de A para B
if N == 1
T = hanoi_move(T,A,B); % move a peça de A para B
else
C = 6-A-B;
% C é a outra torre!
T = hanoi(T,N-1,A,C); % move N-1 peças de A para C
T = hanoi_move(T,A,B); % move 1 peça de A para B
T = hanoi(T,N-1,C,B); % move N-1 peças de C para B
endif;
endfunction;
function T = hanoi_move(T,A,B)
T(A) = T(A) - 1;
T(B) = T(B) + 1;
disp(T);
endfunction;
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
18
Torres de Hanoi
• O funcionamento do programa pode ser visto com a
chamada
>> hanoi([4,0,0],4,1,3);
4
0
0
3
1
0
2
1
1
hanoi(_,2,1,3)
2
0
2
1
1
2
move(_,1,2)
2
1
1
hanoi(_,2,3,2)
2
2
0
1
3
0
0
3
1
0
2
2
1
1
2
hanoi(_,2,2,1)
2
1
1
move(_,2,3)
2
0
2
1
1
2
hanoi(_,2,1,3)
0
1
3
0
0
4
21 Outubro 2005
hanoi(_,3,1,2)
move(_,1,3)
hanoi(_,3,2,3)
Funções, Execução Condicional, Recursividade e Iteração
19
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”.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
20
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;
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
21
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
21 Outubro 2005
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.
Funções, Execução Condicional, Recursividade e Iteração
22
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).
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
23
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.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
24
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.
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
25
Memorização
function f = fib_mem(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;
21 Outubro 2005
Funções, Execução Condicional, Recursividade e Iteração
26
Download

Funções, Execução Condicional, Recursividade e Iteração