Recursividad en Java

Introducción

En este artículo, profundizaremos en el concepto de recursividad en Java y cómo puede aplicarse en la resolución de problemas en la programación.

Aprenderemos las bases de la recursividad, cómo utilizarla correctamente y cómo evitar errores comunes al implementar soluciones recursivas.

¿Qué es la recursividad en Java?

La recursividad es un concepto importante en la programación que permite resolver problemas complejos dividiéndolos en subproblemas más pequeños y manejables.

En términos simples, la recursividad ocurre cuando una función se llama a sí misma directa o indirectamente como parte de su ejecución. Esta técnica puede ser muy útil en ciertos casos, ya que permite expresar soluciones de manera más clara y concisa que utilizando iteraciones.

Sin embargo, también es fundamental comprender sus limitaciones y cómo evitar errores comunes al implementar soluciones recursivas.

Ventajas de la recursividad

  • Las soluciones recursivas a menudo son más simples y fáciles de entender que sus contrapartes iterativas, especialmente en problemas que se dividen naturalmente en subproblemas.
  • La recursividad puede ser una técnica muy eficiente en términos de tiempo de ejecución y uso de recursos para ciertos problemas.
  • La recursividad es esencial para resolver problemas que requieren backtracking o exploración exhaustiva de posibles soluciones.

Desventajas de la recursividad

  • Las llamadas recursivas pueden llevar a un desbordamiento de la pila (Stack Overflow) si la profundidad de las llamadas es demasiado grande.
  • Las soluciones recursivas a menudo son menos eficientes en términos de espacio en comparación con las soluciones iterativas, debido al uso de la pila de llamadas.
  • La recursividad puede ser más difícil de depurar y optimizar que las soluciones iterativas.

Funciones recursivas en Java

A continuación, se presentan las partes fundamentales de una función recursiva y un ejemplo simple.

Estructura básica de una función recursiva

Una función recursiva consta de dos partes principales: el caso base y el caso recursivo. El caso base es la condición que permite finalizar la recursión, mientras que el caso recursivo es la parte donde se realiza la llamada a la función recursiva.

Caso base y caso recursivo

  • Caso base: Es la condición que se cumple cuando el problema se reduce a su mínima expresión y puede resolverse fácilmente. Es crucial definir correctamente el caso base para evitar la recursividad infinita.
  • Caso recursivo: Es la parte de la función donde se realiza la llamada a sí misma, generalmente con un argumento modificado que representa un subproblema más pequeño.

Ejemplo práctico: Creación de una función recursiva simple

A continuación, se presenta un ejemplo de una función recursiva en Java que calcula el factorial de un número entero no negativo:


public class RecursividadJava {
    public static void main(String[] args) {
        int numero = 5;
        System.out.println("El factorial de " + numero + " es: " + factorial(numero));
    }

    public static int factorial(int n) {
        // Caso base
        if (n == 0) {
            return 1;
        }
        // Caso recursivo
        else {
            return n * factorial(n - 1);
        }
    }
}

Lenguaje del código: Java (java)

En este ejemplo, la función factorial() es una función recursiva. El caso base se define como n == 0, ya que el factorial de 0 es 1. El caso recursivo se presenta cuando se llama a la función factorial() con un argumento reducido en 1, es decir, factorial(n - 1).

La función continúa llamándose a sí misma hasta que se alcanza el caso base, momento en el cual se detiene la recursión y se retorna el resultado.

Recursividad directa e indirecta en Java

La recursividad puede presentarse en dos formas principales: directa e indirecta. A continuación, se describen ambas y se proporcionan ejemplos prácticos para ilustrar su uso.

Recursividad directa

La recursividad directa ocurre cuando una función se llama a sí misma directamente para resolver un problema. El ejemplo del factorial presentado anteriormente es un caso de recursividad directa.

Ejemplo práctico: Calcular la suma de los primeros n números naturales.


public class RecursividadDirecta {
    public static void main(String[] args) {
        int n = 5;
        System.out.println("La suma de los primeros " + n + " números naturales es: " + suma(n));
    }

