CodeWithBotina
28 jul 2026 10 min de lectura

¿Qué son los árboles binarios y por qué son tan famosos?

¿Qué son los árboles binarios y por qué son tan famosos?

La mayoría de las estructuras de datos que aprendemos al inicio ordenan la información de forma lineal: una lista, un arreglo, una cola. Pero el mundo real no es lineal. Los sistemas de archivos de tu computadora son jerárquicos. El organigrama de una empresa es jerárquico. El DOM de una página web es jerárquico. Para representar estas estructuras necesitamos algo más que una lista: necesitamos árboles.

Un árbol binario es la forma más fundamental y elegante de estructura jerárquica en computación. Es una estructura de datos no lineal en la que cada nodo tiene como máximo dos hijos, convencionalmente llamados hijo izquierdo e hijo derecho. Esta restricción —“como máximo dos”— es lo que lo hace tan poderoso y, al mismo tiempo, tan manejable.

Pero no te dejes engañar por su aparente simplicidad: los árboles binarios son la base de los sistemas de búsqueda más rápidos (árboles de búsqueda binaria), de los motores de bases de datos (B‑Trees), de la compresión de datos (codificación de Huffman), de los compiladores (árboles de sintaxis abstracta) y hasta de la inteligencia artificial (árboles de decisión). Entenderlos no es solo un ejercicio académico; es entender cómo funciona una parte fundamental del software que usas a diario.


¿Cómo funciona un árbol binario?

La estructura básica

Un árbol binario está compuesto por nodos. Cada nodo contiene tres elementos fundamentales:

  • Un valor (el dato que almacena)
  • Una referencia al hijo izquierdo
  • Una referencia al hijo derecho

El nodo superior, aquel del que cuelga todo el árbol, se llama raíz. Los nodos que no tienen hijos se llaman hojas. Los nodos intermedios son internos.

Terminología clave

  • Padre: el nodo que está inmediatamente arriba de otro
  • Hijo: el nodo que está inmediatamente abajo de otro
  • Hermanos: nodos que comparten el mismo padre
  • Ancestro: cualquier nodo en el camino desde la raíz hasta un nodo dado
  • Descendiente: cualquier nodo en el camino desde un nodo dado hasta una hoja

Dos métricas esenciales: profundidad y altura

La profundidad de un nodo es la longitud del camino desde la raíz hasta ese nodo (contando aristas). La altura de un nodo es la longitud del camino más largo desde ese nodo hasta una hoja. La altura de un árbol es, por tanto, la altura de su raíz.

flowchart TD
    A["RaízProfundidad: 0"] --> B["Nodo internoProfundidad: 1"]
    A --> C["Nodo internoProfundidad: 1"]
    B --> D["HojaProfundidad: 2"]
    B --> E["HojaProfundidad: 2"]
    C --> F["HojaProfundidad: 2"]
    C --> G["HojaProfundidad: 2"]
    
    style A fill:#e3f2fd,stroke:#1565c0
    style B fill:#e8f5e9,stroke:#2e7d32
    style C fill:#e8f5e9,stroke:#2e7d32
    style D fill:#fff3e0,stroke:#ef6c00
    style E fill:#fff3e0,stroke:#ef6c00
    style F fill:#fff3e0,stroke:#ef6c00
    style G fill:#fff3e0,stroke:#ef6c00

Recorridos: las tres formas de visitar un árbol binario

Para procesar un árbol binario, necesitamos recorrerlo. Existen tres recorridos clásicos, cada uno con un orden distinto:

  • Preorden: se visita el nodo raíz, luego el subárbol izquierdo, luego el derecho.
  • Inorden: se visita el subárbol izquierdo, luego la raíz, luego el derecho.
  • Postorden: se visita el subárbol izquierdo, luego el derecho, luego la raíz.
flowchart LR
    subgraph Preorden["Preorden: A → B → D → E → C → F → G"]
        P1["A"] --> P2["B"]
        P1 --> P3["C"]
        P2 --> P4["D"]
        P2 --> P5["E"]
        P3 --> P6["F"]
        P3 --> P7["G"]
    end

    subgraph Inorden["Inorden: D → B → E → A → F → C → G"]
        I1["A"] --> I2["B"]
        I1 --> I3["C"]
        I2 --> I4["D"]
        I2 --> I5["E"]
        I3 --> I6["F"]
        I3 --> I7["G"]
    end

    subgraph Postorden["Postorden: D → E → B → F → G → C → A"]
        O1["A"] --> O2["B"]
        O1 --> O3["C"]
        O2 --> O4["D"]
        O2 --> O5["E"]
        O3 --> O6["F"]
        O3 --> O7["G"]
    end

