Motivação
2
Escher: Metamorphosis (1937) - Drawing Hands (1948) – Relativity (1953)
http://www.worldofescher.com/gallery/
Alguém diz: “Esta sentença é falsa !”
Paradoxo do Cretense Mentiroso
um homem natural de Creta, em praça pública,
anuncia: “Todo cretense é mentiroso !”
Estas sentenças são Verdadeiras ou Falsas ?
O que há de comum neste slide ?
RECURSÃO !
Definições Recursivas
• Em uma definição recursiva um item é definido em termos
de si mesmo, ou seja, o item que está sendo definido
aparece como parte da definição;
Conceito de Recursão
4
• Em uma definição recursiva um item é definido em termos de si mesmo, ou seja,o item
que está sendo definido aparece como parte da definição;
• Mas, atenção: a definição f(x) f(x) não revela nada sobre a função f.
• A definição recursiva da função fatorial fat(n) = n (n 1) 1 é a seguinte:
• Uma definição recursiva é definida por um ou mais casos base e por um ou mais passos
indutivos envolvendo a chamada da própria função (denominada “chamada recursiva”).
• O caso base é uma situação trivial da função, onde calcular o valor da função é imediato,
direto e trivial.
• O passo indutivo é a aplicação recursiva da função em uma versão de menor porte do
problema. Imagine o passo indutivo como sendo um degrau, situado logo abaixo do nível
do problema proposto originalmente, em direção a um caso base. A maior dificuldade neste
método de resolver problemas é termos confiança na solidez desse degrau ! Isto é:
confiança na validade de adotarmos a Hipótese Indutiva. que corresponde a supor a
versão de menor porte do problema como estando completamente resolvida. Isto é um
“método indutivo para resolver problemas”.
• O termo “indutivo” é usado para salientar que a solução em um passo é induzida pela
solução do passo anterior. A relação entre recursão e indução é apresentada mais a frente.
0),1(
0,1)(
nsenfatn
nsenfat
Caso Base
Recorrência (ou Passo Indutivo)
Chamada Recursiva (ou Hipótese Indutiva)
O processo da Recursão
5
• A definição recursiva é uma maneira de especificar a solução de uma instância de um
problema de tamanho n em termos de soluções para instâncias menores.
• Neste processo, cria-se uma pilha de operações pendentes (uma expansão) que são
resolvidas quando se encontra o caso base (uma contração). Por exemplo:
Problema tamanho nfat(3) = 3 fat(2)
fat(2) = 3 fat(1)
fat(1) = 2 fat(0)
fat(0) = 1 Caso base
expansão
contração
3 * fat(2)
3 * (2 * fat(1))
3 * (2 * (1 * fat(0)))
3 * (2 * (1 * 1)) Caso Base
3 * (2 * 1)
3 * 2
6
fat(3)
Implementação de uma Definição Recursiva
6
• Seja a definição recursiva de fatorial:
• Uma vez que encontramos a definição recursiva de uma função, a implementação é
direta, simples, um trabalho automático:
• Você pode verificar se a sua definição e a sua implementação estão corretas
acompanhando a execução de um exemplo:
• Este acompanhamento chama-se TRAÇO. Não dependa de fazer o traço para encontrar
a definição recursiva de uma função. Use o traço para verificar e não para criar a solução.
• Ademais, somente funções matemáticas admitem o TRAÇO. Traço não faz sentido para
procedimentos (e.g. funções em C que disparam ações e/ou não retornam valor). Nestes
casos, acompanham-se as variáveis do procedimento. As variáveis definem o estado do
mundo. … e nem sempre é fácil acompanhar o estado do mundo!
fat(0) = 1
fat(n) = n fat(n 1)
int fat (int n){
if (n==0) return 1;
elsereturn n*fat(n-1);
}
Outro Exemplo de Definição Recursiva
7
• A definição recursiva da soma da série de números naturais, g(n) =1 2 … n , é:
• Este exemplo é mais interessante que o do fatorial, porque há uma bela forma fechada
que representa esta soma:
• Nem toda a definição recursiva tem uma bela forma fechada (… raras têm!). Aliás, quando
tem, não faz sentido implementar a função recursivamente no computador.
• A implementação recursiva de g(n) também é imediata:
• Note que não existem construtores de loop (e.g. for ou while) na implementação de
definições recursivas! Há famílias de linguagens que não têm estes construtores (for e
while), porque usam recursão (e.g. linguagens funcionais, como Lisp, e linguagens de
programação em lógica, como Prolog).
g(1) = 1
g(n) = g(n 1) + n
1 2 … n = n(n 1)/2
int g(int n){
if (n == 1)return 1;
elsereturn n + g(n - 1);
}
Recursão e Indução (Material Avançado)
8
• Existe uma estreita ligação entre indução e definições recursivas. Indução é talvez a
forma mais natural de raciocinar sobre processos recursivos.
• Indução é um método para provar que uma afirmação S(n) é válida para todo n:
• Primeiro mostre que S(1) (ou S(0)) é valida
• Depois assuma que S(k) é válida (esta é a hipótese indutiva)
• Mostre que S(k+1) é válida
• Então, S(n) é válida para todo n.
• Um exemplo clássico: provar que 1 2 … n = n(n 1)/2 para todo n
• S(1) = 1(1 1)/2 = 1, i.e. S(1) é válida!
• Hipótese: 1 2 … k = k(k 1)/2 é uma afirmação válida
• Demonstração que S(k+1) é válida, i.e. 1 2 … k (k 1)= (k 1)(k 2)/2
• Pela hipótese, o lado esquerdo é k(k 1)/2 (k 1)
• Colocando (k 1) em evidência, tem-se: (k 1)(k/2 +1), i.e. (k 1)(k 2)/2
• Indução cai naturalmente no mesmo paradigma da definição recursiva. São duas
variantes do mesmo tema.
• A forma recursiva para o exemplo acima já vimos que é:
• Nem toda a definição recursiva tem uma bela forma fechada (como a n(n 1)/2)
g(1) = 1
g(n) = g(n 1) + n
Este assunto não cai em
provas. São informações
que ajudam a entender
recursão.
Um Roteiro para Encontrar Soluções Recursivas
9
• Devemos observar que, no método indutivo, o problema vai sucessivamente caindo em
problemas de menor porte, até que o caso base é alcançado.
• O passo indutivo corresponde a descobrirmos como modificar o valor vindo da chamada
recursiva para ter o resultado final procurado. E o que devemos fazer é sempre muito
simples, como uma espécie de retoque final (pois o maior trabalho já foi feito pela
hipótese indutiva, ou seja: pela chamada recursiva)
• Não começe tentando simular a execução (deixe isto como teste após encontrar solução.
• Como um roteiro faça sempre o seguinte (até ficar automático no seu cérebro):
1. Defina o domínio e a imagem da função (lembre que são conjuntos: conjunto dos
naturais, conjunto de todos os strings, ...). E entenda o que a função faz (escreva
exemplos). Saber o que a função faz, ajuda muito a encontrar a chamada recursiva!!!
2. Verifique se há precondições (e.g. algum valor não permitido, ou se é simplesmente a
condição de pertencer ao domínio, ou ...).
3. Identifique os elementos mais neutros, simples, do domínio. Os casos base
geralmente (mas não necessariamente) dizem respeito a eles.
4. Formule os casos base
5. Encontre as chamadas recursivas (um degrau a menos na direção do caso base). A
chamada recursiva resolve a maior parte do problema e retorna um valor concreto.
6. Encontre os passos indutivos como sendo um retoque que você faz com o valor
recebido da chamada recursiva. Se você começar a complicar, é sinal de que está
errado !!Com a prática, junte 5 e 6. Depois de formular a solução conceitual, pense em como
funcionará na máquina, … esboçando o código!
Problema de Ordem-n:
Precondição: não há situações especiais
Caso(s) Base:
Hipótese Indutiva (Chamada Recursiva):
Passo(s) Indutivo(s):
naturais (inteiros não-negativos)
inteiros (neg. e não-negativos).
R reais
S conjunto das strings
fat(n) :
n
n=0 fat(n) = 1
fat(n-1)
fat(n) = n * fat(n-1)
Usando o roteiro do slide anterior, primeiro escreva um esboço de solução da maneira
mais independente possível da linguagem de computador em questão. Vamos chamar esta
solução de Solução Conceitual. Por exemplo:
Depois escreva o Código!
E, por fim, tente seguir a execução do Código.
Solução Conceitual
Tamanho e Cópia de Strings
int main(void)
{
...
printf("----\ntamanho= %d\n",stringLen(a));
stringCopy(n,"ana");
printf("n=%s\n",n);
stringCopy(n,"oi");
printf("n=%s\n",n);
printf("n[2]=%d n[3]=%d\n\n",n[2],n[3]);
return 0;
}
n=oi
----
tamanho= 3
n=ana
n[2]=0 n[3]=0
valor numérico de \0 é 0 (zero).
Imprimir \0 como %c dá branco
Escreva as funções stringLen e stringCopy recursivamente.
Não veja as respostas nos slides seguintes. Tente resolver!
Exemplo: tamanho de um string
13
int tamanho(char * s)
{
if (*s == '\0')
return 0;
else
return 1 + tamanho(s+1);
}
Solução conceitual:
tamanho(s): S N
se s é vazia então tamanho é 0
caso contrário
tamanho(s) = 1 + tamanho do resto da string
int tamanho(char * s) // versao com sintaxe [] de indices
{
if (s[0] == '\0')
return 0;
else
return 1 + tamanho(&s[1]);
}
Exemplo imprimir string
14
void imprimeStr(char * s) // versao ponteiro
{
if (*s) // ou: if (*s != '\0')
{
printf("%c",*s);
imprimeStr(s+1);
}
}
#include <stdio.h>
void imprimeStr(char *);
int main(void)
{
char s[] = "Bom Dia";
imprimeStr(s);
printf("\n");
return 0;
}
/* versao vetor
void imprimeStr(char * s)
{
if (s[0]) // ou: if (s[0] != '\0')
{
printf("%c",s[0]);
imprimeStr(&s[1]);
}
}
*/
Solução conceitual
imprime(s)
se s é vazia então nada imprime
caso contrário
imprime 1 caractere de s e depois imprime resto da
string
Imprimir string invertido:
void imprimeInv(char * s)
{
if (*s)
{
imprimeInv(s+1);
printf("%c",*s);
}
Exemplo stringCopy
15
void stringCopy(char * s, char * t)
{
*s = *t;
if (*t)
stringCopy(s+1,t+1);
}
#include <stdio.h>
void stringCopy(char *, char *);
int main(void)
{
char s[] = "Bom Dia";
char * a = (char *)malloc(51);
stringCopy(a,s);
return 0;
}
Solução conceitual
stringCopy(s,t) copia t para s
se t é vazia, então s é vazia
caso contrário
copia 1 caractere de t para s e
copia restante da string t para a próxima posição
de s
void stringCopy(char * s, char * t)
{
if (*s = *t)
stringCopy(s+1,t+1);
}
void stringCopy(char * s, char * t)
{
if (*t == '\0')
*s == '\0‘;
else
{
*s = *t;
stringCopy(s+1,t+1);
}
}
void stringCopy(char * s, char * t)
{
s[0] = t[0];
if (t[0]) // ou if (t[0] != '\0’)
stringCopy(&s[1],&t[1]);
}
ou de maneira
mais concisa
Ou ainda: Ou usando sintaxe [ ] de índice:
16
Exemplo: Potenciação
• Exemplo: função recursiva para cálculo de potenciação
/* Função recursiva para cálculo de potenciacao */
int pot (int x, int n)
{
if (n==0)
return 1;
else
return x*pot(x,n-1);
}
Caso BASE
Passo
Recursivo
17
Exemplo com 2 Casos Base e 2 Chamadas Recursivas
• Série de Fibonacci (um termo é a soma dos dois anteriores): 0, 1, 1, 2, 3, 5, 8, …
• Definição recursiva da Série de Fibonacci:
• Implementação:
fib(0) = 0
fib(1) = 1
fib(n) = fib(n 1) + fib(n 2) se n 1
int fib (int n){
if (n==0) return 0;
else if (n==1) return 1;
elsereturn (fib(n-1) + fib(n-2));
}
19
Funções Recursivas
• Tipos de recursão:
– direta:
• uma função A chama a ela própria
– indireta:
• uma função A chama uma função B que, por sua vez, chama A
• Comportamento:
– quando uma função é chamada recursivamente,
cria-se um ambiente local para cada chamada
– as variáveis locais de chamadas recursivas são independentes
entre si, como se estivéssemos chamando funções diferentes
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
-
3
-
3n
r
main
n
f
fat(3)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
-
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
-
1
-
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
n
f
fat(1)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
-
0
-
1
-
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
n
f
fat(1)
n
f
fat(0)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
1
0
-
1
-
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
n
f
fat(1)
n
f
fat(0)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
1
1
-
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
n
f
fat(1)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
2
2
-
3
-
3n
r
main
n
f
fat(3)
n
f
fat(2)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
6
3
-
3n
r
main
n
f
fat(3)
08/03/10 (c) Casanova, Gattass, Viterbo
Funções Recursivas
#include <stdio.h>
int fat (int n);
int main (void)
{ int n = 3;
int r;
r = fat ( n );
printf("Fatorial de %d = %d \n", n, r);
return 0;
}
/* Função recursiva para cálculo do fatorial */
int fat (int n)
{
int f;
if (n==0)
f=1;
else
f= n*fat(n-1);
return f;
}
6
3n
r
main