Recursividade
 
 
Recursão é a possibilidade de uma função fazer uma chamada a ela mesma. Em um procedimento ou função recursiva, a função chama explicitamente ela mesma passando os parâmetros necessários para a sua execução. Existem diversas situações em que um procedimento recursivo economiza muito trabalho.

Uma chamada de função em C (e normalmente em qualquer linguagem) usa um recurso do S.O. (sistema operacional) chamado de pilha (em inglês, stack). A pilha é usada para armazenar os endereços de retorno das funções bem como os seus parâmetros. A pilha de endereços funciona como uma pilha de papeis. Cada vez que uma nova função é executada, o endereço de retorno (aquele no qual o programa deve retomar a execução após a chamada) é guardado na pilha. A função é então executada. Ao seu término, o endereço de retorno é retirado da pilha e a execução continua a partir deste endereço. Veja o exemplo abaixo de como funciona a pilha de endereços (e parâmetros).

Exemplo 1: A função A chama a função B que chama a função C:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
#include <stdio.h>

int funcao_C(int c)
{
  return(c/2);
}

int funcao_B(int i)
{
  int k;
  k=funcao_C(i-3);
  return(k);
}

int funcao_A(int n)
{
  int x;
  x=funcao_B(n-1);
  return(x);
}

void main()
{
  printf("resultado: %d\n",
    funcao_A(10));
}
 
pilha executando 
a linha 18: 
int x
parametro 10
linha 26
início da pilha 
pilha executando 
a linha 11: 
int k
parametro 9
linha 19
int x
parametro 10
linha 26
início da pilha 
pilha executando 
a linha 5: 
parametro 6
linha 12
int k
parametro 9
linha 19
int x
parametro 10
linha 26
início da pilha 
 
 
Cada vez que uma função é chamada, ela coloca na pilha nesta ordem:

Como a chamada da função coloca na pilha os três elementos citados acima, na chamada da primeira função (o desenho da primeira pilha) mostra o que foi colocado na pilha resultante da chamada da função_A: o endereço de retorno (linha 26), os parâmetros (parametro 10), e as variáveis locais (int x). A pilha fica na ordem invertida porque cada novo elemento é adicionado sobre os já existentes. A operação de desempilhamento ocorre na ordem invertida: primeiro é retirado da pilha o que está por cima dela, ou seja, o que foi colocado por último.

O funcionamento da pilha é muito importante para entender como funcionam as chamadas recursivas. Se você tiver alguma dúvida, não hesite em consultar o professor!
 


O exemplo típico: o cálculo fatorial

Para ensinar chamadas recursivas, o exemplo mais utilizado é o cálculo do fatorial. O fatorial é a multiplicação de todos os números de uma sequência de 1 até o limite.

Para implementarmos o cálculo fatorial recursivo é necessário entender o problema do ponto de vista recursivo. Uma chamada recursiva é usada em problemas que podem ser reduzidos a um outro problema menor a cada chamada da função. Neste exemplo, o fatorial pode ser simplificado se a cada chamada eliminarmos um número da sequência. Isso é facilmente representado matematicamente:

n!(K) = n!(K-1) . K
n!(0) = 1
Nesta fórmula vemos a definição do fatorial em função de um fatorial cujo índice é um menos o índice atual. É muito importante notar que existe uma condição de parada: quando o índice é 0 (e o resultado é 1). Sem a condição de parada a chamada recursiva entra em loop infinito e o computador pode travar.

Veja abaixo a implementação do fatorial recursivo:
 
#include <stdio.h>

int fatorial(int K)
{
  if (K==0) return(1);
  return(fatorial(K-1)*K);
}

void main()
{
  int K=7;

  printf("O fatorial de %d e' %d\n", K, fatorial(K));
}

A função recursiva nada mais faz do que testar a sua condição de parada e chamar a função recursiva reduzindo o seu índice (parâmetro K). A redução do problema na chamada também é impressindível para que a função não trave (entre em loop infinito).


Um exemplo gráfico

As funções recursivas quando aplicadas a funções gráficas tem um resultado muito interessante: fractais. Fractais são desenhos gerados por computador de forma recursiva.

Imagine o seguinte problema:

Um pai fala para o filho:
- Filho, está vendo aquela parede ali? Ande para frente de modo que a cada movimento, você chegue na metade do caminho entre você e a parede. Quando você encostar na parede, eu te dou tudo o que quiser!

Quando o filho chegará a parede? Nunca! Porque teoricamente se ele sempre dividir o espaço que falta em dois, ele nunca o percorrirá por completo, mesmo que a distância seja mínima. Se o seu pai já te falou isso então agora você sabe porque ele não se preocupou! :-)

Imagine uma função recursiva que faça desenhos utilizando sempre como coordenadas as últimas coordenadas desenhadas divididas ao meio. Esta função irá desenhar na tela algo que a princípio fica difícil de imaginar, e que o resultado é sempre interessante para analisarmos como funcionam as funções recursivas.

Abaixo está um programa que usa uma função recursiva que recebe quatro coordenadas. Em seguida faz um círculo no meio e chama ela mesma recursivamente para desenhar mais círculos acima, abaixo, a direita e a esquerda, reduzindo o espaço de desenho e também o diâmetro do círculo. Neste exemplo, o programa lê o número máximo de iterações do usuário.
 
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>
#include <conio.h>

int N;

// desenha um circulo no meio e chama a funcao mais 4 vezes
// recursivamente reduzindo o espaco de desenho e o tamanho
// do circulo. A variavel N e' o controle de parada
void desenha(int x1,int y1,int x2,int y2,int diam,int N)
{
  int mx;   // posicao X do circulo
  int my;   // posicao Y do circulo

  if (N>0) {    // condicao de parada!
    // calcula a posicao do circulo no meio da tela
    mx=(x1+x2)/2;
    my=(y1+y2)/2;
    // primeiro desenha os circulos menores
    desenha(x1,y1,mx,my,diam/2,N-1);
    desenha(mx,y1,x2,my,diam/2,N-1);
    desenha(x1,my,mx,y2,diam/2,N-1);
    desenha(mx,my,x2,y2,diam/2,N-1);
    // agora desenha o circulo maior
    setcolor(N);
    circle(mx,my,diam);
  }
}

void main()
{
  int gdriver=DETECT; // numero da placa de video usada
  int gmode;          // numero do modo de video usado
  int errorcode;      // codigo de erro retornado por "initgraph"

  // le^ o numero de iteracoes e verifica se e' "aceitavel"
  printf("Entre com o numero de iteracoes (1-10): ");
  scanf("%d%*c",&N);
  if (N>=1 && N<=10) {

    // Tenta inicializar o modo grafico detectando a placa de video
    initgraph(&gdriver, &gmode, "");
    errorcode = graphresult();
    if (errorcode != grOk)
    {
      printf("Erro no modo graifco: %s\n", grapherrormsg(errorcode));
      exit(1);
    }

    // chama a funcao de desenho usando a tela toda inicialmente
    desenha(0,0,getmaxx(),getmaxy(),32,N);

    getch();
    closegraph(); // Volta para a tela de texto
  }
}