[ Fatorial | Depurando fatorial | Busca Binária | Depurando a Busca Binária ]

Introdução aos algoritmos recorrentes/recursivos: alguns exemplos

Nesta seção examinaremos alguns exemplos de algoritmos recorrentes (ou recursivos). A apresentação do conceito pode ser encontrada nesta página.

Fatorial implementado de modo recursivo

A função matemática fatorial descreve o númeto de permutações distintas de um conjunto finito. Sua definição é intrinsecamente recursiva:

Em termos práticos a função fatorial pode ser implementada como indicado abaixo, utilizando recorrência de cauda, ou seja, a chamada recursiva corresponde às últimas instruções da função.

Função fatorial implementada recursivamente
C | Python
--- | ---

    <incl1>#include</incl1> <stdio.h>
      <cyan>// Fatorial recursivo</cyan>
      <verm>int</verm> fatRec (<verm>int</verm> n) {
        if (n==0) return 1;     <cyan>// final de recorrencia</cyan>
        return n * fatRec(n-1); <cyan>// senao devolve n x "o fatorial de n-1" (inducao)</cyan>
        }

      <verm>int</verm> main (<verm>void</verm>) {
        <verm>int</verm> n;
        <verd>scanf</verd>("%d", &n);
        <verd>printf</verd>("O fatorial de %d e': %d\n", n, fatRec(n));
        return 1;
        }

    <cyan># Fatorial recursivo</cyan>
      <verm>def</verm> fatRec (n) : <cyan># os finalizadores ';' sao opcionais em Python</cyan>
        if (n==0) : return 1;   <cyan># final de recorrencia</cyan>
        return n * fatRec(n-1); <cyan># senao devolve n x "o fatorial de n-1" (inducao)</cyan>

      <verm>def</verm> main () :
        n = int(input());
        <verd>print</verd>("O fatorial de %d e': %d" % (n, fatRec(n)));

      main();

A execução de uma função recursiva é realizada empilhando-se o contexto de execução, de modo que no momento que a chamada recursiva termina, retorna-se ao contexto empilhado e, ao finalizado a chamada "inicial", o contexto é removido da pilha (desempilhado). Em detalhes, vejamos uma simulação da função fatRec acima apresentada para n=3.

    Ord.  n  Imprimir (esquema de execução)
       1  3  fatRec(3)
       2  2  fatRec(3) = 3*fatRec(2) -> fatRec(2)
       3  1  ^             ^           2*fatRec(1) -> fatRec(1)
       4  0  |             |             ^            1*fatRec(0) --> fatRec(0)
             |             |             |              ^
             |             |             |              |
       5  0  |             |             |              +-- 1 .. final desse contexto n=0
       6  1  |             |             +--------- 1 = 1*1 .... final do contexto 'fatRec(1)'
       7  2  |             +--------- 2 = 2*1 .................. final do contexto 'fatRec(2)'
       8  3  +------ 6 = 3*2 ................................... final do contexto 'fatRec(3)'

Na primeira coluna, sob rótulo Ord., está o tempo de execução, sendo os números a ordem de execução dos comandos. Na segunda coluna está o valor do n no contexto da chamada sendo simulada.

Note que na ordem de cada instrução, separamos o comando `k * fatRec(k-1)` em duas instruções, primeiro obter o valor de `fatRec(k-1)`, digamos `FK`, e depois a instrução `k * FK`.

Um exemplo mostrando o comportamento da função recursiva

Podemos alterar o código da função recursiva fatRec acima para percebermos mais claramente os contextos de execução e os níveis de recorrência. Por nível de recorrência deve-se entender o número de vezes que a recorrência é invocada, por exemplo, para n=0, teremos nível de recorrência 0, para n=1, nível 1 e assim por diante.

Abaixo os códigos em C e em Python, imprimindo a chegada ao contexto n, deslocando-se horizontalmente a impressão de acordo com o nível da recorrência ("indentação"). Por essa razão, precisaremos passar um parâmetro adicional, que sempre terá o valor inicial do n.

