Projeto de Algoritmos

"Para fazer um procedimento recursivo é preciso ter fé."
—prof. Siang Wun Song

"Ao tentar resolver o problema, encontrei obstáculos dentro de obstáculos.
Por isso, adotei uma solução recursiva."
—aluno S.Y., 1998

"To understand recursion,
we must first understand recursion."
—anônimo

Recursão e algoritmos recursivos

(Veja o verbete Recursion na Wikipedia.)

Muitos problemas têm a seguinte propriedade: cada instância — ou seja, cada exemplo concreto — do problema contém uma instância menor do mesmo problema. Dizemos que esses problemas têm estrutura recursiva.  Para resolver um tal problema podemos aplicar o seguinte método:

A aplicação desse método produz um algoritmo recursivo.   Para mostrar como isso funciona, esta página analisa o seguinte problema:  

Determinar o valor de um elemento máximo de um vetor  v[0 .. n-1] . 

É claro que o problema só faz sentido se o vetor não é vazio, ou seja, se n ≥ 1.   Para preparar o terreno, examine uma tradicional solução iterativa do problema:

int maximo (int n, int v[])
{ 
   int j, x;
   x = v[0];
   for (j = 1; j < n; j += 1)
      if (x < v[j]) x = v[j];
   return x;
}

Exercícios

  1. Considere a função iterativa  maximo  acima.  Faz sentido trocar  "x = v[0]"  por  "x = 0", como fazem alguns programadores descuidados?  Faz sentido trocar  "x = v[0]"  por  "x = INT_MIN"?  Faz sentido trocar  "x < v[j]"  por  "x <= v[j]"? 

  2. A função abaixo promete encontrar o valor de um elemento máximo de v[0..n-1]. A função cumpre a promessa?
    int maxi (int n, int v[]) {       
       int j, m = v[0];
       for (j = 1; j < n; j++)
          if (v[j-1] < v[j]) m = v[j];
       return m;
    }
    

Solução recursiva do problema

Eis uma função recursiva que resolve o problema:

int maximo1 (int n, int v[])
{ 
   if (n == 1)
      return v[0];  /* problema pequeno */
   else {
      int x;
      x = maximo1 (n-1, v);  /* instância menor do problema */ 
      if (x > v[n-1])
         return x;
      else
         return v[n-1]; 
   }
}

A análise do algoritmo tem a mesma forma que uma prova por indução.  Se n é 1 então v[0] é o único elemento relevante do nosso vetor e portanto v[0] é o máximo.  Agora suponha que n vale mais que 1. Então nosso vetor tem duas partes: v[0..n-2] e v[n-1] e portanto o valor que procuramos é o maior dentre

v[n-1]      e      o máximo de v[0..n-2].

Eis um roteiro que pode ajudar a verificar se a função está correta.  1. Escreva o que a função deve fazer.  2. Verifique que a função de fato faz o que deveria quando n é pequeno, ou seja, quando n=1.  3. Agora imagine que n é grande, ou seja, n1; suponha que a função faria a coisa certa se no lugar de n tivessemos algo menor que n;  verifique que a função faz o que dela se espera.

Para que uma função recursiva seja compreensível é muito importante que o autor da função diga, explicitamente, o que a função faz. Portanto, eu deveria escrever o seguinte comentário antes do código:

/* Ao receber v e n >= 1, a função devolve o valor */
/* de um elemento máximo de v[0..n-1].             */

[A pergunta "Como o computador executa um algoritmo recursivo?" é relevante, mas será ignorada por enquanto. Veja a página sobre pilhas.]

Algumas pessoas acham que funções recusivas consomem muito tempo. Mas isso é apenas uma lenda propagada por programadores que não sabem usar recursão.

Exercícios

  1. Critique a seguinte função recursiva; ela promete encontrar o valor de um elemento máximo de v[0..n-1].
    int maximo1A (int n, int v[]) {       
       int x;
       if (n == 1) return v[0];    
       if (n == 2) {
          if (v[0] < v[1]) return v[1];
          else return v[0];
       }
       x = maximo1A (n-1, v);
       if (x < v[n-1]) return v[n-1];
       else return x;
    }
    
  2. Critique a seguinte função recursiva; ela promete encontrar o valor de um elemento máximo de v[0..n-1].
    int maximo1B (int n, int v[]) {
       if (n == 1) return v[0];
       if (maximo1B (n-1, v) < v[n-1]) return v[n-1];
       else return maximo1B (n-1, v);
    }
    
  3. Escreva uma função recursiva maxmin que calcule o valor de um elemento máximo e o valor de um elemento mínimo de um vetor v[0..n-1]. Quantas comparações envolvendo os elementos do vetor a sua função faz?

  4. Programa de teste.  Escreva um pequeno programa para testar a função recursiva maximo1.  O seu programa deve pedir ao usuário que digite uma seqüência de números e em seguida devolver o valor do maior dos números digitados.  [Solução]   Agora faça uma nova versão do programa para determinar um elemento máximo de um vetor aleatório. Acrescente ao seu programa uma função que confira a resposta dada por maximo1.  [Solução]

  5. Escreva uma função recursiva que calcule a soma dos elementos positivos do vetor de inteiros  v[0..n-1].  O problema faz sentido quando  n é igual a 0?  Quanto deve valer a soma nesse caso?  [Solução]