¿Por qué es tan famosa esta estructura de datos?

Los árboles binarios son famosos por una razón fundamental: permiten operaciones en tiempo logarítmico cuando se usan correctamente. Un árbol binario equilibrado de n nodos tiene una altura de O(\log n). Esto significa que buscar, insertar o eliminar un elemento en un árbol de búsqueda binaria bien equilibrado toma, en el peor de los casos, un tiempo proporcional a \log n, mucho más rápido que una lista enlazada (O(n)) y con una garantía de rendimiento que el hashing no siempre ofrece.

Sus aplicaciones en el mundo real son innumerables:

  • Sistemas de archivos: los directorios y subdirectorios son árboles.
  • Bases de datos: los índices B‑Tree y sus variantes son árboles binarios generalizados.
  • Compiladores: el análisis sintáctico genera árboles de sintaxis abstracta.
  • Inteligencia artificial: los árboles de decisión son la base de muchos algoritmos de machine learning.
  • Compresión de datos: la codificación de Huffman utiliza árboles binarios.
  • Redes: los algoritmos de enrutamiento usan árboles.
flowchart LR
    subgraph Aplicaciones["Aplicaciones de los árboles binarios"]
        FS["Sistemas de archivos"]
        DB["Bases de datos"]
        COMP["Compiladores"]
        AI["Inteligencia Artificial"]
        ZIP["Compresión de datos"]
        NET["Redes"]
    end
    
    FS --> BT["Árboles Binarios"]
    DB --> BT
    COMP --> BT
    AI --> BT
    ZIP --> BT
    NET --> BT
    
    style BT fill:#e1f5fe,stroke:#0288d1

Tipos de árboles binarios

No todos los árboles binarios son iguales. Existen variantes con propiedades específicas que los hacen adecuados para diferentes problemas.

Árbol binario completo (Full Binary Tree)

Cada nodo tiene 0 o 2 hijos. No hay nodos con un solo hijo. Todos los nodos internos tienen exactamente dos hijos y todas las hojas están al mismo nivel.

flowchart TD
    A["A"] --> B["B"]
    A --> C["C"]
    B --> D["D"]
    B --> E["E"]
    C --> F["F"]
    C --> G["G"]

Árbol binario perfecto (Perfect Binary Tree)

Es un caso especial de árbol completo: todos los nodos internos tienen dos hijos y todas las hojas están a la misma profundidad. Su número de nodos es 2^{h+1} - 1, donde h es la altura.

Árbol binario completo (Complete Binary Tree)

Todos los niveles, excepto posiblemente el último, están completamente llenos, y los nodos del último nivel están lo más a la izquierda posible. Esta es la estructura que se usa en los heaps (montículos).

flowchart TD
    A["A"] --> B["B"]
    A --> C["C"]
    B --> D["D"]
    B --> E["E"]
    C --> F["F"]

Árbol de búsqueda binaria (Binary Search Tree - BST)

Es un árbol binario con una propiedad adicional: para cada nodo, todos los valores en su subárbol izquierdo son menores que el valor del nodo, y todos los valores en su subárbol derecho son mayores. Esto permite búsquedas rápidas: en cada paso, descartas la mitad del árbol.

Árbol binario balanceado (Balanced Binary Tree)

Un árbol binario está balanceado si, para cada nodo, la altura de sus subárboles izquierdo y derecho difiere en como máximo 1. Las variantes más conocidas son los árboles AVL y Red‑Black.

Árbol binario sesgado (Skewed Binary Tree)

Es el caso degenerado: cada nodo tiene un solo hijo. En la práctica, el árbol se comporta como una lista enlazada, con complejidad O(n).

flowchart TD
    A["A"] --> B["B"]
    B --> C["C"]
    C --> D["D"]
    D --> E["E"]

Matemáticas de los árboles binarios

Como cualquier estructura de datos, los árboles binarios se pueden describir con precisión matemática. Estas son las fórmulas fundamentales.

