Recursão e funções recursivas

Esta página é um resumo de

O máximo de um vetor

Um elemento máximo de um vetor numérico  v[0 .. n-1]  é um número x tal que

x = v[i]  para algum i no intervalo 0..n-1  e
xv[j]  para todo j no intervalo 0..n-1.

Portanto, v[0..n-1] tem um elemento máximo desde que não seja vazio, ou seja, desde que n ≥ 1.  É muito fácil calcular um elemento máximo:

// A função max devolve um elemento máximo
// de v[0..n-1]. Ela supõe que n >= 1. 
//
int max (int v[], int n) { 
   int j, x = v[0];
   for (j = 1; j < n; ++j)
      if (x < v[j])
         x = v[j];
   return x;
}

Agora examine esta solução recursiva (exercício 5.16 do livro de Sedgewick):

// A função maxr devolve um elemento máximo
// de v[0..n-1]. Ela supõe que n >= 1. 
//
int maxr (int v[], int n) { 
   if (n == 1) return v[0];
   else {
      int x;
      x = maxr(v, n-1);
      if (x > v[n-1]) return x;
      else return v[n-1]; 
   }
}

Eis uma receita básica para escrever um algoritmo recusivo:

  1. se o problema é pequeno, resolva-o diretamente;
  2. se o problema é grande, reduza-o a uma versão menor do mesmo problema e
  3. aplique esta receita ao problema menor.

Veja outra maneira recursiva de determinar um elemento máximo de v[0..n-1]. A função maxr2 é apenas uma embalagem: o trabalho pesado é feito pela função recursiva auxiliar:

int maxr2 (int v[], int n) {
   return auxiliar(v, 0, n-1);
}

// Recebe v e índices p e r tais que p <= r. 
// Devolve um elemento máximo do vetor v[p..r]. 
//
int auxiliar (int v[], int p, int r) {
   if (p == r) return v[p];
   else {
      int x;
      x = auxiliar(v, p + 1, r);
      if (v[p] < x) return x;
      else return v[p]; 
   } 
}

Veja mais uma versão, desta vez sem função-embalagem:

// A função maxr3 devolve um elemento máximo
// de v[0..n-1]. Ela supõe que n >= 1. 
//
int maxr3 (int *v, int n) {
   if (n == 1) return v[0];
   else {
      int x;
      x = maxr3(v + 1, n - 1);
      if (v[0] < x) return x;
      else return v[0]; 
   } 
}

O programa 5.6 de Sedgewick mostra mais uma maneira recursiva de encontrar um máximo de um vetor. Esta solução usa a estratégia da divisão-e-conquista:

// A função maxr4 devolve um elemento máximo
// do vetor v[p..r]. Ela supõe que p <= r. 
//
int maxr4 (int v[], int p, int r) {   
   int u, v; 
   int m = (p + r)/2; 
   if (p == r) return v[p];
   u = maxr4(v, p, m);
   v = maxr4(v, m+1, r);
   if (u > v) return u; 
   else return v;
}

Exercícios

  1. Execute maxr com n = 1. Execute maxr com n = 2. Execute maxr com n = 3.
  2. Critique a seguinte função, que promete devolver o valor de um elemento máximo de v[p..r], com p ≤ r.
    int maximoO (int v[], int p, int r) {
       int j, x = INT_MIN; 
       for (j = p; j <= r; ++j)
          if (x < v[j]) x = v[j];
       return x; }
    
  3. Discuta a seguinte função, que promete devolver o valor de um elemento máximo de v[p..r].
    int maximo3 (int v[], int p, int r) {
       int x;
       if (p == r)  return v[p];
       x = maximo3 (v, p+1, r);
       return (x > v[p]) ? x : v[p]; }
    
  4. Qual o valor de X(5)?
    int X (int n) {
       if (n == 1 || n == 2) return n;
       else return X(n-1) + n * X(n-2); }
    
  5. Escreva uma função recursiva que devolva a soma dos elementos de um vetor v[0..n-1] de números inteiros. Para que valores de n o problema faz sentido?
  6. Escreva uma função recursiva que imprima os elementos de um vetor v[0..n-1] ao contrário, ou seja, primeiro v[n-1], depois v[n-2] e assim por diante. Para que valores de n o problema faz sentido?
  7. Escreva uma função recursiva que receba um inteiro positivo N e devolva o número ⌊log2N, ou seja, o piso do logaritmo de N na base 2. (Veja a função lg em outra página.)
  8. Escreva uma função recursiva que receba um inteiro positivo N e devolva o número ⌈log2N, ou seja, o teto do logaritmo de N na base 2.
  9. [Roberts 6, p.192]  Escreva uma função recursiva que receba um inteiro positivo n e devolva a soma dos dígitos decimais de n.  Por exemplo, ao receber 1729 sua função deve devolver 19.
  10. [Roberts 11, p.189]  Dados t[0] e t[1], os números t[2], t[3], etc.  são definidos pela relação  t[k] = t[k-2] + t[k-1]. Critique a seguinte função, que promete calcular t[n] para qualquer n ≥ 2:
    int SeqAditiva (int n, int t0, int t1) {
       if (n == 0) return t0;
       return SeqAditiva(n-1, t1, t0 + t1); }
    
  11. [Sedg 5.15]  Escreva uma função recursiva que inverta a ordem dos nós de uma lista encadeada que termina com NULL.
  12. [Sedg 5.17]  Escreva uma função recursiva que determine um elemento máximo de uma lista encadeada que termina com NULL.

 


