Recursão em Java
1. Introdução
Neste artigo, vamos nos concentrar em um conceito central em qualquer linguagem de programação - recursão.
Explicaremos as características de arecursive functione mostraremos como usar a recursão para resolver vários problemas em Java.
2. Entenda a recursão
2.1. A definição
Em Java, o mecanismo de chamada de função oferece suporte athe possibility of having a method call itself. Esta funcionalidade é conhecida comorecursion.
Por exemplo, suponha que queremos somar os inteiros de 0 a algum valorn:
public int sum(int n) {
if (n >= 1) {
return sum(n - 1) + n;
}
return n;
}
Existem dois requisitos principais de uma função recursiva:
-
A Stop Condition - a função retorna um valor quando uma certa condição é satisfeita, sem uma chamada recursiva adicional
-
The Recursive Call - a função chama a si mesma com uminput que é um passo mais próximo da condição de parada
Cada chamada recursiva adicionará um novo quadro à memória da pilha da JVM. Então,if we don’t pay attention to how deep our recursive call can dive, an out of memory exception may occur.
Esse problema em potencial pode ser evitado aproveitando a otimização da recursão da cauda.
2.2. Recursão da cauda versus recursão da cabeça
Nos referimos a uma função recursiva comotail-recursionwhen the recursive call is the last thing that function executes. Caso contrário, é conhecida comohead-recursion.
Nossa implementação acima da funçãosum() é um exemplo de recursão de cabeça e pode ser alterada para recursão de cauda:
public int tailSum(int currentSum, int n) {
if (n <= 1) {
return currentSum + n;
}
return tailSum(currentSum + n, n - 1);
}
Com recursão de cauda,the recursive call is the last thing the method does, so there is nothing left to execute within the current function.
Assim, logicamente, não há necessidade de armazenar o quadro de pilha da função atual.
Embora o compiladorcan utilize esse ponto para otimizar a memória, deve-se notar que oJava compiler doesn’t optimize for tail-recursion for now.
2.3. Recursão Versus Iteração
A recursão pode ajudar a simplificar a implementação de alguns problemas complicados, tornando o código mais claro e legível.
Mas, como já vimos, a abordagem recursiva muitas vezes requer mais memória à medida que a memória da pilha necessária aumenta com cada chamada recursiva.
Como alternativa, se podemos resolver um problema com recursão, também podemos resolvê-lo por iteração.
Por exemplo, nosso métodosum pode ser implementado usando iteração:
public int iterativeSum(int n) {
int sum = 0;
if(n < 0) {
return -1;
}
for(int i=0; i<=n; i++) {
sum += i;
}
return sum;
}
Em comparação com a recursão, a abordagem iterativa pode potencialmente oferecer melhor desempenho. Dito isto, a iteração será mais complicada e difícil de entender em comparação com a recursão, por exemplo: atravessar uma árvore binária.
Fazer a escolha certa entre recursão da cabeça, recursão da cauda e uma abordagem iterativa depende do problema e situação específicos.
3. Exemplos
Agora, vamos tentar resolver alguns problemas de forma recursiva.
3.1. Encontrando a enésima potência de dez
Suponha que precisemos calcular an-ésima potência de 10. Aqui nossa entrada én. Pensando de forma recursiva, podemos calcular(n-1)-ésima potência de 10 primeiro e multiplicar o resultado por 10.
Então, para calcular a(n-1)-ésima potência de 10 será a(n-2)-ésima potência de 10 e multiplique esse resultado por 10, e assim por diante. Continuaremos assim até chegarmos a um ponto em que precisamos calcular a (n-n) -ésima potência de 10, que é 1.
Se quiséssemos implementar isso em Java, escreveríamos:
public int powerOf10(int n) {
if (n == 0) {
return 1;
}
return powerOf10(n-1) * 10;
}
3.2. Encontrando o enésimo elemento da sequência de Fibonacci
Começando com0 e1,the Fibonacci Sequence is a sequence of numbers where each number is defined as the sum of the two numbers proceeding it:0 1 1 2 3 5 8 13 21 34 55…
Então, dado um númeron, nosso problema é encontrar on-ésimo elemento deFibonacci Sequence. To implement a recursive solution, we need to figure out the Stop Condition and the*Recursive Call*.
Felizmente, é muito simples.
Vamos chamarf(n) den-ésimo valor da sequência. Então teremosf(n) = f(n-1) + f(n-2) (oRecursive Call).
Enquanto isso,f(0) = 0 ef(1) = 1 (Stop Condition).
Então, é realmente óbvio para nós definir um método recursivo para resolver o problema:
public int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
3.3. Conversão de decimal em binário
Agora, vamos considerar o problema de converter um número decimal em binário. O requisito é implementar um método que receba um valor inteiro positivone retorne uma representação bináriaString.
Uma abordagem para converter um número decimal em binário é dividir o valor por 2, registrar o restante e continuar a dividir o quociente por 2.
Continuamos dividindo assim até obter um quociente de0. Então, escrevendo todos os restantes na ordem de reserva, obtemos a sequência binária.
Portanto, nosso problema é escrever um método que retorne esses restantes em ordem de reserva:
public String toBinary(int n) {
if (n <= 1 ) {
return String.valueOf(n);
}
return toBinary(n / 2) + String.valueOf(n % 2);
}
3.4. Altura de uma árvore binária
A altura de uma árvore binária é definida como o número de arestas da raiz à folha mais profunda. Nosso problema agora é calcular esse valor para uma determinada árvore binária.
Uma abordagem simples seria encontrar a folha mais profunda, contando as bordas entre a raiz e a folha.
Mas tentando pensar em uma solução recursiva, podemos reformular a definição para a altura de uma árvore binária como a altura máxima do ramo esquerdo da raiz e do ramo direito da raiz, mais1.
Se a raiz não tem ramo esquerdo e ramo direito, sua altura ézero.
Aqui está a nossa implementação:
public int calculateTreeHeight(BinaryNode root){
if (root!= null) {
if (root.getLeft() != null || root.getRight() != null) {
return 1 +
max(calculateTreeHeight(root.left),
calculateTreeHeight(root.right));
}
}
return 0;
}
Portanto, vemos quesome problems can be solved with recursion in a really simple way.
4. Conclusão
Neste tutorial, introduzimos o conceito de recursão em Java e o demonstramos com alguns exemplos simples.
A implementação deste artigo pode ser encontradaover on Github.