Exploración de Árboles en Java

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel intermedio-avanzado ★ ★ ★ ★ ★ (5/5)

🌳 ¿Qué es la exploración de árboles?

Los árboles son una de las estructuras de datos no lineales más importantes en ciencias de la computación. Se utilizan para representar jerarquías, organizar datos de búsqueda y modelar decisiones. Sin embargo, una estructura de datos solo resulta útil si podemos acceder a sus elementos de forma sistemática. A este proceso de visitar todos los nodos de un árbol siguiendo un orden determinado se le denomina exploración o recorrido de árboles (tree traversal).

A diferencia de las estructuras lineales como arrays o listas enlazadas, donde existe un único camino natural para recorrer los elementos, en un árbol existen múltiples formas válidas de visitar todos los nodos. Cada estrategia de recorrido produce un orden diferente de visita y tiene aplicaciones específicas en algoritmia.

💡
Concepto clave: Explorar un árbol significa visitar todos y cada uno de sus nodos exactamente una vez, siguiendo un criterio predefinido. Las dos grandes familias de recorridos son: en profundidad (DFS) y en anchura (BFS).

Los recorridos de árboles se clasifican en dos grandes familias:

Recorridos en profundidad (Depth-First Search, DFS): exploran la mayor profundidad posible en cada rama antes de retroceder. Se implementan naturalmente con recursión. Dentro de esta familia encontramos tres variantes: preorden, inorden y postorden.

Recorridos en anchura (Breadth-First Search, BFS): exploran nivel por nivel, visitando todos los nodos de una misma profundidad antes de descender al siguiente nivel. Utilizan una cola como estructura auxiliar.

📜 Contexto histórico: Knuth y los recorridos de árboles

Los algoritmos de recorrido de árboles tienen raíces profundas en la historia de las ciencias de la computación. Donald E. Knuth, profesor emérito de Stanford y autor de la monumental obra The Art of Computer Programming, fue uno de los primeros en formalizar y sistematizar los algoritmos de recorrido de árboles binarios en el volumen 1 de su serie, publicado en 1968.

Knuth planteó un problema fundamental que ocupó a los informáticos durante décadas: diseñar un algoritmo que recorriese un árbol binario en inorden sin usar recursión ni una pila auxiliar. Este desafío inspiró soluciones elegantes como el algoritmo de Morris (1979), que utiliza los punteros nulos del propio árbol para crear enlaces temporales (threaded tree), logrando un recorrido inorden con complejidad espacial O(1).

Donald Knuth, padre de los algoritmos de recorrido de árboles formalizados
Donald Knuth (n. 1938), cuyo trabajo en «The Art of Computer Programming» formalizó los recorridos de árboles. Premio Turing 1974.

La notación que usamos hoy — preorden, inorden y postorden — se consolidó precisamente en los trabajos de Knuth y fue adoptada universalmente en la enseñanza de estructuras de datos. Comprender estos recorridos es fundamental para cualquier programador, ya que aparecen en compiladores, bases de datos, sistemas de archivos, inteligencia artificial y muchos otros dominios.

🧱 Estructura de un nodo de árbol binario en Java

Antes de implementar los recorridos, necesitamos definir la estructura básica de un nodo de árbol binario. Cada nodo almacena un dato y referencias a sus hijos izquierdo y derecho. En Java, modelamos esta estructura con una clase genérica:

Java
public class NodoArbol<T> {
    T dato;
    NodoArbol<T> izquierdo;
    NodoArbol<T> derecho;

    public NodoArbol(T dato) {
        this.dato = dato;
        this.izquierdo = null;
        this.derecho = null;
    }

    public NodoArbol(T dato, NodoArbol<T> izquierdo, NodoArbol<T> derecho) {
        this.dato = dato;
        this.izquierdo = izquierdo;
        this.derecho = derecho;
    }

    @Override
    public String toString() {
        return dato.toString();
    }
}

Con esta clase podemos construir cualquier árbol binario enlazando nodos. Por ejemplo, el siguiente código crea un árbol de prueba que usaremos a lo largo de todo el artículo:

Java
/*
 * Árbol de ejemplo:
 *
 *        a
 *       / \
 *      b   c
 *     / \
 *    d   e
 */
