Skip to content

1. Conteos ​

Proceso de contar elementos, sucesos o características específicas en un conjunto de datos. Se puede hacer de dos formas:

EnfoqueDescripción
IteraciónRecorrer el conjunto y sumar cada vez que se cumple una condición.
RecursiónLa función se llama a sí misma para procesar partes más pequeñas del conjunto y realizar el conteo.

Ejemplo básico #3: conteo con recursividad ​

java
public static int contarElementos(List<Integer> lista, int index) {
    if (index == lista.size()) {
        return 0; // Caso base: llegamos al final, no hay más elementos
    } else {
        return 1 + contarElementos(lista, index + 1); // Sumamos 1 y seguimos con el siguiente
    }
}

Ejemplo básico #4: conteo con iteración ​

java
public static int contarElementos(List<Integer> lista) {
    int count = 0;
    for (int i = 0; i < lista.size(); i++) {
        count++; // Aumentamos el contador por cada elemento
    }
    return count;
}

2. Procesos recursivos de lista ​

  • La recursividad se utiliza cuando un problema tiene una estructura compleja o no lineal (colecciones de objetos, conjuntos de datos) y no se resuelve fácilmente con bucles.
  • Las listas de objetos pueden contener otros objetos o colecciones. La recursividad permite procesarlas de forma jerárquica, operando sobre cada elemento o subestructura.
  • Un proceso recursivo es aquel en el que una función se llama a sí misma para procesar los elementos de una lista. Es útil cuando el problema se descompone en subproblemas similares. Aunque simplifica el código, no siempre es la mejor opción.

Ejemplo: suma recursiva de una lista ​

java
import java.util.List;

public class Estructuras {

    // Método recursivo para sumar los elementos de la lista
    public static int sumaLista(List<Integer> lista) {
        // Caso base: si la lista está vacía, el resultado es 0
        if (lista.isEmpty()) {
            return 0;
        }
        // Caso recursivo: primer elemento + suma del resto de la lista
        else {
            return lista.get(0) + sumaLista(lista.subList(1, lista.size()));
        }
    }

    public static void main(String[] args) {
        List<Integer> miLista = List.of(1, 2, 3, 4, 5);
        int resultado = sumaLista(miLista);
        System.out.println(resultado); // Output: 15
    }
}

3. Removiendo recursividad ​

Aunque la recursividad puede ser elegante, en ocasiones no es la mejor opción, sobre todo si hay preocupación por el rendimiento o si la pila de llamadas puede desbordarse con listas muy grandes. En esos casos se elimina la recursividad y se usa una solución iterativa.

Ejemplo: suma iterativa ​

java
import java.util.List;

public class SumaIterativa {
    public static int sumarLista(List<Integer> lista) {
        int suma = 0;
        for (int num : lista) {
            suma += num; // Sumar cada elemento
        }
        return suma;
    }

    public static void main(String[] args) {
        List<Integer> numeros = List.of(1, 2, 3, 4, 5);
        int resultado = sumarLista(numeros); // Llamada iterativa
        System.out.println("La suma de la lista es: " + resultado);
    }
}

Ventaja: evita la sobrecarga de la pila de llamadas (no hay llamadas recursivas) y es más eficiente en uso de memoria.

4. Decidiendo cuándo usar y cuándo no usar recursividad ​

Cuándo usarla ​

SituaciónExplicación
Estructuras jerárquicas o anidadasÁrboles, grafos, listas anidadas o cualquier estructura que se pueda dividir en subproblemas de forma recursiva.
Subproblemas similaresEl problema se descompone en subproblemas del mismo tipo.
Búsqueda o clasificaciónBúsqueda binaria, QuickSort y MergeSort son naturalmente recursivos.
Simplificación del códigoEn algunos casos lo hace más legible y expresivo.

Cuándo NO usarla ​

SituaciónExplicación
Alto rendimiento o grandes datosMayor costo de memoria por la pila de llamadas; con listas grandes puede agotar la memoria.
Falta de caso base claraSi no se puede definir un caso base que termine la recursión, puede generar llamadas infinitas.
Operaciones simples sobre coleccionesSi se resuelve fácilmente con bucles o métodos integrados, la iteración es más simple y generalmente más eficiente.
Complejidad y legibilidadSi el problema no se presta a la descomposición recursiva, la solución se vuelve más compleja y menos legible.

Aplicaciones de la recursividad ​

Factorial (recursivo e iterativo) ​

Comparar la eficiencia de ambos enfoques ayuda a entender las diferencias de rendimiento.

java
// Recursivo
public static int factorialRecursivo(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorialRecursivo(n - 1);
    }
}

// Iterativo
public static int factorialIterativo(int n) {
    int resultado = 1;
    for (int i = 1; i <= n; i++) {
        resultado *= i;
    }
    return resultado;
}

Fibonacci (recursivo e iterativo) ​

Sucesión en la que cada número es la suma de los dos anteriores.

java
// Recursivo
public static int fibonacciRecursivo(int n) {
    if (n <= 1) {
        return n;
    } else {
        return fibonacciRecursivo(n - 1) + fibonacciRecursivo(n - 2);
    }
}

// Iterativo
public static int fibonacciIterativo(int n) {
    if (n <= 1) {
        return n;
    }
    int a = 0, b = 1, c;
    for (int i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

Ideas clave para recordar ​

  1. Toda función recursiva necesita un caso base y un caso recursivo que se acerque al caso base.
  2. La recursividad brilla en estructuras jerárquicas y problemas que se dividen en subproblemas iguales.
  3. Toda solución recursiva se puede convertir a iterativa; se elige según rendimiento, memoria y legibilidad.