    public static int suma(int n) {
        // Caso base
        if (n == 0) {
            return 0;
        }
        // Caso recursivo
        else {
            return n + suma(n - 1);
        }
    }
}

Lenguaje del código: Java (java)

En este ejemplo, la función suma() se llama a sí misma directamente en el caso recursivo, lo que representa un caso de recursividad directa.

Recursividad indirecta

La recursividad indirecta ocurre cuando una función llama a otra función distinta, que a su vez llama a la función original, creando un ciclo de llamadas entre las funciones.

Ejemplo práctico: Calcular el valor absoluto de un número utilizando recursividad indirecta.


public class RecursividadIndirecta {
    public static void main(String[] args) {
        int n = -5;
        System.out.println("El valor absoluto de " + n + " es: " + valorAbsoluto(n));
    }

    public static int valorAbsoluto(int n) {
        if (n < 0) {
            return negativo(n);
        } else {
            return n;
        }
    }

    public static int negativo(int n) {
        return valorAbsoluto(-n);
    }
}

Lenguaje del código: Java (java)

En este ejemplo, la función valorAbsoluto() llama a la función negativo() en caso de que el número ingresado sea negativo. A su vez, la función negativo() llama de nuevo a la función valorAbsoluto() con el valor negativo del número original.

Este proceso crea un ciclo de llamadas entre ambas funciones, lo que constituye un caso de recursividad indirecta.

Ejemplos de problemas resueltos con recursividad en Java

En esta sección, se presentan varios problemas resueltos utilizando recursividad en Java. Estos ejemplos ilustran cómo la recursividad puede simplificar la resolución de problemas complejos.

Ejemplo 1: Factorial de un número

Este ejemplo ya se presentó anteriormente. La función recursiva calcula el factorial de un número entero no negativo.


public static int factorial(int n) {
    // Caso base
    if (n == 0) {
        return 1;
    }
    // Caso recursivo
    else {
        return n * factorial(n - 1);
    }
}

Lenguaje del código: Java (java)

Ejemplo 2: Serie de Fibonacci

La función recursiva calcula el n-ésimo término de la serie de Fibonacci.


public static int fibonacci(int n) {
    // Casos base
    if (n == 0) {
        return 0;
    } else if (n == 1) {
        return 1;
    }
    // Caso recursivo
    else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

Lenguaje del código: Java (java)

Ejemplo 3: Torre de Hanoi

El problema de la Torre de Hanoi consiste en mover n discos de tamaño diferente desde una barra de origen a una barra de destino, utilizando una barra auxiliar, siguiendo las siguientes reglas:

  • Solo se puede mover un disco a la vez.
  • Un disco nunca puede colocarse encima de otro disco más pequeño.

public static void hanoi(int n, char origen, char destino, char auxiliar) {
    // Caso base
    if (n == 1) {
        System.out.println("Mover disco " + n + " de " + origen + " a " + destino);
    }
    // Caso recursivo
    else {
        hanoi(n - 1, origen, auxiliar, destino);
        System.out.println("Mover disco " + n + " de " + origen + " a " + destino);
        hanoi(n - 1, auxiliar, destino, origen);
    }
}

Lenguaje del código: Java (java)

Ejemplo 4: Búsqueda binaria

La búsqueda binaria es un algoritmo eficiente para buscar un elemento específico en un arreglo ordenado.


public static int busquedaBinaria(int[] arr, int valor, int inicio, int fin) {
    // Caso base: el valor no se encuentra en el arreglo
    if (inicio > fin) {
        return -1;
    }

    int medio = (inicio + fin) / 2;

    // Caso base: el valor se encuentra en el arreglo
    if (arr[medio] == valor) {
        return medio;
    }
    // Caso recursivo
    else if (arr[medio] > valor) {
        return busquedaBinaria(arr, valor, inicio, medio - 1);
    } else {
        return busquedaBinaria(arr, valor, medio + 1, fin);
    }
}

Lenguaje del código: Java (java)

Estos ejemplos demuestran cómo la recursividad puede utilizarse para resolver diversos problemas en Java de manera eficiente y elegante. La recursividad, cuando se aplica correctamente, permite simplificar el código y facilitar la comprensión de la solución.