Recurs˜
ao versus Itera¸
c˜
ao
[ePD94, Cap. 5.13-15] Problema: C´alculo de n! = n × (n − 1) × . . . × 1 int facti(int n) { int fac=n; while(n>0){ fac=fac*n; n--; } return fac; } Defini¸c˜ao recursiva: n! = 1 se n = 0 n × (n − 1)! se n ≥ 1 int factr(int n){ if(n==0) return(1); else return(n*factr(n-1)); }Chamadas recursivas e valores retornados
de cada chamada
Suponhamos n = 5 5! 5*4! 4*3! 3*2! 2*1! 1*0! 1 5! 5*4! 4*3! 3*2! 2*1! 1*0! 1 1 1*1=1 2*1=2 3*2=6 4*6=24 5*24=120Paradigmas
Paradigma iterativo:
uma sequˆencia de instru¸c˜oes ´e excutada duma forma repetitiva, controlada por uma dada condi¸c˜ao (p.e. decremento de um contador)(ciclo iterativo).
Paradigma recursivo:
1. existˆencia de casos simples, em que a resposta ´e determinada directamente; 2. ser poss´ıvel uma decomposi¸c˜ao duma instˆancia do problema, em instˆancias mais
simples da mesma forma.
3. repeti¸c˜ao por chamadas sucessivas: numa fun¸c˜ao recursiva, s˜ao criadas v´arias activa¸c˜oes dela pr´opria que desaparecem h´a medida que a execu¸c˜ao avan¸ca. Em cada momento apenas uma das activa¸c˜oes est´a activa, estando as restantes `a espera que essa termine para continuarem.
Equivalˆencia entre os paradigmas Os dois paradigmas s˜ao equivalentes: dada uma fun¸c˜ao recursiva existe sempre uma iterativa e vice-versa.
Escrever a representa¸
c˜
ao dum inteiro n na base b.
Fun¸c˜ao Iterativavoid num_b_ite(int n,int b) {
if (n<0) { printf("-"); n= -n;}
if(n==0) {printf("%d",n); return;}
while(n) { printf("%d",n%b); n=n/b; } } Fun¸c˜ao Recursiva:
void num_b(int n,int b) {
if (n<0) {printf("-");n= -n;} if(n/b) num_b(n/b,b);
printf("%d",n%b); }
Estas fun¸c˜oes n˜ao s˜ao exactamente equivalentes! Porquˆe? Como torn´a-las equiva-lentes?
Sequˆ
encia de Fibonacci
(outra vez...)0 1 1 2 3 5 8 13 21 34 55 89 144 . . .
Come¸cando com 0 e 1, cada termo seguinte ´e a soma dos dois anteriores Assim o n-´esimo termo ´e calculado por
fn = fn−1 + fn−2 e f0 = 0 e f1 = 1.
Ent˜ao, escrever uma fun¸c˜ao recursiva ´e imediato int fib(int n) {
if(n==0 || n==1) return n; return fib(n-1)+fib(n-2); }
Segue a execu¸c˜ao para f(4);. Como ´e uma vers˜ao iterativa?
O Puzzle das Torres de Hanoi
(inventado por Eduoard Lucas (1880))S˜ao dados trˆes suportes (a, b e c) e n discos de tamanhos diferentes.Os discos
est˜ao empilhados num dos suportes por ordem crescente de tamanhos.Pretende-se
mover os discos para outro suporte de modo que:
• em cada passo exactamente um disco seja movido de um suporte para o outro
• um disco n˜ao pode nunca estar por cima de um menor
Algoritmo (Fun¸
c˜
ao recursiva)
1. Representar cada suporte por um caracter (’a’, ’b’ e ’c’). Pretende-se uma fun¸c˜ao recursiva:
void hanoi(int n, char inicio, char fim, char temp);
2. Escrever uma fun¸c˜ao moveUm(char ini, char fim) que move a pe¸ca do cimo
do suporte ini para o suporte fim. moveUm(char ini, char fim){
printf("%c-> %c",ini,fim); }
3. A fun¸c˜ao hanoi() fica:
void hanoi(int n, char inicio, char fim, char temp) { if (n==1) moveUm(inicio,fim); else { hanoi(n-1,inicio,temp,fim); moveUm(inicio,fim); hanoi(n-1,temp,fim,inicio); } }
Execu¸
c˜
ao
%hanoi 3 a-> b a-> c b-> c a-> b c-> a c-> b a-> b %O algoritmo ´e expon^encial O(2n): O n´umero de movimentos ´e exponencial com n,
Tn = 2Tn−1 + 1
isto ´e,
Tn = 2n − 1
M´
etodo de ordena¸
c˜
ao Quicksort
C.A.R.Hoare 196056 25 37 58 95 19 73
M´etodo: Dada uma sequˆencia de valores, escolhe-se um elemento (pivot) e os
restantes valores s˜ao reagrupados em duas subsequˆencias (parti¸c˜ao):
• os que s˜ao menores que esse elemento
• os que s˜ao maiores.
O mesmo processo ´e aplicado recursivamente a cada subsequˆencia.Quando um
subconjunto tem menos que 2 elementos a recurs˜ao p´ara. Vamos escolher sempre o elemento do meio, como pivot.
void quicksort(int v[], int esq, int dir){ int i; if(esq>=dir) return ; i=particao(v,esq,dir); quicksort(v,esq,i-1); quicksort(v,i+1,dir); }
int particao(int v[],int esq,int dir){ int i, fim;
troca(v,esq,(esq+dir)/2); fim=esq;
for(i=esq+1;i<=dir;i++) if(v[i]<v[esq]) troca(v,++fim,i); troca(v,esq,fim);
return fim;
void troca(int v[], int i,int j){ int temp;
Execu¸
c˜
ao
Subvector: 56 25 37 58 95 19 73 Pivot: 58 Particao esq: 19 25 37 56 58 Particao dir: 95 73 Subvector: 19 25 37 56 Pivot: 25 Particao esq: 19 Particao dir: 37 56 Subvector: 37 56 Pivot: 37 Particao esq: Particao dir: 56 Subvector: 95 73 Pivot: 95 Particao esq: 73 Particao dir:Quicksort para ordenar uma vari´
avel
indexada de strings
void quicksort_s(char v[][MAXCOL],int esq, int dir){ int i;
if(esq>=dir) return;
i=particao_s(v,esq,dir);
quicksort_s(v,esq,fim-1); quicksort_s(v,fim+1,dir); }
void particao(char v[][MAXCOL], int esq,int dir) { int i,fim;
troca_s(v,esq,(esq+dir)/2); fim=esq;
for(i=esq+1;i<=dir;i++) if(strcmp(v[i],v[esq])<0) troca_s(v,++fim,i); troca_s(v,esq,fim);
returm fim;}
void troca_s(char v[][MAXCOL], int i,int j) { char temp[MAXCOL];
indexada de strings.
Exerc´ıcio 10.2. Escreve uma fun¸c˜ao recursiva que implemente o m´etodo de
ordena¸c˜ao por sele¸c˜ao.
Exerc´ıcio 10.3. Escreve uma fun¸c˜ao recursiva que dado n e k determine a
potˆencia k de n.
Exerc´ıcio 10.4. Escreve uma fun¸c˜ao recursiva que determine o m´aximo divisor
comun entre dois inteiros.
Exerc´ıcio 10.5. Escreve uma fun¸c˜ao recursiva que gere todas as permuta¸c˜oes
Retrocesso na Resolu¸
c˜
ao de Problemas de
Pesquisa
Para muitos problemas, o m´etodo de resolu¸c˜ao consiste em percorrer uma sequˆencia de decis˜oes, cada uma fazendo avan¸car no caminho para a solu¸c˜ao. Se se fizerem as escolhas correctas chega-se a uma solu¸c˜ao sen˜ao tem de ser retrocecer e escolher outros caminhos.
• Divis˜ao em subtarefas
• Pesquisar exaustivamente um ´arvore de sub-tarefas
• Uso de Recurs˜ao Exemplos:
• procura da sa´ıda de um labirinto
• procura de um caminho numa rede que liga v´arios pontos
INCONVENIENTE: O tamanho da ´arvore de pesquisa cresce exponencialmente
com o tamanho dos dados
A saltar vai a todo o lado ...
Problema: dado um tabuleiro n × n determinar se um cavalo – do jogo de xadrez – consegue percorrer todas as posi¸c˜oes do tabuleiro, uma s´o vez,em n2 − 1 movimentos.
Estado Inicial: uma posi¸c˜ao do tabuleiro Estado Final: Ter visitado todas as posi¸c˜oes
Mudan¸ca de Estado: salto em “L” 2 por 1, de uma posi¸c˜ao para outra (candidata) Movimento v´alido: se existe uma posi¸c˜ao para saltar e essa posi¸c˜ao ainda n˜ao foi visitada
3 2
4 1
C
5 8
Algoritmo de Procura com Retrocesso
(Backtracking)
tenta
inicializa sele¸c˜ao de candidaos repete
seleciona um candidato
se aceit´avel marcar posi¸c˜ao se n˜ao estado final
tenta o passo seguinte
se n˜ao sucedeu retira marca da posi¸c˜ao
at´e estado final ou n˜ao haver mais candidatos Importante: N´umero de candidatos ´e finito!!!
Salto de Cavalo – I
int h[10][10], tab_max; main(){ int i,j;
do {
printf(" Tamanho do tabuleiro[3-10]: "); scanf("%d",&tab_max);
for(i=1;i<=tab_max; i++) for(j=1;j<=tab_max; j++) h[i][j]=0; printf("Posicao inicial:\n"); printf("Linha: "); scanf("%d",&i); printf("Coluna: "); scanf("%d",&j); h[i][j]=1; if (try(2,i,j)==1) for(i=1;i<=tab_max;i++) {
for(j=1;j<=tab_max;j++) printf("%d ",h[i][j]); printf("\n \n");}
else printf("Nao ha solucao \n"); printf("Ctrl C para terminar \n"); } while(getchar() !=EOF);
#define tab_sq tab_max*tab_max
#define in_tab(x) (x>0 && x<=tab_max) int casasx[]={2,1,-1,-2,-2,-1,1,2}; int casasy[]={1,2, ,2, 1,-1,-2,-2,-1}; int try(int i,int x,int y){
int k = 0, u, v; while( k < 8 ){
u=x+casasx[k]; v=y+casasy[k]; if(in_tab(u) && in_tab(v))
if (h[u][v] == 0) { h[u][v]=i; if (i >= tab_sq) return 1; if (try(i+1,u,v)==1) return 1; h[u][v]=0; } k++; } return 0;}
Tamanho do tabuleiro[3-10]: 5 Posicao inicial: Linha: 1 Coluna: 1 1 6 15 10 21 14 9 20 5 16 19 2 7 22 11 8 13 24 17 4 25 18 3 12 23 Ctrl C para terminar Tamanho do tabuleiro[3-10]: 5 Posicao inicial: Linha: 3 Coluna: 3 23 10 15 4 25 16 5 24 9 14 11 22 1 18 3 6 17 20 13 8 21 12 7 2 19 Ctrl C para terminar ^C
tabuleiro de xadrez (8x8) por forma a que n˜ao se ataquem mutuamente, isto ´e,
que n˜ao haja duas rainhas numa mesma linha, coluna ou diagonal?
Sugest˜ao: Considere uma vari´avel indexada v[8] tal que v[i] corresponde `a linha em que estar´a a rainha da coluna i. A condi¸c˜ao para que duas rainhas estejam na mesma linha ´e:
#define linha(i,j) v[i] == v[j] e na mesma diagonal ´e
#define abs(x) (x >= 0? x: -x)
#define diagonal(i,j) abs(v[i]-v[j]) == abs(i-j) Podemos tentar gerar uma solu¸c˜ao:
• colocando aleat´oriamente valores de 0 a 8 (todos distintos) em v[] e depois ver se as condi¸c˜oes anteriores se verificam para todos. Para cada hip´otese h´a 56 testes e h´a 88 hip´oteses... o m´etodo ´e muito ineficiente
• Tentar gerar uma solucao da esquerda para a direita, come¸cando por fazer: i=0 -> v(i)=0
i=1 -> procurar o primeiro valor que
satisfaca as condicoes,i.e, v[1]=2
Em geral para cada i, se existe j > v[i] que satisfaz as condi¸c˜oes relativamente `as rainhas j´a colocadas, fazemos v[i]=j e passa-se para o i seguinte. Sen˜ao existe tal j, volta-se ao i anterior e tenta-se aplicar o processo.
Leituras
Referˆ
encias
[Bro97] J. Glenn Brookshear. Computer Science, an overview. Addison-Wesley, 1997.
[ePD94] H.M. Deitel e P.J. Deitel. C:How to Program. Prentice Hall International Editions, 2 edition, 1994.