Processamento de lista encadeada

Suponha que uma lista encadeada termina com NULL e tem nós do tipo node:

typedef struct node *link;
struct node { 
   int  item; 
   link next; 
};

Eis algumas funções recursivas de manipulação da lista.  Duas delas aplicam uma função Visit a cada nó da lista; não importa o que essa função faz, desde que não altere o campo next do nó.  Essas funções são uma versão simplificada do Programa 5.5 do livro do Sedgewick.

// A função count devolve o número de nós
// da lista encadeada x. A lista termina
// com NULL. 
// 
int count(link x) { 
   if (x == NULL) return 0;
   return 1 + count(x->next); 
}
// A função traverse percorre a lista
// encadeada h, que termina com NULL. Ela
// aplica a função Visit a cada nó da lista:
// primeiro ao primeiro nó, depois ao
// segundo, etc. 
//
void traverse(link h) { 
   if (h == NULL) return;
   Visit(h); 
   traverse(h->next);
}
// A função traverse percorre a lista
// encadeada h, que termina com NULL. Ela
// aplica a função Visit a cada nó da lista
// EM ORDEM INVERSA: primeiro ao último nó,
// depois ao penúltimo etc. 
//
void traverseR(link h) { 
   if (h == NULL) return;
   traverseR(h->next);
   Visit(h); 
}
// A função delete remove da lista
// encadeada x, que termina com NULL, 
// o primeiro nó que tiver item == val. 
//
link delete(link x, int val) { 
   if (x == NULL) return NULL;
   if (x->item == val) { 
      link t = x->next; 
      free(x); 
      return t; 
   }
   x->next = delete(x->next, val);
   return x;
}

Exercícios

  1. A função delete acima remove da lista todos os nós que contêm val ou apenas o primeiro desses nós?
  2. Escreva uma função recursiva que receba uma lista encadeada terminada com NULL e construa um vetor que tenha o mesmo conteúdo da lista. Os nós da lista são definidos por
    typedef struct node *link;
    struct node {int item; link next;};
    
  3. Escreva uma função recursiva que receba um vetor de inteiros v[0..n-1] e construa uma lista encadeada com o mesmo conteúdo do vetor.

Palíndrome

Um palíndrome é uma palavra que coincide com sua inversa. Por exemplo, "OSSO" é um palíndrome.  A função recursiva abaixo verifica se uma cadeia de caracteres (= string) é palíndrome. Ela foi copiada da Figura 4-4 de Roberts.

typedef enum {FALSE, TRUE} bool;