Outra solução recursiva

A função maximo1 discutida acima "puxa pra esquerda" o fim do vetor, ou seja, troca v[0..n-1] pelo vetor v[0..n-2].  É possível escrever uma versão que "empurre pra direita" o início do vetor?

/* Ao receber v e n >= 1, esta função devolve o valor  */
/* de um elemento máximo do vetor v[0..n-1].           */

int maximo2 (int n, int v[]) {
   return mx (0, n, v);
}

/* Recebe v, ini e fim tais que ini < fim.  Devolve o  */
/* valor de um elemento máximo do vetor v[ini..fim-1]. */

int mx (int ini, int fim, int v[]) {
   if (ini == fim-1) return v[ini];
   else {
      int x;
      x = mx (ini + 1, fim, v);
      if (x > v[ini]) return x;
      else return v[ini]; 
   } 
}

Observe que maximo2 é apenas uma "embalagem": o serviço pesado é executado pela funcão recursiva mx.  A função mx resolve um problema mais geral que o original. Isso ocorre freqüentemente na construção de algoritmos recursivos: é preciso generalizar o problema para que uma solução recursiva se torne possível.

A título de curiosidade,  eis outra maneira, talvez surpreendente, de implementar o "empurra pra direita". Ela usa aritmética de endereços:

int maximo2 (int n, int v[]) {
   int x;
   if (n == 1) return v[0];
   x = maximo2 (n - 1, v + 1);
   if (x > v[0]) return x;
   return v[0];
}

Exercícios

  1. Escreva uma função recursiva que calcule a soma dos elementos positivos do vetor  v[ini..fim-1].  O problema faz sentido quando  ini é igual a fim?  Quanto deve valer a soma nesse caso?

  2. 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.

  3. Escreva uma função recursiva que calcule o piso do logaritmo de N na base 2.

Mais exercícios

  1. Qual o valor de X (4)?  [Solução]
    int X (int n) {
       if (n == 1 || n == 2) return n;
       else return X (n-1) + n * X (n-2);
    }
    

  2. Qual é o valor de f (1,10)? Escreva uma função equivalente que seja mais simples.
    double f (double x, double y) {
       if (x >= y) return (x + y)/2;
       else return f (f (x+2, y-1), f (x+1, y-2));
    }
    
  3. Qual o resultado da execução do programa abaixo?
    int ff (int n) {
       if (n == 1) return 1;
       if (n % 2 == 0) return ff (n/2);
       return ff ((n-1)/2) + ff ((n+1)/2);
    }
    int main (void) {
       printf ("%d", ff (7)); 
       return EXIT_SUCCESS;
    }
    

  4. Execute fusc (7,0).
    int fusc (int n, int profund) {
      int i;
      for (i = 0; i < profund; i++) 
         printf ("  ");
      printf ("fusc (%d,%d)\n", n, profund);
      if (n <= 1) 
         return 1;
      if (n % 2 == 0) 
         return fusc (n/2, profund+1);
      return fusc ((n-1)/2, profund+1) + fusc ((n+1)/2, profund+1) ;
    }
    

  5. Critique a seguinte função recursiva:
    int XX (int n) {
       if (n == 0) return 0;
       else return XX (n/3+1) + n;
    }
    

  6. A função de Fibonacci é definida assim:   F (0) = 0F (1) = 1   e   F (n) = F(n-1) + F(n-2)   para n > 1. Descreva a função F em linguagem C. Faça uma versão iterativa e uma recursiva.

  7. A seguinte função calcula o maior divisor comum dos inteiros positivos m e n. Escreva uma função recursiva equivalente.
    int Euclides (int m, int n) {
       int r;
       do {
          r = m % n; 
          m = n; 
          n = r;
       } while (r != 0);
       return m; 
    }
    

  8. Escreva uma função recursiva eficiente que receba inteiros positivos k e n e calcule k n.  (Suponha que k n cabe em um int.) Quantas multiplicações sua função executa aproximadamente?

 


Veja bons exemplos de recursão no capítulo sobre algoritmos de enumeração

URL of this site: www.ime.usp.br/~pf/algoritmos/
1998 | Last modified: Tue Nov 28 11:52:32 BRST 2006
Paulo Feofiloff
IME-USP

Valid HTML 4.0!     Valid CSS!