Algoritmos e Estruturas de Dados I –
Recursão
Profa. Mercedes Gonzales
Márquez
Recursão

A solução de um problema é dita recursiva quando
ela é escrita em função de si própria para instancias
menores do problema. Veja, por exemplo, o
problema de calcular o fatorial de um número n. A
solução iterativa pode ser facilmente implementado
assim:
Implementação não Recursiva
Função inteiro Fatorial (inteiro: n)
inteiro: i;
Início
result ← 1;
para i de 2 até n repita
result ← result * i;
Fim para
Retorne(result)
Recursão

A solução recursiva está diretamente ligada ao
conceito de indução matemática, no qual usa-se
como hipótese que a solução de um problema de
tamanho n pode ser obtida a partir da sua solução
de tamanho menor, por exemplo, n-1. No caso do
exemplo do fatorial teriamos:
Implementação Recursiva
Função inteiro Fatorial (inteiro: n)
inteiro: i;
Início
Se n=0 então
fatorial ← 1;
Senão
fatorial ← n*fatorial(n-1)
Fim se
Fim
Recursão
Toda recursão é composta por:
● Caso base
– Uma ou mais instâncias do problema que podem ser
solucionadas facilmente (solução trivial)
 Chamadas Recursivas
– O objeto define-se em termos de si próprio,
procurando convergir para o caso base.
Por exemplo, o fatorial de um número n pode ser
calculado a partir do fatorial do número n−1.
Recursão

Esquematicamente, os algoritmos recursivos tem a
seguinte forma:
Se <condição para o caso base> então
resolução direta para o caso base
Senão
uma ou mais chamadas recursivas
Fim se