Função fatorial implementada recursivamente
C | Python
--- | ---

    <incl1>#include</incl1> <stdio.h>
      char brancos[] = "                                                            ";
      <verm>int</verm> fatRec (int N, <verm>int</verm> k) {
        <verm>int</verm> aux;
        <verd>printf</verd>("%*.s", 2*(N-k), brancos); <cyan>// truque: nivel rec. maior => deslocar mais</cyan>
        <verd>printf</verd>("Entrei em fat(%d)\n", k);
        if (k==0) aux = 1;             <cyan>// final de recorrencia</cyan>
        else aux = k * fatRec(N, k-1); <cyan>// senao devolve k x "o fatorial de k-1"</cyan>
        <verd>printf</verd>("%*.s", 2*(N-k), brancos);
        <verd>printf</verd>("Saindo de fat(%d): devolve %d\n", k, aux);
        return aux;
        }

      <verm>int</verm> main (<verm>void</verm>) {
        <verm>int</verm> n, fat;
        <verd>scanf</verd>("%d", &n);
        <verd>printf</verd>("Antes de iniciar chamada a fat(%d)\n", n);
        fat = fatRec(n, n);
        <verd>printf</verd>("Apos chamada a fat(%d)\n", n);
        return 1;
        }

    from __future__ import print_function; <cyan># para Python 2</cyan>
      <cyan># Fatorial recursivo</cyan>
      <verm>def</verm> fatRec (N, k) : <cyan># os finalizadores ';' sao opcionais em Python</cyan>
        <verd>print</verd>((N-k) * "  ", end=""); <cyan># truque: nivel rec. maior => deslocar mais</cyan>
        <verd>print</verd>("Entrei em fat(%d)" % k);
        if (k==0) : aux = 1;      <cyan># final de recorrencia</cyan>
        else : aux = k * fatRec(n, k-1); <cyan># senao devolve k x "o fatorial de k-1"</cyan>
        <verd>print</verd>((N-k) * "  ", end=""); <cyan># truque: nivel rec. maior => deslocar mais</cyan>
        <verd>print</verd>("Saindo de fat(%d): devolve %d" % (k, aux));
        return aux;

      <verm>def</verm> main () :
        n = int(input());
        <verd>print</verd>("Antes de iniciar chamada a fat(%d)" % n);
        fatRec(n, n);
        <verd>print</verd>("Apos chamada a fat(%d)" % n);

      main();

Assim, se rodarmos qualquer uma das versões para n=3, teremos a seguinte impressão:

    Antes de iniciar chamada a fat(3)
    Entrei em fat(3)
      Entrei em fat(2)
        Entrei em fat(1)
          Entrei em fat(0)
          Saindo de fat(0): devolve 1
        Saindo de fat(1): devolve 1
      Saindo de fat(2): devolve 2
    Saindo de fat(3): devolve 6
    Apos chamada a fat(3)

Outro exemplo de recursividade em C e em Python: busca binária

Se analisarmos a frequência de uso dos variados tipos de algoritmo, provavelmente o campeão de uso é a busca de elemento em vetor/lista/conjunto. Para que essa busca seja eficiente, geralmente mantemos os dados ordem (e.g., crescente), assim podemos empregar um algoritmo de busca muito rápido, a busca binária.

A busca binária é feita sobre listas ordenadas, adotando o seguinte esquema:
1. busca(vet, x, ini, fim) <cyan>// busca o elemento x em vet entre as posições ini e fim;
2. se (ini > fim), então devolva que não existe mais intervalo onde procurar
3. meio = (ini + fim) / 2; <cyan>// usando a divisão inteira
4. se (vet[meio] == x), então devolva que encontramos na posição meio
5. se (vet[meio] < x), então x não pode estar na primeira metade, busque entre meio+1 e fim
6. se (vet[meio] > x), então x não pode estar na segunda metade, busque entre ini e meio-1

Note que na descrição acima, o algoritmo é naturalmente recursivo, então faremos essa implementação em C e em Python.