Número máximo de nodos

En un árbol binario de altura h (donde la raíz está en el nivel 0), el número máximo de nodos es:

N_{\text{máx}} = 2^{h+1} - 1

Esto se debe a que cada nivel i puede tener como máximo 2^i nodos.

Número mínimo de nodos

El número mínimo de nodos para una altura h es:

N_{\text{mín}} = h + 1

Esto ocurre en un árbol sesgado, donde cada nivel tiene exactamente un nodo.

Relación entre nodos y altura

Para un árbol binario con n nodos, la altura mínima posible es:

h_{\text{mín}} = \lceil \log_2(n+1) \rceil - 1

Y la altura máxima posible es:

h_{\text{máx}} = n - 1

Número de nodos hoja

En un árbol binario completo (donde todos los nodos tienen 0 o 2 hijos), el número de hojas L y el número de nodos internos I cumplen:

L = I + 1

Esta es una propiedad fundamental de los árboles binarios completos.

Profundidad promedio

Para un árbol binario de búsqueda construido a partir de inserciones aleatorias, la profundidad esperada de un nodo es aproximadamente:

2 \ln n \approx 1.39 \log_2 n

Esto explica por qué los árboles BST aleatorios funcionan tan bien en la práctica, incluso sin balanceo explícito.


Árboles binarios en código

Veamos cómo se implementa un árbol binario en los lenguajes más populares. Todos los ejemplos comparten la misma estructura: una clase Node con un valor y dos referencias a hijos.

Java

public class BinaryTree {
    static class Node {
        T value;
        Node left;
        Node right;
        
        Node(T value) {
            this.value = value;
            this.left = null;
            this.right = null;
        }
    }
    
    private Node root;
    
    public BinaryTree() {
        this.root = null;
    }
    
    // Recorrido en preorden
    public void preorder() {
        preorder(root);
    }
    
    private void preorder(Node node) {
        if (node == null) return;
        System.out.print(node.value + " ");
        preorder(node.left);
        preorder(node.right);
    }
}

Python

class Node:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right
    
    def __repr__(self):
        return f"Node({self.value})"

class BinaryTree:
    def __init__(self):
        self.root = None
    
    def preorder(self, node=None):
        if node is None:
            node = self.root
        if node is None:
            return
        print(node.value, end=" ")
        self.preorder(node.left)
        self.preorder(node.right)

JavaScript

class Node {
    constructor(value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }
}

class BinaryTree {
    constructor() {
        this.root = null;
    }
    
    preorder(node = this.root) {
        if (node === null) return;
        console.log(node.value);
        this.preorder(node.left);
        this.preorder(node.right);
    }
}

C#

public class BinaryTree
{
    public class Node
    {
        public T Value { get; set; }
        public Node Left { get; set; }
        public Node Right { get; set; }
        
        public Node(T value)
        {
            Value = value;
            Left = null;
            Right = null;
        }
    }
    
    private Node _root;
    
    public BinaryTree()
    {
        _root = null;
    }
    
    public void Preorder()
    {
        Preorder(_root);
    }
    
    private void Preorder(Node node)
    {
        if (node == null) return;
        Console.Write(node.Value + " ");
        Preorder(node.Left);
        Preorder(node.Right);
    }
}

Rust

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: T,
    left: Option>>>,
    right: Option>>>,
}

impl Node {
    fn new(value: T) -> Rc> {
        Rc::new(RefCell::new(Node {
            value,
            left: None,
            right: None,
        }))
    }
}

struct BinaryTree {
    root: Option>>>,
}

impl BinaryTree {
    fn new() -> Self {
        BinaryTree { root: None }
    }
}

La encuesta: ¿qué estructura de datos elegirías?

Imagina que estás construyendo un sistema que debe manejar datos jerárquicos con operaciones frecuentes de búsqueda, inserción y eliminación, y necesitas que el rendimiento sea predecible y eficiente incluso en el peor de los casos. ¿Qué estructura de datos elegirías?

¿Qué estructura de datos usarías para este problema?

Referencias

1 Me gusta 0 No me gusta 1 total

Cargando reacciones...

Comentarios (0)

Cargando sesión...

Aún no hay comentarios. Sé el primero en comentar.

Volver a todas las publicaciones