Sem a condição de parada, expressa no caso base,
uma recursão iria se repetir indefinidamente.
Recursão
Implementação Recursiva
Função inteiro fatorial (inteiro: n)
inteiro: i;
Início
Se n=0 então
retorne(1)
Senão
retorne(n*fatorial(n-1))
Fim se
Fim
Teste de mesa para n=4
Fatorial = 1
Fatorial = 1 * (Fatorial(0))
1
Fatorial = 1
Fatorial = 2 * (Fatorial(1))
1
Fatorial = 2
Fatorial = 3 * (Fatorial(2))
2
Fatorial = 6
6
Fatorial = 4 * (Fatorial(3))
Fatorial = 24
Sequência de Fibonacci – Definição
Iterativa
0 1 1 2 3 5 8 13 …
Simulação:
Anterior = 0
Anterior = 1
Anterior = 1
Anterior = 2
Anterior = 3
….
atual = 1
atual = 1
atual = 2
atual = 3
atual = 5
seguinte = 1
seguinte = 2
seguinte = 3
seguinte = 5
seguinte = 8
Função inteiro fib(inteiro: n)
Inteiro: i, anterior, atual, seguinte
Início
se (n= 1)
retorne (0)
senão se (n = 2)
retorne (1)
senão
anterior ← 0
atual ← 1
para i de 3 até n repita
seguinte ← atual + anterior
anterior ← atual
atual ← seguinte
fim para
fim se
fim se
retorne (atual)
Sequência de Fibonacci – Definição
Recursiva
fib(1) = 0
fib(2) = 1
fib(3) = fib(2) + fib(1)
fib(4) = fib(3) + fib(2)
Fib(n) = fib(n-1) + fib(n - 2)
Função inteiro fib_rec(inteiro: n)
Início
se (n = 1)
retorne (0)
senão se (n = 2)
retorne (1)
senão
retorne fib_rec(n - 1) + fib_rec(n - 2)
fim se
fim se
Fim
Recursão
O que acontece na memória
Precisamos entender como é feito o controle sobre as
variáveis locais em chamadas recursivas.
A memória de um sistema computacional é dividida em
três partes:
Espaço Estático: Contém as variáveis globais e código
do programa.
Heap: Para alocação dinâmica de memória.
Pilha: Para execução de funções.
Recursão
O que acontece na pilha:
Toda vez que uma função é invocada, suas variáveis locais são
armazenadas no topo da pilha.
Quando uma função termina a sua execução, suas variáveis
locais são removidas da pilha. Considere o exemplo:
Função inteiro f1(inteiro: a, inteiro: b)
Inteiro: c
c←5
retorne (c+a+b)
Função inteiro f2(inteiro: a, inteiro: b)
Inteiro: c
c ← f1(b, a)
retorne (c)
/*Chamada */
f2(2, 3)
Recursão
O que acontece na memória
Quando f2(2,3) é invocada, suas variáveis locais são
alocadas no topo da pilha.
Recursão
O que acontece na memória
A função f2 invoca a função f1(b,a) e as variáveis locais
desta são alocadas no topo da pilha sobre as de f2.
Recursão
O que acontece na memória
A função f1 termina, devolvendo 10. As variáveis locais de f1
são removidas da pilha.
Recursão
O que acontece na memória
Finalmente f2 termina a sua execução devolvendo 10. Suas
variáveis locais são removidas da pilha.
Recursão
Exemplo 3. Soma de elementos de um vetor : Faça um
algoritmo que preencha por leitura um vetor de 10
elementos inteiros, imprima o seu conteúdo, e o
resultado do somatório dos seus elementos, calculado
por uma função recursiva.
Função inteiro soma(inteiro: n)
Início
se (n = 1)
retorne (v[n])
senão
retorne (v[n]+soma(n-1))
Fim
Recursão
Exemplo 4. Escreva uma função recursiva que recebe
como parâmetros um número real X e um inteiro n e
retorna o valor de Xn.
Obs.:n pode ser negativo.
Função Real Potencia(real: X,inteiro: n)
Início
Se ( n ==0 ) então retorne (1)
Senão
se ( n <0 ) então
retorne (1 / (X* Potencia(X, abs(N)-1))
senão retorne (X* Potencia(X, N -1))
Fim se
Fim se
Recursão
Exemplo
5. Reescreva a função abaixo tornando-a
recursiva. Esta função conta o número de algarismos
(dígitos) que possui um número inteiro n.
Função inteiro digitos(inteiro: n)
Inteiro: cont
Início
cont ← 1;
enquanto (abs(n )>9) faça
n = div(n,10)
cont ← cont+1
Fim enquanto
retorne (cont)
Fim
Recursão
Função inteiro digitos(inteiro: n)
Início
Se (abs( n )<10) então
retorne (1)
Senão
retorne (1+ digitos(div(n,10))
Fim se
Fim
Recursão
Exemplo
6. Escreva uma função recursiva que calcule a
soma dos dígitos de um inteiro positivo n. A soma dos
dígitos de 132, por exemplo, é 6.
Função inteiro somadigitos(inteiro: n)
Início
Se (n <10) então
retorne (n)
Senão
retorne (mod(n,10)+ somadigitos(div(n,10))
Fim se
Fim
Recursão
Exemplo
7. Escreva uma função recursiva que
determine o inverso de um número inteiro positivo n,
dado o número de dígitos de n. O inverso é obtido pela
inversão dos dígitos do número. O inverso do 132, por
exemplo, é 231.
Recursão
Exemplo
7. Escreva uma função recursiva que
determine o inverso de um número inteiro positivo n,
dado o número de dígitos de n. O inverso é obtido pela
inversão dos dígitos do número. O inverso do 132, por
exemplo, é 231.
Função inteiro inverso(inteiro: n, inteiro: ndig)
Início
Se (n <10) então
retorne (n)
Senão
retorne (mod(n,10)*10**(ndig-1)+ inverso(div(n,10),ndig-1)
Fim se
Fim
Recursão
Exemplo
8. Verificar se um número é capicua utilizando
uma função recursiva que considere como entrada o número
inteiro positivo n e o número de algarismos de n. Um número
capicua é aquele número que coincide com seu inverso, de
acordo a definição do exemplo 7.
Função lógico capicua(inteiro: n, inteiro: ndig)
Início
Se (n <10) então
retorne (1)
Senão
se (mod(n,10)<>div(n,10**(ndig-1))
retorne (0)
fim se
capicua((div(mod(n,10**(ndig-1))),10),ndig-2)
Fim se
Recursão
Exemplo
9. Seja uma linguagem hipotética na qual não
existem operadores para adição, nem subtração. Sabese que nesta linguagem, existe uma função prox(n) que
dá o sucessor do número n, e existe também uma função
ant(n) que dá o predecessor de um número n. Usando
apenas essas funções (prox e ant), defina a função
recursiva som(x,y), que toma como argumento os
números naturais x e y, e retorna sua soma.
Recursão
Função inteiro som(inteiro: x,y)
Início
Se (x=0) então
retorne (y)
Senão
se (y=0) então
retorne (x)
senão
retorne (som(ant(x),prox(y))
Fim se
Fim
Recursão
Exemplo 10- Faça um algoritmo que realize uma busca
em um vetor ordenado de elementos.

Pode-se realizar a busca de duas formas:
–
–
Busca Sequencial: os elementos são pesquisados
fazendo a varredura completa do vetor (ineficiente). O
pior caso ocorrerá quando o elemento estiver no último
índice do vetor.
Busca Binária
Busca Sequencial
Procurar por R
A
C
E
H
L
M
P
R
T
V
-8 Comparações!
-Se estivermos procurando o item V, o número de comparações seria
a quantidade de elementos no vetor
-O ideal seria dividir o vetor pela metade para então procurar (Busca
Binária)
Busca Binária


Divide seu vetor em duas metades
Três condições
1.
2.
3.
Se o item for igual ao item que está na metade do
vetor, o item foi encontrado
Se for menor, procure na primeira metade
Se for maior procure na segunda metade
Busca Binária
Procurar por R
1
2
3
4
5
6
7
8
9
10
A
C
E
H
L
M
P
R
T
V
X
I
I
X
F
-2 Comparações!
-Casos piores: quando os itens estiverem no início do vetor. Nesse
caso, seria melhor utilizar busca seqüencial. Mas como saber quando
o ítem está no início do vetor?
Busca Binária
Procedimento Busca_Binária(inteiro: x,Inicio, Fim);
Inteiro: meio
Início
meio ← div((inicio + fim), 2)
Se fim < inicio então
escreva (‘Elemento Não Encontrado’)
Senão se (v[meio]) = x então
escreva (‘Elemento está na posição ’,meio)
senão
se v[meio] < x então
inicio ← meio +1;
Busca_Binaria (x, inicio, fim);
senão
fim ← meio - 1;
Busca_Binaria (x, inicio, fim);
fim se
fim se
fim se
Recursão
Exemplo 11-Problema da Torre de Hanói
O problema ou quebra-cabeça conhecido como torre de
Hanói consiste em transferir, com o menor número de
movimentos, a torre composta por N discos do pino A
(origem) para o pino C (destino), utilizando o pino B como
auxiliar. Somente um disco pode ser
movimentado de cada vez e um disco não pode ser
colocado sobre outro disco de menor diâmetro.
Recursão
Solução: Transferir a torre com N-1 discos de A para B, mover
o maior disco de A para C e transferir a torre com N-1 de B
para C. Embora não seja possível transferir a torre com N-1 de
uma só vez, o problema torna-se mais simples: mover um disco
e transferir duas torres com N-2 discos. Assim, cada
transferência de torre implica em mover um disco e transferir
de duas torres com um disco a menos e isso deve ser feito até
que torre consista de um único disco.
Recursão
procedimento MoveTorre(inteiro:n, literal: Orig, Dest, Aux)
início
se n = 1 então
MoveDisco(Orig, Dest)
senão
MoveTorre(n - 1, Orig, Aux, Dest)
MoveDisco(Orig, Dest)
MoveTorre(n - 1, Aux, Dest, Orig)
fim se
fim
procedimento MoveDisco(literal:Orig, Dest)
início
Escreva(“Movimento: ”, Orig, “ -> ”, Dest)
fim
Recursão
Uma chamada a MoveTorre(3, ‘A’, ‘C’, ‘B’) teria a seguinte
saída:
Movimento: A -> C
Movimento: A -> B
Movimento: C -> B
Movimento: A -> C
Movimento: B -> A
Movimento: B -> C
Movimento: A -> C
Recursão
Exemplo 12A recursividade pode ser utilizada para gerar todas as
possíveis permutações de um conjunto de símbolos. Por
exemplo, existem seis permutações no conjunto de
símbolos A, B e C: ABC, ACB, BAC, BCA, CBA e CAB.
O conjunto de permutações de N símbolos é gerado
tomando-se cada símbolo por vez e prefixando-o a todas as
permutações que resultam dos (N - 1) símbolos restantes.
Consequentemente, permutações num conjunto de
símbolos podem ser especificadas em termos de
permutações num conjunto menor de símbolos. Formule
um algoritmo recursivo para este problema.
Recursão
Solução: O número total de permutações de n objetos é dado
por P(n) = n!. Ou seja: para 3 objetos P(3) = 3! = 3 x 2 x 1 = 6
permutações. Para 4 objetos temos: P(4) = 4! = 4 x 3 x 2 x 1 =
24 permutações. O recurso básico para a elaboração do
algoritmo consiste em montar uma árvore de recursão para
uma “instância” de solução para o problema, por exemplo 3
objetos.
Recursão
procedimento PermutaRecursiva(literal: str[], inteiro: k)
inteiro: i,n
Inicio
n ← tamanho(str)
se (k = n) então
escreve (str) /* Caso base */
senão /* Chamada recursiva */
para i de k até n repita
TroqueCarateres( str, k, i)
PermutaRecursiva(str, k + 1)
TroqueCarateres( str, i, k)
fim para
fim se
Fim
/* Chamada principal*/
PermutaRecursiva(str,1)
Recursão
- Na chamada recursiva :
Para cada posição i de vetor de caracteres, entre k e o
fim do vetor:
1. Troque os caracteres das posições i e k.
2. Gere todas as permutações do nível k+1, ou seja,
todas as permutações de n-1 caracteres.
3. Restaure o vetor original trocando as posições i e k.
Recursão
Exemplo 13O algoritmo Merge Sort tem como objetivo a ordenação de
um vetor A de n elementos reais. A sua estratégia é
baseada no paradigma dividir para conquistar.
1. Passo Dividir
Se um dado vetor A tem um ou nenhum elemento,
simplesmente retorne pois ele já está ordenado. Caso
contrário, divida A[p .. r] em dois subvetores A[p.. q] e
A[q + 1 .. r], cada um contendo a metade dos elementos de
A[p .. r]. q é o índice do médio de A[p .. r].
2. Passo Conquistar
Ordene recursivamente os dois subvetores A[p .. q] e
A[q + 1 .. r].
3. Passo combinar ou intercalar
Combine os elementos de volta em A[p .. r] mesclando os
subvetores ordenados A[p .. q] e A[q + 1 .. r] em uma
sequência ordenada. Para realiza esta etapa vamos definir
um procedimento Merge (A, p, q, r).
Recursão
Procedimento merge_sort (real: v[], inteiro: p, inteiro: r)
Inicio
inteiro: q
se (p < r) então
q ← (p + r) / 2;
merge_sort (v, p, q);
merge_sort (v, q, r);
merge (v, p, q, r);
fim se
Fim
Recursão
procedimento merge (real:
v[], inteiro: p, inteiro: q, inteiro: r)
Inicio
inteiro: i, j,k, nv1,nv2,*w
aloque(w, r-p) /*alocação dinâmica de memória que será melhor
explicado no próximo tópico sobre apontadores*/
i ← p, j ← q+1 , k ← 0
enquanto (i <= q && j <= r) faça
se (v[i] <= v[j]) então
k ← k+1, i ← i+1, w[k] ← v[i]
senão
k ← k+1, j ← j+1,w[k] ← v[j]
fim se
fim enquanto
enquanto (i < q) faça
k ← k+1,i ← i+1, w[k] ← v[i]
fim enquanto
enquanto (j <= r) faça
k ← k+1 j ← j+1, w[k] ← v[j]
fim enquanto
para i de p até r repita v[i] ← w[i-p] libera(w)
Recursão
Download

AEDI-recursao