Busca binária implementada recursivamente
C | Python
--- | ---

    <verm>int</verm> busca (<verm>int</verm> vet[], <verm>int</verm> x, <verm>int</verm> ini, <verm>int</verm> fim) { <cyan>// busca x entre vet[ini] e vet[fim]</cyan>
      <verm>int</verm> meio;
      if (ini>meio) return -1; <cyan>// nao tem mais onde procurar!</cyan>
      meio = (ini+fim) / 2; <cyan>// pegar indice do elemento do meio</cyan>
      if (vet[meio]==x)
        return meio; <cyan>// encontrei na posicao meio</cyan>
      else
      if (vet[meio] < x) <cyan>// vet[ini]...vet[meio] NAO contem x</cyan>
        <cyan>// entao busque (recursivamente) entre vet[meio+1] e vet[fim]</cyan>
        return busca(vet, x, meio+1, fim);
      else <cyan>// vet[meio]...vet[fim] NAO contem x</cyan>
        <cyan>// entao busque (recursivamente) entre vet[ini] e vet[meio-1]</cyan>
        return busca(vet, x, ini, meio-1);
      } <cyan>// nunca executaria esta linha - por que?

    <verm>def</verm> busca (vet, x, ini, fim) : <cyan># busca x entre vet[ini] e vet[fim]</cyan>
      if (ini>meio) : return -1; <cyan># nao tem mais onde procurar!</cyan>
      meio = (ini+fim) / 2; <cyan># pegar indice do elemento do meio</cyan>
      if (vet[meio]==x) :
        return meio; <cyan># encontrei na posicao meio</cyan>
      elif (vet[meio] < x) : <cyan># vet[ini]...vet[meio] NAO contem x</cyan>
        <cyan># entao busque (recursivamente) entre vet[meio+1] e vet[fim]</cyan>
        return busca(vet, x, meio+1, fim);
      else : <cyan># vet[meio]...vet[fim] NAO contem x</cyan>
        <cyan># entao busque (recursivamente) entre vet[ini] e vet[meio-1]</cyan>
        return busca(vet, x, ini, meio-1);
      <cyan># nunca executaria esta linha - por que?

Simulando e depurando a busca binária

Da mesma forma que inserimos várias linhas de impressão para ajudar a entender as recorrência na função fatorial, inclusive usando um truque para visualizar o nível da recorrência, faremos o mesmo com o algoritmo de busca binária.