NodoArbol<String> d = new NodoArbol<>("d");
NodoArbol<String> e = new NodoArbol<>("e");
NodoArbol<String> b = new NodoArbol<>("b", d, e);
NodoArbol<String> c = new NodoArbol<>("c");
NodoArbol<String> a = new NodoArbol<>("a", b, c);
// 'a' es la raíz del árbol
Buena práctica: Usar genéricos (<T>) permite que el árbol almacene cualquier tipo de dato (String, Integer, objetos propios), manteniendo la seguridad de tipos en tiempo de compilación.

🔍 Recorridos en profundidad (DFS)

Los recorridos en profundidad (Depth-First Search) se caracterizan por explorar completamente cada rama del árbol antes de pasar a la siguiente. Se implementan de forma natural mediante recursión, aprovechando la propia estructura recursiva del árbol: un árbol es un nodo raíz con subárboles izquierdo y derecho, cada uno de los cuales es a su vez un árbol.

Las tres variantes de DFS se diferencian únicamente en cuándo se procesa la raíz respecto a los subárboles:

RecorridoOrden de visitaMnemotécnico
PreordenRaíz → Izquierdo → DerechoRID — la Raíz va primero
InordenIzquierdo → Raíz → DerechoIRD — la Raíz va en medio
PostordenIzquierdo → Derecho → RaízIDR — la Raíz va al final

▶️ Recorrido en preorden (Pre-order)

En el recorrido en preorden, se visita primero la raíz, después el subárbol izquierdo y finalmente el subárbol derecho. Es el recorrido más intuitivo y el que refleja la estructura del árbol de forma más directa.

🔹 Algoritmo paso a paso

El algoritmo consta de tres pasos para cada nodo:

1. Procesar el nodo actual (por ejemplo, imprimir su valor).

2. Llamar recursivamente al método con el hijo izquierdo.

3. Llamar recursivamente al método con el hijo derecho.

Java
/**
 * Recorrido en preorden: Raíz → Izquierdo → Derecho
 * @param nodo raíz del subárbol a recorrer
 */
public static <T> void preorden(NodoArbol<T> nodo) {
    if (nodo == null) {
        return; // Caso base: subárbol vacío
    }
    System.out.print(nodo.dato + " ");  // 1. Procesar raíz
    preorden(nodo.izquierdo);            // 2. Recorrer subárbol izquierdo
    preorden(nodo.derecho);              // 3. Recorrer subárbol derecho
}

🔹 Traza del ejemplo

Aplicando preorden al árbol de ejemplo (raíz a):

Traza
preorden(a)  →  imprime "a", luego preorden(b), luego preorden(c)
  preorden(b)  →  imprime "b", luego preorden(d), luego preorden(e)
    preorden(d)  →  imprime "d", luego preorden(null), preorden(null) → retorna
    preorden(e)  →  imprime "e", luego preorden(null), preorden(null) → retorna
  preorden(c)  →  imprime "c", luego preorden(null), preorden(null) → retorna

Resultado: a  b  d  e  c
💡
Aplicación práctica: El preorden es especialmente útil para serializar un árbol (guardarlo en un fichero) y para crear copias exactas de su estructura, ya que procesa la raíz antes de los hijos.

🔀 Recorrido en inorden (In-order)

En el recorrido en inorden, se visita primero el subárbol izquierdo, después la raíz y finalmente el subárbol derecho. Este recorrido tiene una propiedad fundamental: cuando se aplica a un árbol binario de búsqueda (BST), los nodos se visitan en orden ascendente.

Java
/**
 * Recorrido en inorden: Izquierdo → Raíz → Derecho
 * En un BST, produce los valores en orden ascendente.
 * @param nodo raíz del subárbol a recorrer
 */
public static <T> void inorden(NodoArbol<T> nodo) {
    if (nodo == null) {
        return;
    }
    inorden(nodo.izquierdo);             // 1. Recorrer subárbol izquierdo
    System.out.print(nodo.dato + " ");   // 2. Procesar raíz
    inorden(nodo.derecho);               // 3. Recorrer subárbol derecho
}

🔹 Traza del ejemplo

Traza
inorden(a) → inorden(b), imprime "a", inorden(c)
  inorden(b) → inorden(d), imprime "b", inorden(e)
    inorden(d) → inorden(null), imprime "d", inorden(null)
    inorden(e) → inorden(null), imprime "e", inorden(null)
  inorden(c) → inorden(null), imprime "c", inorden(null)

