programação ii recursÃo - bfeijo/prog2/progii_recursao_parte1_e_parte2.pdf · simples, como uma...

32
Programação II RECURSÃO Bruno Feijó Dept. de Informática, PUC-Rio

Upload: others

Post on 05-Sep-2019

7 views

Category:

Documents


0 download

TRANSCRIPT

Programação II

RECURSÃO

Bruno Feijó

Dept. de Informática, PUC-Rio

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)(

nsenfatnnse

nfatCaso BaseRecorrê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 Base3 * (2 * 1)3 * 26

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) = 1fat(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 de 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. Aliás, quando tem, não faz sentido implementar a função recursivamente no computador.

• Faz diferença escrevermos a chamada recursiva no início ou no fim da expressão? Isto é:

• A resposta é: matematicamente não faz diferença; mas, se formos implementar no computador, pode fazer diferença.

• A razão é porque, quando a chamada recursiva fica por último, as operações pendentes podem ir sendo resolvidas imediatamente e facilmente (sem criar uma pilha sempre crescente):

• A função que pode ter a chamada recursiva no final é denominada “recursiva de cauda” (tail recursive). Mas, nem toda a função pode ser recursiva de cauda.

g(1) = 1g(n) = g(n − 1) + n

1 + 2 + … + n = n(n + 1)/2

g(n) = g(n − 1) + n ou g(n) = n + g(n − 1)fat(n) = fat(n − 1) × n ou fat(n) = n × fat(n − 1)

fat(3)fat(2) * 3(fat(1) * 2) * 3...

fat(3)3 * fat(2)3 * (2 * fat(1))...

8

• A definição recursiva de g(n) =1 + 2 + … + n é:

• A implementação é 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).

• Em C, o fato de escrevermos a função g(n) na forma recursiva de cauda (como acima), não significa que o compilador vai tratá-la como tal. Recursão de cauda não é natural para compiladores C. Portanto, o código acima vai criar uma pilha gigante (e ineficiente). É possível definir funções recursivas de cauda em C, mas não é fácil para o programador iniciante (nem tem a elegância e a clareza de código que gostaríamos). Em compensação a linguagem C faz muitas outras coisas tremendamente eficientes e simples!

g(1) = 1g(n) = g(n − 1) + n

int g(int n){

if (n == 1)return 1;

elsereturn n + g(n - 1);

}

Recursão e Indução

9

• 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) = 1g(n) = g(n − 1) + n

Um Roteiro para Encontrar Soluções Recursivas

10

• 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 base5. 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 !!

SOLUÇÃO CONCEITUAL

11

Solução Conceitual

Problema de Ordem-n:

Precondição:

Caso(s) Base:

Hipótese Indutiva (Chamada Recursiva):

Passo(s) Indutivo(s):

Ν inteiros não-negativosΖ inteiros neg. e não-neg.R reaisSimb símbolos de caracteresSeq sequênciasItem itens...

fat(n) : Ν → Ν

n ∈ Ν

n=0 → fat(n) = 1

fat(n-1)

fat(n) = n * fat(n-1)

Use combinações com Seq paradefinir domínios mais específicos.Por exemplo: SeqΝ sequências de inteiros

⟨2, 3, 7⟩SeqSimb sequências de símbolos

⟨a, b, c⟩

Solução Conceitual

Problema de Ordem-n:

Precondição:

Caso(s) Base:

Hipótese Indutiva (Chamada Recursiva):

Passo(s) Indutivo(s):

Se você não quiser usar a notação do slide anterior (Ν,…,Simb,…), você pode escrever sentenças em Português. Fica menos preciso, mas é perfeitamente válido.

A função fat recebe um número natural n e retorna um número natural que é o fatorial de n

n só pode ser um número natural

Se n é zero, a função fat retorna 1

O valor de fat para n-1 é conhecido

A função fat retorna o valor de n multiplicado pelo valor de fatpara n-1

EXEMPLOS

14

Exemplo imprimir string

15

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 vetorvoid imprimeStr(char * s){

if (s[0]) // ou: if (s[0] != '\0'){

printf("%c",s[0]);imprimeStr(&s[1]);

}}*/

imprime(s)se s é nulo então nada imprimecaso contrário

imprime 1° caractere de s (*s) e imprime resto do string

Imprimir string invertido:

void imprimeInv(char * s){

if (*s){

imprimeInv(s+1);printf("%c",*s);

}

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]=0valor 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

17

int tamanho(char * s){

if (*s == '\0')return 0;

elsereturn 1 + tamanho(s+1);

}

tamanho(s)se s é nulo então tamanho é 0 (i.e. retorna 0)caso contrário

tamanho(s) = 1 + tamanho do resto do string(i.e. retorna 1 + tamanho(s+1))

int tamanho(char * s) // versao vetor{

if (s[0] == '\0')return 0;

elsereturn 1 + tamanho(&s[1]);

}

Exemplo stringCopy

18

void stringCopy(char * s, char * t) // copia t para s{ // versao vetor:

*s = *t; // s[0] = t[0];if (*t) // if (t[0]) ou if (t[0] != '\0')

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;

}

void stringCopy(char * s, char * t) {

while (*s++ = *t++);

}

stringCopy(s,t) // copia t para sse t é nulo (*t == ‘\0’) então s é nulocaso contrário

copia 1° caractere de t para s (i.e. *s = *t) e copia restante do string, i.e. stringCopy(s+1,t+1)

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);

}}

// stringCopy(&s[1],&t[1]);

19

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;

elsereturn x*pot(x,n-1);

}

Caso BASE

PassoRecursivo

20

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) = 0fib(1) = 1fib(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));

}

ILUSTRAÇÃO DA RECURSÃO EM C

21

22

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-3

n

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-3

n

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;

}

10-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;

}

11-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;

}

22-3-3

n

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;

}

63-3

n

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;

}

63

n

r

main

32

Referências

Waldemar Celes, Renato Cerqueira, José Lucas Rangel, Introdução a Estruturas de Dados, Editora Campus (2004)

Capítulo 4 – Funções (pag. 54)

Capítulo 7 – Cadeias de caracteres (pag. 91)