Busca binária recursiva com mensagens para visualizar nível de recorrência
C | Python
--- | ---

    <cyan>// busca x entre vet[ini] e vet[fim]</cyan>
    <verm>int</verm> busca (<verm>int</verm> vet[], <verm>int</verm> x, <verm>int</verm> ini, <verm>int</verm> fim, <verm>int</verm> N, <verm>int</verm> k) {
      <verm>int</verm> meio = (ini+fim) / 2; <cyan>// pegar indice do elemento do meio</cyan>
      <verd>printf</verd>("%*.s", 2*(N-k), brancos); <cyan>// para fazer indentacao</cyan>
      <verd>printf</verd>("Entrei em busca(%d,%d): meio = %d\n", ini, fim, meio);
      if (ini > meio) {
        <verd>printf</verd>("%*.s", 2*(N-k), brancos);
        <verd>printf</verd>("Nao ha mais onde buscar: %d > %d)\n", ini,fim);
        return -1; <cyan>// nao tem mais onde procurar!</cyan>
        }
      if (vet[meio]==x) {
        <verd>printf</verd>("%*.s", 2*(N-k), brancos);
        <verd>printf</verd>("Achei %d na posicao %d!\n", x, meio);
        return meio; <cyan>// encontrei na posicao meio</cyan>
        }
      else
      if (vet[meio] < x) { <cyan>// vet[ini]...vet[meio] NAO contem x</cyan>
        <verd>printf</verd>("%*.s", 2*(N-k), brancos);
        <verd>printf</verd>("Busque na segunda metade: %d e %d\n", meio + 1, fim);
        <cyan>// entao busque (recursivamente) entre vet[meio+1] e vet[fim]</cyan>
        return busca(vet, x, meio+1, fim, N, k-1);
        }
      <verd>printf</verd>("%*.s", 2*(N-k), brancos);
      <verd>printf</verd>("Busque na primeira metade: %d e %d\n", ini, meio - 1);
      <cyan>// entao busque (recursivamente) entre vet[ini] e vet[meio-1]</cyan>
      return busca(vet, x, ini, meio-1, N, k-1);
      }

    <verm>int</verm> main (<verm>void</verm>) {
      <verm>int</verm> vet[] = { -1, 0, 3, 3, 5, 6, 7, 8, 9 };
      <verm>int</verm> n = 9, resp;
      <cyan>//int x = 7; // buscar 7</cyan>
      <verm>int</verm>  x = 2; <cyan>// buscar 2</cyan>
      <verd>printf</verd>("Antes de iniciar chamada a busca(%d)\n", x);
      resp = busca(vet, x, 0, n, n, n);
      <verd>printf</verd>("Apos chamada a busca(%d)\n", x);
      if (resp>-1) <verd>printf</verd>("Encontrei em %d: de fato %d = %d\n", resp, x, vet[resp]);
      else <verd>printf</verd>("NAO encontrei %d\n", x);
      return 0;
      }

    <cyan># busca x entre vet[ini] e vet[fim]</cyan>
    <verm>def</verm> busca (vet, x, ini, fim, N, k) :
      global conta;
      conta += 1;
      if (conta>10) : return -1;
      meio = (ini+fim) / 2; <cyan># pegar indice do elemento do meio</cyan>
      <verd>print</verd>((N-k) * "  ", end=""); <cyan># truque: nivel rec. maior => deslocar mais</cyan>
      <verd>print</verd>("Entrei em busca(%d,%d): meio = %d" % (ini,fim, meio));
      if (ini>meio) :
        <verd>print</verd>((N-k) * "  ", end=""); <cyan># truque: nivel rec. maior => deslocar mais</cyan>
        <verd>print</verd>("Nao ha mais onde buscar: %d > %d)" % (ini,fim));
        return -1; <cyan># nao tem mais onde procurar!</cyan>
      if (vet[meio]==x) :
        <verd>print</verd>((N-k) * "  ", end="");
        <verd>print</verd>("Achei %d na posicao %d!" % (x,meio));
        return meio;
      elif (vet[meio] < x) : <cyan># vet[ini]...vet[meio] NAO contem x</cyan>
        <verd>print</verd>((N-k) * "  ", end="");
        <verd>print</verd>("Busque na segunda metade: %d e %d" % (meio + 1, fim));
        <cyan># entao busque (recursivamente) entre vet[meio+1] e vet[fim]</cyan>
        return busca(vet, x, meio+1, fim, N, k-1);
      else : <cyan># vet[meio]...vet[fim] NAO contem x</cyan>
        <verd>print</verd>((N-k) * "  ", end="");
        <verd>print</verd>("Busque na primeira metade: %d e %d" % (ini, meio - 1));
        <cyan># entao busque (recursivamente) entre vet[ini] e vet[meio-1]</cyan>
        return busca(vet, x, ini, meio-1, N, k-1);

    <verm>def</verm> main () :
      vet = [ -1, 0, 3, 3, 5, 6, 7, 8, 9 ];
      n = len(vet);
      #x = 7; <cyan># buscar 7</cyan>
      x = 2; <cyan># buscar 2</cyan>
      <verd>print</verd>("Antes de iniciar chamada a busca(%d)" % x);
      resp = busca(vet, x, 0, n, n, n);
      <verd>print</verd>("Apos chamada a busca(%d)" % x);
      if (resp>-1) : <verd>print</verd>("Encontrei em %d: de fato %d = %d" % (resp, x, vet[resp]));
      else : <verd>print</verd>("NAO encontrei %d" % x);

    main();

Procure criar uma função recursiva, eventualmente com mais de uma chamada como é o caso da função de busca binária acima, com as mensagens e usando o truque para fazer indentação. Então procure simular manualmente sua função, depois rode sua implementação e veja se o resultado foi o esperado. Cuidado com recorrência infinita (que equivale a laça infinito), por exemplo, utilize uma variável global `conta=0` e dentro de sua função, use como primeira linha algo como `conta += 1; if (conta>20) return -1;` (no Python lembre-se de declarar como global com a linha: `global conta`).

Leônidas de Oliveira Brandão
http://line.ime.usp.br

Alterações:
2020/08/15: novo formato, pequenas revisões
2020/08/13: novo formato, pequenas revisões
2020/06/18: nova imagem "img/img_fatorial_def.png" e "img/img_fatorial_fat3.png";
2019/06/03: extensáo da seção "Exemplo de função ou definição recursiva";
2018/06/15: primeira versão.