// Function: IsPalindrome
// Usage: if (IsPalindrome(str) == 1) ...
// --------------------------------------
// This function returns TRUE if the
// character string str is a palindrome.
// (The function is just a wrapper.)
// 
bool IsPalindrome (char str[]) {
   return CheckPalindrome(srtr, strlen(str));
}
// Function: CheckPalindrome
// Usage: if (IsPalindrome(str, n) == 1) ...
// -----------------------------------------
// This function returns TRUE if the array
// of characters str[0..n-1] is a
// palindrome. 
//
bool CheckPalindrome (char str[], int n) {
   if (n <= 1) {
      return TRUE;
   } else {
      return (str[0] == str[n-1] && 
              CheckPalindrome(str + 1, n-2));
}

Desenho de uma régua inglesa

Imagine uma linha vertical que representa o intervalo 0..2n.  Marque os pontos 0, 1, 2, … , 2n ao longo da linha, de cima para baixo. Agora desenhe traços horizontais a partir dos pontos  1, 2, 3, … , 2n-1  da linha vertical.  O traço horizontal no ponto médio do intervalo deve ter comprimento n; os traços nos pontos médios dos subintervalos 0..2n-1 e 2n-1..2n têm comprimento n-1; e assim por diante.  Diremos que isso é uma régua de ordem n.

        01 -
        02 --
        03 -
        04 ---
        05 -
        06 --
        07 -
        08 ----
        09 -
        10 --
        11 -
        12 ---
        13 -
        14 --
        15 -

O seguinte programa recursivo desenha uma régua de ordem n.  Ele foi copiado do Programa 5.8 de Sedgewick. Também é o exercício 5.11 do livro do Roberts.

// Desenha uma régua de ordem n no intervalo
// 0..2^n. Esta função é apenas uma
// "embalagem". 
//
void Rule (int n) {
   int i, pot = 1;
   for (i = 0; i < n; i++)
      pot *= 2; 
   rule(0, pot, n);
}
// Desenha uma régua de ordem h no intervalo
// p..r. 
//
void rule (int p, int r, int h) { 
   int m = (p + r)/2;
   if (h > 0) { 
      rule(p, m, h-1);
      mark(m, h);
      rule(m, r, h-1);
   }
}

Estamos supondo que o procedimento auxiliar mark(m,h) faz um traço horizontal de comprimento h a partir do ponto m da linha vertical.

Exercícios

  1. Escreva uma versão iterativa da função rule.
  2. [Biblioteca gráfica do Roberts]  Compile e teste a funcão  Rule. Troque a função auxiliar mark por uma função apropriada da biblioteca gráfica do Roberts. Há duas versões da biblioteca: uma padrão, que produz um arquivo .ps e outra que exibe os desenhos na tela do monitor, mas só funciona no UNIX (e talvez no Linux). A interface e a implementação da primeira versão estão em standard/graphics.h e standard/graphics.c respectivamente. A interface e a implementação da segunda estão em unix-xwindows/graphics.h e unix-xwindows/graphics.c.
  3. [Sedg 5.25]  Escreva uma função recursiva que preencha uma matriz de n colunas e 2n linhas com todas as sequências de n bits de tal forma que o número representado por cada linha seja uma unidade maior que o número representado pela linha anterior. Exemplo com n = 3:
         000
         001
         010
         011
         100
         101
         110
         111
    

 


Mais exercícios

  1. [Excelente!]  Escreva uma função que faça a indentação de um programa C. Sua função deve ler um arquivo contendo o texto de um programa C e imprimir o programa devidamente indentado.
  2. Escreva e teste um programa que receba uma cadeia de caracteres e gere todas as permutações da cadeia. Por exemplo, ao receber abc, o seu programa deve gerar
       abc
       acb
       bac
       bca
       cab
       cba
    
    O programa deve imprimir aquelas permutações que são palavras válidas da língua portuguesa. Use o arquivo  br-latin1.txt, que contém todas as palavras da língua portuguesa falada no Brasil.  (O arquivo foi gerado por Yoshiharu Kohayakawa a partir do trabalho de Ricardo Ueda.)  Minha página sobre algoritmos de enumeração pode ser útil.  Veja também o programa 3.17 no livro de Sedgewick.
  3. [South Pacific Contest, 1993]  O método k-periódico de criptografia funciona assim: escolha uma permutação de 1,2, … , k que vai funcionar como chave;  quebre sua mensagem em segmentos de k caracteres (complete o último segmento com ? se necessário); aplique a permutação a cada segmento. Exemplo: se k = 4 e a chave é  2431  então  Mary  torna-se  yMra  e  Maryan  torna-se  yMra?a?n.   Escreva uma função que, ao receber cadeias plaintext, cyphertext1 e cyphertext2,  encontre um k e uma chave que transforme plaintext em cyphertext1. Em seguida, aplique a chave inversa a cyphertext2.

 


Veja minhas notas de aula sobre recursão.
URL: www.ime.usp.br/~pf/mac0122-2002/
Atualizado em 2017-11-01
© Paulo Feofiloff
IME-USP