Recursão versus Iteração Problema: Cálculo de n! = n (n 1)... 1 int facti(int n) { int fac=n; while(n>0){ fac=fac*n; n--; } return fac; } [epd94, Cap. 5.13-15] Definição 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)); } Departamento de Ciência de Computadores da FCUP PI Aula 10 1
Chamadas recursivas e valores retornados de cada chamada 5! 5*4! 4*3! 3*2! 5! 5*24=120 5*4! 4*6=24 4*3! 3*2=6 3*2! Suponhamos n = 5 2*1! 1*0! 1 2*1! 1*0! 1 2*1=2 1*1=1 1 Departamento de Ciência de Computadores da FCUP PI Aula 10 2
Paradigmas Paradigma iterativo: uma sequência de instruções é excutada duma forma repetitiva, controlada por uma dada condição (p.e. decremento de um contador)(ciclo iterativo). Paradigma recursivo: 1. existência de casos simples, em que a resposta é determinada directamente; 2. ser possível uma decomposição duma instância do problema, em instâncias mais simples da mesma forma. 3. repetição por chamadas sucessivas: numa função recursiva, são criadas várias activações dela própria que desaparecem há medida que a execução avança. Em cada momento apenas uma das activações está activa, estando as restantes à espera que essa termine para continuarem. Departamento de Ciência de Computadores da FCUP PI Aula 10 3
Equivalência entre os paradigmas Os dois paradigmas são equivalentes: dada uma função recursiva existe sempre uma iterativa e vice-versa. Departamento de Ciência de Computadores da FCUP PI Aula 10 4
Escrever a representação dum inteiro n na base b. Função Iterativa void 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ção 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); } Departamento de Ciência de Computadores da FCUP PI Aula 10 5
Estas funções não são exactamente equivalentes! Porquê? Como torná-las equivalentes? Departamento de Ciência de Computadores da FCUP PI Aula 10 6
Sequência de Fibonacci (outra vez...) 0 1 1 2 3 5 8 13 21 34 55 89 144... Começando com 0 e 1, cada termo seguinte é a soma dos dois anteriores Assim o n-ésimo termo é calculado por f n = f n 1 + f n 2 e f 0 = 0 e f 1 = 1. Então, escrever uma função recursiva é imediato int fib(int n) { if(n==0 n==1) return n; return fib(n-1)+fib(n-2); } Segue a execução para f(4);. Como é uma versão iterativa? Departamento de Ciência de Computadores da FCUP PI Aula 10 7
O Puzzle das Torres de Hanoi (inventado por Eduoard Lucas (1880)) São dados três suportes (a, b e c) e n discos de tamanhos diferentes.os discos estão 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ão pode nunca estar por cima de um menor o terceiro suporte pode ser usado como auxiliar Departamento de Ciência de Computadores da FCUP PI Aula 10 8
Algoritmo (Função recursiva) 1. Representar cada suporte por um caracter ( a, b e c ). Pretende-se uma função recursiva: void hanoi(int n, char inicio, char fim, char temp); 2. Escrever uma função moveum(char ini, char fim) que move a peça do cimo do suporte ini para o suporte fim. moveum(char ini, char fim){ printf("%c-> %c",ini,fim); } Departamento de Ciência de Computadores da FCUP PI Aula 10 9
3. A função 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); } } Departamento de Ciência de Computadores da FCUP PI Aula 10 10
Execução %hanoi 3 a-> b a-> c b-> c a-> b c-> a c-> b a-> b % O algoritmo é expon^encial O(2 n ): O número de movimentos é exponencial com n, T n = 2T n 1 + 1 isto é, T n = 2 n 1...(porquê?) Departamento de Ciência de Computadores da FCUP PI Aula 10 11
Método de ordenação Quicksort C.A.R.Hoare 1960 56 25 37 58 95 19 73 Método: Dada uma sequência de valores, escolhe-se um elemento (pivot) e os restantes valores são reagrupados em duas subsequências (partição): os que são menores que esse elemento os que são maiores. O mesmo processo é aplicado recursivamente a cada subsequência.quando um subconjunto tem menos que 2 elementos a recursão pára. Vamos escolher sempre o elemento do meio, como pivot. 19 25 37 56 58 95 73 19 25 37 56 58 73 95 Departamento de Ciência de Computadores da FCUP PI Aula 10 12
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; temp=v[i]; v[i]=v[j]; v[j]=temp;} Departamento de Ciência de Computadores da FCUP PI Aula 10 13
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: Ordenado: 19 25 37 56 58 73 95 Execução Departamento de Ciência de Computadores da FCUP PI Aula 10 14
Departamento de Ciência de Computadores da FCUP PI Aula 10 15
Quicksort para ordenar uma variável 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]; strcpy(temp,v[i]); strcpy(v[i],v[j]); strcpy(v[j],temp);} Exercício 10.1. Escreve uma função recursiva da pesq. binária em variável Departamento de Ciência de Computadores da FCUP PI Aula 10 16
indexada de strings. Exercício 10.2. Escreve uma função recursiva que implemente o método de ordenação por seleção. Exercício 10.3. Escreve uma função recursiva que dado n e k determine a potência k de n. Exercício 10.4. Escreve uma função recursiva que determine o máximo divisor comun entre dois inteiros. Exercício 10.5. Escreve uma função recursiva que gere todas as permutações de n elementos. Departamento de Ciência de Computadores da FCUP PI Aula 10 17
Retrocesso na Resolução de Problemas de Pesquisa Para muitos problemas, o método de resolução consiste em percorrer uma sequência de decisões, cada uma fazendo avançar no caminho para a solução. Se se fizerem as escolhas correctas chega-se a uma solução senão tem de ser retrocecer e escolher outros caminhos. Divisão em subtarefas Pesquisar exaustivamente um árvore de sub-tarefas Uso de Recursão Exemplos: procura de uma solução de um puzzle procura duma estratégia vencedora num jogo de dois jogadores Departamento de Ciência de Computadores da FCUP PI Aula 10 18
procura da saída de um labirinto procura de um caminho numa rede que liga vários pontos INCONVENIENTE: O tamanho da árvore 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ções do tabuleiro, uma só vez,em n 2 1 movimentos. Estado Inicial: uma posição do tabuleiro Estado Final: Ter visitado todas as posições Mudança de Estado: salto em L 2 por 1, de uma posição para outra (candidata) Movimento válido: se existe uma posição para saltar e essa posição ainda não foi visitada Se o cavalo estiver na posição C estão marcadas as possíveis soluções: Departamento de Ciência de Computadores da FCUP PI Aula 10 19
3 2 4 1 C 5 8 6 7 Departamento de Ciência de Computadores da FCUP PI Aula 10 20
Algoritmo de Procura com Retrocesso (Backtracking) tenta inicializa seleção de candidaos repete seleciona um candidato se aceitável marcar posição se não estado final tenta o passo seguinte se não sucedeu retira marca da posição até estado final ou não haver mais candidatos Importante: Número de candidatos é finito!!! Departamento de Ciência de Computadores da FCUP PI Aula 10 21
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); } Departamento de Ciência de Computadores da FCUP PI Aula 10 22
#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;} Departamento de Ciência de Computadores da FCUP PI Aula 10 23
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 Exercício 10.6. Problema das 8 rainhas É possível colocar 8 rainhas num Departamento de Ciência de Computadores da FCUP PI Aula 10 24
tabuleiro de xadrez (8x8) por forma a que não se ataquem mutuamente, isto é, que não haja duas rainhas numa mesma linha, coluna ou diagonal? Sugestão: Considere uma variável indexada v[8] tal que v[i] corresponde à linha em que estará a rainha da coluna i. A condição para que duas rainhas estejam na mesma linha é: #define linha(i,j) v[i] == v[j] e na mesma diagonal é #define abs(x) (x >= 0? x: -x) #define diagonal(i,j) abs(v[i]-v[j]) == abs(i-j) Podemos tentar gerar uma solução: colocando aleatóriamente valores de 0 a 8 (todos distintos) em v[] e depois ver se as condições anteriores se verificam para todos. Para cada hipótese há 56 testes e há 8 8 hipóteses... o método é muito ineficiente Tentar gerar uma solucao da esquerda para a direita, começando por fazer: i=0 -> v(i)=0 Departamento de Ciência de Computadores da FCUP PI Aula 10 25
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ções relativamente às rainhas já colocadas, fazemos v[i]=j e passa-se para o i seguinte. Senão existe tal j, volta-se ao i anterior e tenta-se aplicar o processo. Departamento de Ciência de Computadores da FCUP PI Aula 10 26
Leituras [epd94, Cap. 5.13-15] [Bro97, Cap. 4.5] Departamento de Ciência de Computadores da FCUP PI Aula 10 27
Referências [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. Departamento de Ciência de Computadores da FCUP PI Aula 10 28