Resultado: d  b  e  a  c

🔹 Inorden en un BST numérico

Observa cómo el inorden produce los valores ordenados en un BST:

Java
/*
 * BST numérico:
 *        8
 *       / \
 *      3   10
 *     / \    \
 *    1   6    14
 */
NodoArbol<Integer> n1  = new NodoArbol<>(1);
NodoArbol<Integer> n6  = new NodoArbol<>(6);
NodoArbol<Integer> n14 = new NodoArbol<>(14);
NodoArbol<Integer> n3  = new NodoArbol<>(3, n1, n6);
NodoArbol<Integer> n10 = new NodoArbol<>(10, null, n14);
NodoArbol<Integer> n8  = new NodoArbol<>(8, n3, n10);

inorden(n8);  // Salida: 1  3  6  8  10  14  (¡ordenados!)
Buena práctica: Si necesitas obtener los elementos de un BST ordenados, el recorrido inorden es la solución más elegante y eficiente, con complejidad O(n) tanto en tiempo como en las llamadas recursivas.

🔄 Recorrido en postorden (Post-order)

En el recorrido en postorden, se visitan primero ambos subárboles y la raíz se procesa al final. Este recorrido es particularmente útil cuando necesitamos procesar los hijos antes que el padre, como al calcular el tamaño de directorios o al liberar la memoria de un árbol.

Java
/**
 * Recorrido en postorden: Izquierdo → Derecho → Raíz
 * Ideal para liberar memoria o calcular propiedades bottom-up.
 * @param nodo raíz del subárbol a recorrer
 */
public static <T> void postorden(NodoArbol<T> nodo) {
    if (nodo == null) {
        return;
    }
    postorden(nodo.izquierdo);           // 1. Recorrer subárbol izquierdo
    postorden(nodo.derecho);             // 2. Recorrer subárbol derecho
    System.out.print(nodo.dato + " ");   // 3. Procesar raíz
}

🔹 Traza del ejemplo

Traza
postorden(a) → postorden(b), postorden(c), imprime "a"
  postorden(b) → postorden(d), postorden(e), imprime "b"
    postorden(d) → postorden(null), postorden(null), imprime "d"
    postorden(e) → postorden(null), postorden(null), imprime "e"
  postorden(c) → postorden(null), postorden(null), imprime "c"

Resultado: d  e  b  c  a

🔹 Aplicación: calcular la altura del árbol

El postorden es la estrategia natural para calcular propiedades bottom-up (de las hojas hacia la raíz), como la altura:

Java
/**
 * Calcula la altura de un árbol binario usando recorrido postorden.
 * La altura de un árbol vacío es -1 por convención.
 */
public static <T> int altura(NodoArbol<T> nodo) {
    if (nodo == null) {
        return -1;  // Caso base
    }
    int alturaIzq = altura(nodo.izquierdo);   // Postorden: primero hijos
    int alturaDer = altura(nodo.derecho);
    return 1 + Math.max(alturaIzq, alturaDer); // Después raíz
}

// Ejemplo:
System.out.println(altura(a)); // Salida: 2 (a→b→d tiene 2 aristas)

🌊 Recorrido en anchura (BFS — Breadth-First Search)

A diferencia de los recorridos en profundidad, el recorrido en anchura explora el árbol nivel por nivel, visitando todos los nodos de una misma profundidad antes de pasar al siguiente nivel. Es el equivalente a leer un libro «de izquierda a derecha y de arriba abajo».

La implementación utiliza una cola (Queue) como estructura auxiliar. El algoritmo funciona de la siguiente manera:

1. Insertar la raíz en la cola.

2. Mientras la cola no esté vacía, extraer el nodo del frente.

3. Procesar el nodo extraído.

4. Insertar en la cola los hijos del nodo (primero izquierdo, luego derecho), si existen.

Java
import java.util.LinkedList;
import java.util.Queue;

/**
 * Recorrido en anchura (BFS): nivel por nivel, de izquierda a derecha.
 * Usa una cola FIFO como estructura auxiliar.
 * @param raiz nodo raíz del árbol
 */
