O objetivo dessa unidade é apresentar o conceito de Recursividade. Será feita uma abordagem focando na linguagem C.
A recursividade pode ser vista por muitos como um bicho de sete cabeças, porém muitas vezes pode ser a melhor maneira de resolver um determinado problema, além de ser uma forma mais legível e simples do que a forma interativa. Então vamos tentar entender como funciona a recursão:
Você já viu um jogo de bonecas russas? A princípio, você vê apenas uma boneca, geralmente de madeira pintada, parecida com esta:
Você pode tirar a metade de cima da primeira boneca, e o que vê dentro dela? Outra boneca, um pouco menor!
Você pode tirar essa boneca de dentro da outra e separar suas metades inferior e superior. E você verá outra boneca, ainda menor:
E de novo:
Você pode continuar fazendo isso. Eventualmente você vai encontrar uma boneca russa pequenininha. Ela é formada por uma peça única, e não se abre:
Começamos com uma boneca grande, e vimos bonecas cada vez menores, até encontrar uma tão pequena que não podia conter nenhuma outra.
O que as bonecas russas têm a ver com Recursividade? Já vamos descobrir.
Chamamos de recursividade ou recursão quando uma função chama a si mesma.
Toda função recursiva é composta por duas partes:
Caso base: é o caso mais simples. É usada uma condição em que se resolve o problema com facilidade.
Chamadas recursivas: procuram simplificar o problema de tal forma que convergem para o caso base.
Descobriu?
Assim como uma boneca russa tem dentro de si uma boneca menor, que tem dentro de si uma boneca ainda menor, até chegarmos a uma boneca pequena demais para ter outra dentro de si, a recursividade soluciona um problema dividindo-o em instâncias menores (chamadas recursivas) até chegar ao ponto em que não conseguimos mais dividir esse problema (caso base).
Em uma função recursiva, a cada chamada é criada na memória uma nova ocorrência da função com comandos e variáveis “isolados” das ocorrências anteriores. A função é executada até que todas as ocorrências tenham sido resolvidas.
Torna a escrita do código mais simples e elegante, tornando-o fácil de entender e de manter.
Caso não se tenha cuidado é fácil cair em um loop infinito recursivo o qual pode inclusive esgotar a memória
Em muitos casos uma solução iterativa gasta menos memória, e torna-se mais eficiente em termos de performance do que usar recursão.
Vamos fazer o cálculo do fatorial de um dado número. Primeiramente utilizaremos uma solução iterativa:
#include < stdio.h > int fatorial(int num){ int i, resultado = 1; for(i = 1; i <= num; i++){ resultado = resultado * i; } return resultado; } int main(){ int num, resultado; scanf("%d", &num); if(num < 0){ printf("Erro: Numero negativo!"); }else{ resultado = fatorial(num); printf("Resultado: %d", resultado); } return 0; }
Agora uma solução recursiva:
/*Cálculo de fatorial com função recursiva*/ #include < stdio.h > /*Função recursiva que calcula o fatorial de um numero inteiro n*/ int fatorial(int n){ int vfat; /*Caso base: fatorial de n == 0 retorna 1*/ if(n == 0){ return 1; }else{ /*Chamada recursiva*/ vfat = n * fatorial(n - 1); return vfat; } } int main(){ int numero, fat; printf("Digite o numero que deseja calcular o fatorial: "); scanf("%d",&numero); /*chamada da função fatorial*/ if(numero < 0){ printf("Erro: Numero negativo!"); }else{ fat = fatorial(numero); printf("Fatorial de %d = %d",numero,fat); } return 0; }
Perceba que na solução acima dividimos o nosso problema em instâncias menores do problema. Nos temos o caso base que é quando o número n for igual a 0, para esse caso retornamos o valor 1 e a função encerrada sem necessidade de chamadas recursivas. Caso o número seja maior que 0 temos as chamadas recursivas até cair no caso base que é resolvido e assim, as chamadas retornam valores de forma a solucionar o cálculo.
Vejamos o raciocionio:
Veja a figura a seguir que representa as chamadas recursivas para o cálculo de 4!
Vamos escrever um programa que tenha uma função recursiva para calcular o valor de uma base elevada a um expoente.
/*Cálculo o valor de uma base elevada a um expoente.*/ #include < stdio.h > /*Função recursiva*/ int pot(int base, int expoente){ /*caso base*/ if(expoente == 0) return 1; /*Chamada recursiva*/ return base * pot(base,expoente-1); } int main(){ int base, expoente; printf("Digite a base: "); scanf("%d", &base); printf("Digite o expoente: "); scanf("%d", &expoente); if(base < 1 || expoente < 0){ printf("Erro!"); }else{ /*Chamada da função recursiva juntamente com o resultado*/ printf("\nResultado: %d", pot(base, expoente)); } return 0; }
Podemos observar mais uma vez um problema que pode ser subdivido em subproblemas menores. Temos o caso base que é quando o expoente for igual a 0 e também as chamadas recursivas que fazem com que o expoente vá diminuindo a medida em que são feitas as chamas de função, até chegar ao caso base.
A lógica é bem simples, porém requer atenção:
Na figura abaixo temos a representação das chamadas de função para 4 elevado a 3.