Resumo
Uma função pode chamar a si mesma: a recursão resolve um problema reduzindo-o a uma instância menor do mesmo problema. Esta aula traduz para C++ definições recursivas da matemática (o fatorial, a potência, a soma dos algarismos), cada uma com seu caso base e seu caso recursivo; mostra que a indução garante sua correção e que uma medida que decresce garante sua terminação; acompanha com a pilha de chamadas os quadros que se empilham e se desfazem, e explica quando a pilha se esgota. Examina o limite do tipo do resultado, que estoura muito antes da pilha; compara a versão recursiva e a iterativa de Fibonacci contando chamadas, e fecha com um critério para escolher entre a recursão e um laço.
Objetivos de aprendizagem
- Escrever uma função recursiva, com seu caso base e seu caso recursivo, a partir de uma definição recursiva.
- Justificar a correção e a terminação de uma função recursiva por indução sobre o tamanho do problema.
- Acompanhar com a pilha de chamadas a execução de uma função recursiva e explicar o estouro da pilha.
- Comparar o custo da versão recursiva e da iterativa de Fibonacci contando suas chamadas e suas operações.
Definições recursivas
O fatorial
O fatorial de um natural é o produto dos naturais de 1 a : , com a convenção . As reticências dessa fórmula escondem um laço, e a aula Laços ensinou a escrevê-lo. Ora, a mesma definição admite outra forma, que não menciona nenhum laço: o fatorial de é vezes o fatorial de , e o de 0 é 1.
Esta é uma definição recursiva: define o fatorial de por meio do fatorial de um número menor. Não é circular, porque cada aplicação da segunda regra desce um degrau e a escada termina na primeira: . As duas regras têm nome. A primeira é o caso base, que dá o resultado sem se referir a nada mais; a segunda é o caso recursivo, que reduz o problema a uma instância menor do mesmo problema.
C++ permite que uma função chame a si mesma, e com isso a definição se traduz quase palavra por palavra.
#include <print>// Fatorial de n.// Pré-condição: 0 <= n <= 12 (a cota se justifica mais adiante).// Pós-condição: retorna n! = 1 · 2 · ... · n, com 0! = 1.int factorial(int n){ if (n == 0) { return 1; // caso base } return n * factorial(n - 1); // caso recursivo}int main(){ for (int n{0}; n <= 6; ++n) { std::println("{}! = {}", n, factorial(n)); }}0! = 11! = 12! = 23! = 64! = 245! = 1206! = 720A função tem exatamente a forma da definição: um if que atende o caso base e um return que aplica o caso recursivo. A chamada factorial(n - 1) não é diferente de qualquer outra chamada da aula Funções: avalia-se seu argumento, cria-se um quadro novo com seu próprio n e, ao terminar, o valor retornado é multiplicado pelo n de quem chamou.
O contrato não é decorativo: com o caso recursivo chamaria com , depois com , e nunca alcançaria o 0; e não cabe em um int. Muitos textos introdutórios escrevem este fatorial com o caso base if (n == 0 || n == 1), redundante mas inofensivo, e sem dizer nada dos negativos nem do tamanho do resultado: seu código não termina com e dá um resultado sem sentido a partir de . Ambos os defeitos serão examinados em sua seção.
Caso base e caso recursivo
Toda função recursiva tem um ou vários casos base, que se resolvem diretamente, e um ou vários casos recursivos, que chamam a mesma função com um argumento menor em algum sentido preciso e combinam o que ela retorna.
A potência , com natural, define-se por e : o tamanho é o expoente. A soma dos algarismos de um natural (a de 2026 é ) define-se assim: se , a soma é , porque tem um só algarismo; se não, é a soma dos algarismos de , que é sem seu último algarismo, mais esse último algarismo, . Aqui o tamanho é o número de algarismos.
#include <print>// x elevado a n.// Pré-condição: n >= 0.// Pós-condição: retorna x^n, com x^0 = 1.double power(double x, int n){ if (n == 0) { return 1.0; } return x * power(x, n - 1);}// Soma dos algarismos decimais de n.// Pré-condição: n >= 0.// Pós-condição: retorna a soma dos algarismos de n.int digitSum(int n){ if (n < 10) { return n; } return digitSum(n / 10) + n % 10;}int main(){ std::println("2^10 = {}", power(2.0, 10)); std::println("1.5^3 = {}", power(1.5, 3)); std::println("soma dos algarismos de 2026: {}", digitSum(2026));}2^10 = 10241.5^3 = 3.375soma dos algarismos de 2026: 10Em digitSum, a divisão inteira n / 10 tira o último algarismo e n % 10 o extrai, como na mudança de base da aula Algoritmos numéricos. Para , a função pede a soma dos algarismos de 202, que pede a de 20, que pede a de 2; esta é um caso base e retorna 2, e os resultados se compõem na volta: , , .
Escrever uma função recursiva consiste, portanto, em responder a duas perguntas: que casos se resolvem sem ajuda, e como se obtém a solução de um caso a partir da de um menor. Se ambas as respostas estão corretas, a função também está, e a seção seguinte o demonstra.
Carregando o conteúdo…