public static <T> void bfs(NodoArbol<T> raiz) {
    if (raiz == null) {
        return;
    }

    Queue<NodoArbol<T>> cola = new LinkedList<>();
    cola.add(raiz);  // 1. Insertar raíz

    while (!cola.isEmpty()) {
        NodoArbol<T> actual = cola.poll();          // 2. Extraer del frente
        System.out.print(actual.dato + " ");         // 3. Procesar

        if (actual.izquierdo != null) {
            cola.add(actual.izquierdo);              // 4a. Encolar hijo izquierdo
        }
        if (actual.derecho != null) {
            cola.add(actual.derecho);                // 4b. Encolar hijo derecho
        }
    }
}

🔹 Traza paso a paso

Traza BFS
Cola inicial: [a]

Paso 1: Extraer a → Procesar "a" → Encolar b, c  → Cola: [b, c]
Paso 2: Extraer b → Procesar "b" → Encolar d, e  → Cola: [c, d, e]
Paso 3: Extraer c → Procesar "c" → Sin hijos      → Cola: [d, e]
Paso 4: Extraer d → Procesar "d" → Sin hijos      → Cola: [e]
Paso 5: Extraer e → Procesar "e" → Sin hijos      → Cola: []

Resultado: a  b  c  d  e   (nivel 0, nivel 1, nivel 2)

🔹 BFS por niveles (con separación)

Una variante muy utilizada es el recorrido por niveles (level-order traversal), que agrupa los nodos por profundidad. Esto es muy útil para imprimir el árbol visualmente o para algoritmos que necesitan procesar cada nivel de forma independiente:

Java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * Recorrido BFS por niveles. Devuelve una lista de listas,
 * donde cada sublista contiene los valores de un nivel del árbol.
 */
public static <T> List<List<T>> bfsPorNiveles(NodoArbol<T> raiz) {
    List<List<T>> niveles = new ArrayList<>();
    if (raiz == null) {
        return niveles;
    }

    Queue<NodoArbol<T>> cola = new LinkedList<>();
    cola.add(raiz);

    while (!cola.isEmpty()) {
        int tamanoNivel = cola.size(); // Nodos en el nivel actual
        List<T> nivelActual = new ArrayList<>();

        for (int i = 0; i < tamanoNivel; i++) {
            NodoArbol<T> nodo = cola.poll();
            nivelActual.add(nodo.dato);

            if (nodo.izquierdo != null) cola.add(nodo.izquierdo);
            if (nodo.derecho != null)   cola.add(nodo.derecho);
        }
        niveles.add(nivelActual);
    }
    return niveles;
}

// Ejemplo de uso:
// bfsPorNiveles(a) → [[a], [b, c], [d, e]]
💡
Dato clave: El truco para agrupar por niveles es capturar cola.size() al inicio de cada iteración. Ese valor nos indica cuántos nodos pertenecen al nivel actual, ya que todos los nodos encolados durante la iteración pertenecerán al nivel siguiente.

⚖️ Comparativa DFS vs BFS

Elegir entre DFS y BFS depende del problema concreto, la forma del árbol y los requisitos de memoria. A continuación se presenta una comparativa detallada:

CriterioDFS (Profundidad)BFS (Anchura)
Estructura auxiliarPila (implícita o explícita)Cola (Queue)
Complejidad temporalO(n)O(n)
Complejidad espacialO(h) donde h = alturaO(w) donde w = anchura máxima
Mejor para árboles...Profundos y estrechosAnchos y poco profundos
Encuentra camino más corto❌ No garantizado✅ Sí (en grafos no ponderados)
Implementación naturalRecursiónIterativa con cola
Riesgo de Stack Overflow⚠️ En árboles muy profundos❌ No aplica
Uso típicoBúsqueda, serialización, cálculos bottom-upEncontrar nodo más cercano, recorrido por niveles
⚠️
Cuidado con la recursión: En árboles muy profundos (miles de niveles), DFS recursivo puede provocar StackOverflowError. En esos casos, usa la versión iterativa con pila explícita que veremos más adelante.

📚 DFS iterativo con pila explícita

Aunque la recursión es la forma más natural de implementar DFS, en ciertos escenarios — árboles extremadamente profundos o entornos con pila de llamadas limitada — conviene usar una pila explícita. Esto nos da el mismo resultado que la versión recursiva pero sin riesgo de desbordamiento de pila.

🔹 Preorden iterativo

