Programação I Aula 16 Mais exemplos de recursãopbv/aulas/programacaoI/teorica-16.pdf · Usar...
Transcript of Programação I Aula 16 Mais exemplos de recursãopbv/aulas/programacaoI/teorica-16.pdf · Usar...
Programação IAula 16 — Mais exemplos de recursão
Pedro Vasconcelos
DCC/FCUP
2018
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 1 / 27
Nesta aula
1 Desenhar árvores
2 Sequência de Fibonnacci
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 2 / 27
Desenhar árvores
Vamos fazer uma função recursiva para desenhar árvores usandoturtle graphics.
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 3 / 27
Desenhar árvores (cont.)
Parameterizamos a árvore pelo número de bifurcações n.
Para desenhar uma árvore de ordem n:1 desenhamos o tronco;2 rodamos à esquerda e desenhamos uma árvore de ordem n − 1;3 rodamos à direita e desenhamos outra árvore de ordem n − 1.
Entre os passos 2 e 3 devemos repor a posição e orientação datartaruga.
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 4 / 27
Desenhar árvores (cont.)
Mais alguns parâmetros:
size tamanho em pixelsratio fração de tamanho do
troncoangle ângulo de rotação
para as sub-árvores
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 5 / 27
Em Python
def draw_tree(n, angle, ratio, size):forward(ratio*size) # desenhar uma linhaif n>0: # caso recursivo
nsize = (1-ratio)*size(x,y) = position() # guardar posiçãoh = heading() # e orientaçãoleft(angle)draw_tree(n-1, angle, ratio, nsize)penup() # não desenhargoto(x,y) # repor a posiçãosetheading(h) # e orientaçãopendown() # voltar a desenharright(angle)draw_tree(n-1, angle, ratio, nsize)
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 6 / 27
Exemplos: draw_tree(6, . . . )
angle = 30 angle = 60 angle = 90
ratio
=0.
3ra
tio=
0.5
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 7 / 27
Extras
Exercícios da folha 9:Mudar a cor de ramos e a espessura do traço conforme o nível nUsar random para escolher o ângulo e/ou redução do tamanhode ramos aleatoriamente
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 8 / 27
Nesta aula
1 Desenhar árvores
2 Sequência de Fibonnacci
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 9 / 27
Sequência de Fibonnacci
Conhecida na Índia (700 DC)Introduzida na Europa por Leonardo de Pisa (1202 DC)Modelo simplificado do crescimento de uma população:
um par de coelhos são largados num campo;os coelhos podem acasalar com um mês;no fim do segundo mês, nasce um novo par de coelhos;os coelhos não morrem e cada par produz sempre um novo par aofim de um mês.
Quantos pares de coelhos existem ao fim de um ano?
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 10 / 27
Sequência de Fibonnacci
mês 0 1 2 3 4 5 6 7 . . .pares 1 1 1 + 1︸ ︷︷ ︸
=2
1 + 2︸ ︷︷ ︸=3
2 + 3︸ ︷︷ ︸=5
3 + 5︸ ︷︷ ︸=8
5 + 8︸ ︷︷ ︸=13
8 + 13︸ ︷︷ ︸=21
. . .
Inicialmente há 1 parAo fim de 1 mês o par acasalou (mas ainda há só 1 par)Ao fim de 2 meses nasce um novo parAo fim de 3 meses, nascem 2 pares (da primeira e segundafêmea)Ao fim de 4 meses, nascem 3 pares. . .Depois dois valores iniciais, cada número é a soma dos doisimediatamente anteriores
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 11 / 27
Recorrência
O n-ésimo número de Fibonnacci Fn é dado pela seguinte relação derecorrência:
F0 = 1F1 = 1Fn = Fn−1 + Fn−2 (n > 1)
Vamos usar esta relação para calcular a sequência de Fibonnacci comuma função recursiva em Python.
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 12 / 27
Implementação recursiva
def fib(n):"Calcular o n-ésimo número de Fibonnaci."if n == 0 or n == 1: # caso base
return 1else: # caso recursivo
return fib(n-1) + fib(n-2)
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 13 / 27
Testando a implementação
Vamos testar esta função imprimindos os primeiros 100 valores dasequência.
print("n", ":", "F_n")for n in range(100):
print(n, ":", fib(n))
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 14 / 27
Testando a implementação (cont.)
n : F_n0 : 11 : 12 : 23 : 34 : 55 : 86 : 137 : 21...
30 : 134626931 : 217830932 : 352457833 : 5702887KeyboardInterrupt
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 15 / 27
Testando a implementação (cont.)
Os resultado estão corretos mas o cálculo demora cada vez maistempo à media que n aumenta
A partir de n ≈ 30 demora vários segundos por cada novo valor
Esta implementação é pouco eficiente!
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 16 / 27
Eficiência
Vamos fazer um gráfico do tempo de computação em função de n
Começamos por gerar um ficheiro de texto com a tabela queassocia a cada n o tempo de computação de fib(n)
Para obter tempos usamos a bibloteca time (permite acesso aorelógio interno do computador)
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 17 / 27
Eficiência (cont.)
import timef = open("fib_time.txt", "w")for n in range(40):
t0 = time.clock() # relógio inicialfn = fib(n)t1 = time.clock() # relógio final# a diferença entre os valores do relógio# dá-nos o tempo do cálculo (em segundos)delta = t1 - t0f.write("%d\t %f\n" % (n,delta))
f.close()
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 18 / 27
Eficiência (cont.)Para traçar o gráfico vamos usar o utilitário GNUplot:
gnuplot> plot("fib_time.txt")
0
5
10
15
20
25
30
35
40
45
0 5 10 15 20 25 30 35 40
CPU
tim
e
n
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 19 / 27
Análise
Para cada n > 1 necessitamos de resolver dois sub-problemas detamanho n − 1 e n − 2O tempo T (n) de cálculo de fib(n) satisfaz
T (n) = · · ·+ T (n − 1) + T (n − 2)
A solução desta recorrência é uma função exponencial em nPodemos evitar o crescimento exponencial evitando cálculosdesnecessários
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 20 / 27
Cálculos desnecessários
Exemplo: calcular fib(4)
fib(4)= fib(3) + fib(2)= (fib(2) + fib(1)) + fib(2)= (fib(2) + fib(1)) + (fib(1) + fib(0))= ((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))
O cálculo de fib(2) é repetido desnecessariamenteSempre que calculamos fib(n) temos de calcular todosinferiores a n
Ideia: guardar valores já calculados para evitar repetir trabalho
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 21 / 27
Fibonacci com memorização
Vamos usar um dicionário global para guardar resultadospreviamente calculados
Inicialmente o dicionário começa vazio
No inicio da função testamos se o valor já foi calculo e retornamosimediamente nesse caso
Após calcular um novo valor guardamos no dicionário
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 22 / 27
Fibonacci com memorização (cont.)
fib_memo = {} # inicialização do dicionário
def fastfib(n):"Calcular o n-ésimo Fibonnacci c/memorização."# verificar se o resposta já foi calculada# e nesse retorná-la imediatamenteif n in fib_memo:
return fib_memo[n]# caso contrário, calcular como anteriormenteif n == 0 or n == 1:
r = 1else:
r = fastfib(n-1) + fastfib(n-2)# memorizar a resposta e retornarfib_memo[n] = rreturn r
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 23 / 27
Observações
O dicionário fib_memo foi declarado fora da funçãoDeve ser uma variável global para que os valores memorizadospersistam depois do retorno da funçãoPor exemplo: depois de calcular fib(10) o dicionário vai contertodos os valores de fib(0) até fib(10)
>>> fib_memo{}>>> fastfib(10)89>>> fib_memo{1: 1, 0: 1, 2: 2, 3: 3, 4: 5, 5: 8,6: 13, 7: 21, 8: 34, 9: 55, 10: 89}
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 24 / 27
Observações (cont.)
Com memorização conseguimos calcular Fibonnacci para n muitomaiores praticamente instantaneamente:
>>> fastfib(40)165580141>>> fastfib(500)225591516161936330872512695036072072046011324913758190588638866418474627738686883405015987052796968498626
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 25 / 27
Solução iterativa
Podemos também calcular números de Fibonnacci de formaiterativa (i.e. sem recursão)
Depois dos dois primeiros valores cada número é a soma dosanteriores
Ideia: basta guardar os dois números anteriores para calcular oseguinte
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 26 / 27
Solução iterativa (cont.)
def iterfib(n):# dois valores seguidos na sequência# inicialmente fib(0)=1, fib(1)=1v0 = 1v1 = 1# repetir n-1 vezesfor i in range(n-1):
v0, v1 = v1, v0+v1return v1
Esta solução é eficiente e não necessita de guardar todos os valoresjá calculados.
Pedro Vasconcelos (DCC/FCUP) Programação I Aula 16 — Mais exemplos de recursão 2018 27 / 27