Java
import java.util.ArrayDeque;
import java.util.Deque;

/**
 * Recorrido en preorden usando pila explícita (iterativo).
 * Evita StackOverflowError en árboles muy profundos.
 */
public static <T> void preordenIterativo(NodoArbol<T> raiz) {
    if (raiz == null) {
        return;
    }

    Deque<NodoArbol<T>> pila = new ArrayDeque<>();
    pila.push(raiz);

    while (!pila.isEmpty()) {
        NodoArbol<T> actual = pila.pop();
        System.out.print(actual.dato + " ");

        // Apilar derecho primero para que izquierdo salga antes (LIFO)
        if (actual.derecho != null) {
            pila.push(actual.derecho);
        }
        if (actual.izquierdo != null) {
            pila.push(actual.izquierdo);
        }
    }
}

// preordenIterativo(a) → a  b  d  e  c  (mismo resultado que recursivo)
💡
Detalle crucial: Observa que apilamos el hijo derecho antes que el izquierdo. Como la pila es LIFO (Last In, First Out), el hijo izquierdo se extrae primero, respetando el orden preorden: Raíz → Izquierdo → Derecho.

🔹 Inorden iterativo

El inorden iterativo es ligeramente más complejo. Necesitamos ir bajando por la izquierda hasta llegar a null, procesar el nodo y luego movernos al subárbol derecho:

Java
/**
 * Recorrido en inorden usando pila explícita (iterativo).
 */
public static <T> void inordenIterativo(NodoArbol<T> raiz) {
    Deque<NodoArbol<T>> pila = new ArrayDeque<>();
    NodoArbol<T> actual = raiz;

    while (actual != null || !pila.isEmpty()) {
        // Descender por la izquierda hasta el fondo
        while (actual != null) {
            pila.push(actual);
            actual = actual.izquierdo;
        }
        // Procesar el nodo del tope de la pila
        actual = pila.pop();
        System.out.print(actual.dato + " ");
        // Moverse al subárbol derecho
        actual = actual.derecho;
    }
}

// inordenIterativo(a) → d  b  e  a  c  (mismo resultado que recursivo)

🏗️ Ejemplo integrador: explorador de sistema de archivos

Para integrar todos los conceptos, implementaremos un sistema de archivos simulado representado como un árbol. Cada nodo puede ser un directorio (con hijos) o un archivo (hoja). Aplicaremos los diferentes recorridos para resolver problemas reales: listar archivos, calcular tamaños y buscar elementos.

Java
import java.util.*;

/**
 * Nodo del sistema de archivos. Puede ser archivo o directorio.
 * Los directorios pueden tener múltiples hijos (árbol n-ario).
 */
class NodoFS {
    String nombre;
    boolean esDirectorio;
    long tamanoBytes;          // Solo para archivos
    List<NodoFS> hijos;       // Solo para directorios

    // Constructor para archivo
    NodoFS(String nombre, long tamanoBytes) {
        this.nombre = nombre;
        this.esDirectorio = false;
        this.tamanoBytes = tamanoBytes;
        this.hijos = Collections.emptyList();
    }

    // Constructor para directorio
    NodoFS(String nombre, NodoFS... hijos) {
        this.nombre = nombre;
        this.esDirectorio = true;
        this.tamanoBytes = 0;
        this.hijos = Arrays.asList(hijos);
    }
}

public class ExploradorFS {

    /**
     * PREORDEN: Listar todos los elementos con indentación.
     * Procesa la raíz primero → muestra la estructura jerárquica.
     */
    public static void listarArbol(NodoFS nodo, int profundidad) {
        if (nodo == null) return;

        String indent = "  ".repeat(profundidad);
        String icono = nodo.esDirectorio ? "📁" : "📄";
        String info = nodo.esDirectorio ? "" : " (" + nodo.tamanoBytes + " bytes)";
        System.out.println(indent + icono + " " + nodo.nombre + info);

        for (NodoFS hijo : nodo.hijos) {
            listarArbol(hijo, profundidad + 1);
        }
    }

    /**
     * POSTORDEN: Calcular el tamaño total de un directorio.
     * Procesa los hijos primero → suma sus tamaños antes de reportar.
     */
    public static long calcularTamano(NodoFS nodo) {
        if (nodo == null) return 0;

        if (!nodo.esDirectorio) {
            return nodo.tamanoBytes;  // Archivo: retornar su tamaño
        }

        long total = 0;
        for (NodoFS hijo : nodo.hijos) {
            total += calcularTamano(hijo);  // Postorden: hijos primero
        }
        return total;  // Luego la raíz acumula
    }

    /**
     * BFS: Encontrar el archivo más grande en el primer nivel.
     * Explora nivel por nivel → ideal para búsquedas de proximidad.
     */
    public static String archivoMasGrandeNivel(NodoFS raiz, int nivelObjetivo) {
        if (raiz == null) return null;

        Queue<NodoFS> cola = new LinkedList<>();
        cola.add(raiz);
        int nivelActual = 0;
        String mayorArchivo = null;
        long mayorTamano = -1;

        while (!cola.isEmpty() && nivelActual <= nivelObjetivo) {
            int tamNivel = cola.size();
            for (int i = 0; i < tamNivel; i++) {
                NodoFS nodo = cola.poll();
                if (nivelActual == nivelObjetivo && !nodo.esDirectorio) {
                    if (nodo.tamanoBytes > mayorTamano) {
                        mayorTamano = nodo.tamanoBytes;
                        mayorArchivo = nodo.nombre;
                    }
                }
                for (NodoFS hijo : nodo.hijos) {
                    cola.add(hijo);
                }
            }
            nivelActual++;
        }
        return mayorArchivo;
    }

    public static void main(String[] args) {
        // Construir sistema de archivos de ejemplo
        NodoFS raiz = new NodoFS("proyecto",
            new NodoFS("src",
                new NodoFS("Main.java", 2048),
                new NodoFS("Utils.java", 1024),
                new NodoFS("modelos",
                    new NodoFS("Usuario.java", 512),
                    new NodoFS("Producto.java", 768)
                )
            ),
            new NodoFS("docs",
                new NodoFS("README.md", 4096),
                new NodoFS("API.md", 8192)
            ),
            new NodoFS("pom.xml", 256)
        );

        System.out.println("=== Listado del árbol (Preorden) ===");
        listarArbol(raiz, 0);

        System.out.println("\n=== Tamaño total (Postorden) ===");
        System.out.println("Total: " + calcularTamano(raiz) + " bytes");

        System.out.println("\n=== Archivo más grande en nivel 2 (BFS) ===");
        System.out.println("Archivo: " + archivoMasGrandeNivel(raiz, 2));
    }
}

🔹 Salida esperada

Consola
=== Listado del árbol (Preorden) ===
📁 proyecto
  📁 src
    📄 Main.java (2048 bytes)
    📄 Utils.java (1024 bytes)
    📁 modelos
      📄 Usuario.java (512 bytes)
      📄 Producto.java (768 bytes)
  📁 docs
    📄 README.md (4096 bytes)
    📄 API.md (8192 bytes)
  📄 pom.xml (256 bytes)

=== Tamaño total (Postorden) ===
Total: 16896 bytes

=== Archivo más grande en nivel 2 (BFS) ===
Archivo: API.md
Observa cómo cada recorrido resuelve un problema distinto: Preorden muestra la jerarquía natural (como el comando tree en Linux). Postorden calcula propiedades acumulativas (como du -sh). BFS permite búsquedas por nivel de profundidad.

✏️ Ejercicios resueltos

Ejercicio 1: Contar nodos de un árbol binario

Escribe un método recursivo que cuente el número total de nodos de un árbol binario. Utiliza la estructura NodoArbol<T> definida anteriormente.

Ejercicio 2: Verificar si un árbol es simétrico

Escribe un método que determine si un árbol binario es simétrico (es decir, si su mitad izquierda es una imagen especular de la derecha).

Ejercicio 3: Encontrar la profundidad de un nodo específico

Implementa un método que, dado un árbol binario y un valor objetivo, devuelva la profundidad (nivel) del nodo que contiene ese valor, o -1 si no existe.

Ejercicio 4: Recorrido en zigzag (BFS alternante)

Implementa un recorrido por niveles en zigzag: el primer nivel se lee de izquierda a derecha, el segundo de derecha a izquierda, el tercero de izquierda a derecha, y así sucesivamente.

⚠️ Errores comunes y buenas prácticas

🔹 Error 1: Olvidar el caso base en recursión

Java
// ❌ MAL: sin caso base → StackOverflowError
public static void preorden(NodoArbol nodo) {
    System.out.print(nodo.dato + " ");
    preorden(nodo.izquierdo); // NullPointerException cuando hijo es null
    preorden(nodo.derecho);
}

// ✅ BIEN: siempre verificar si el nodo es null
public static void preorden(NodoArbol nodo) {
    if (nodo == null) return;  // CASO BASE OBLIGATORIO
    System.out.print(nodo.dato + " ");
    preorden(nodo.izquierdo);
    preorden(nodo.derecho);
}

🔹 Error 2: Orden incorrecto al apilar en DFS iterativo

Java
// ❌ MAL: apilar izquierdo antes → recorre primero el derecho
pila.push(actual.izquierdo);  // Sale segundo (LIFO)
pila.push(actual.derecho);    // Sale primero

// ✅ BIEN: apilar derecho antes para que izquierdo salga primero
pila.push(actual.derecho);    // Sale segundo
pila.push(actual.izquierdo);  // Sale primero (LIFO)

🔹 Error 3: Usar Stack en lugar de Deque

La clase java.util.Stack hereda de Vector y está obsoleta. La alternativa moderna recomendada por la documentación oficial de Java es usar Deque implementado con ArrayDeque:

Java
// ❌ OBSOLETO: Stack hereda de Vector (sincronizado innecesariamente)
Stack<NodoArbol> pila = new Stack<>();

// ✅ RECOMENDADO: ArrayDeque es más rápido y moderno
Deque<NodoArbol> pila = new ArrayDeque<>();

🔹 Error 4: No verificar nulos al encolar hijos en BFS

Java
// ❌ MAL: encolar sin verificar → NullPointerException al extraer
cola.add(actual.izquierdo); // Puede ser null
cola.add(actual.derecho);

// ✅ BIEN: verificar antes de encolar
if (actual.izquierdo != null) cola.add(actual.izquierdo);
if (actual.derecho != null)   cola.add(actual.derecho);
Regla de oro: Siempre que trabajes con árboles, recuerda el patrón «guardia al inicio»: la primera línea de cualquier método recursivo de árbol debe ser if (nodo == null) return;. Esto protege contra NullPointerException y establece el caso base de la recursión.

❓ Preguntas frecuentes sobre Exploración de Árboles en Java

Las dudas más comunes respondidas de forma clara y directa.

DFS (Depth-First Search) explora lo más profundo posible antes de retroceder, utilizando una pila (implícita en recursión o explícita). BFS (Breadth-First Search) explora nivel por nivel utilizando una cola. DFS consume menos memoria en árboles profundos y estrechos, mientras que BFS es mejor para encontrar el camino más corto o explorar árboles anchos y poco profundos.
El recorrido inorden visita primero el subárbol izquierdo, después la raíz y finalmente el subárbol derecho. En un árbol binario de búsqueda (BST), esto produce los nodos en orden ascendente, lo que lo convierte en el recorrido más utilizado para obtener datos ordenados de un BST.
Todos los recorridos estándar (preorden, inorden, postorden y BFS) tienen complejidad temporal O(n), donde n es el número de nodos, ya que cada nodo se visita exactamente una vez. La complejidad espacial varía: O(h) para DFS recursivo (donde h es la altura del árbol) y O(w) para BFS (donde w es la anchura máxima del árbol).
Preorden es útil para crear copias del árbol o serializar su estructura. Inorden se usa en BST para obtener datos ordenados. Postorden es ideal para liberar memoria del árbol (se eliminan hijos antes que padres) o para calcular propiedades que dependen de los subárboles, como la altura o el tamaño.
Sí. Se puede implementar DFS de forma iterativa usando una pila explícita (Stack o Deque en Java). Se extrae el nodo del tope de la pila, se procesa y se apilan sus hijos en orden inverso. Esta técnica evita el riesgo de desbordamiento de pila (StackOverflowError) en árboles muy profundos.
DFS utiliza una pila, que puede ser la pila de llamadas del sistema (en la versión recursiva) o una pila explícita (java.util.Deque). BFS utiliza una cola (java.util.Queue, típicamente implementada con LinkedList o ArrayDeque). La elección de la estructura de datos es lo que determina el orden de visita de los nodos.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Exploración de Árboles en Java? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!