Árboles binarios

En este episodio introduzco a los árboles binarios. Cómo los podemos clasificar (completos, balanceados...), y cómo los podemos recorrer: en anchura, en pre-orden, en inorden o en post-orden.

Los árboles binarios son una estructura fundamental en programación, caracterizados por tener un orden 2, lo que significa que cada nodo puede tener como máximo dos hijos. Esto implica que un nodo puede tener cero, uno o dos hijos, y esta simplicidad los hace muy versátiles y ampliamente utilizados en diversas aplicaciones.

Dentro de los árboles binarios, podemos distinguir varios tipos que nos ayudan a entender mejor su estructura y comportamiento. Por ejemplo, los árboles full, o full binary trees, son aquellos en los que cada nodo tiene o ningún hijo o exactamente dos hijos. Esto quiere decir que no existen nodos con un solo hijo; si un nodo no es hoja, debe tener ambos hijos, izquierdo y derecho. Esta propiedad hace que la estructura sea muy regular y equilibrada en cuanto a la distribución de nodos.

Por otro lado, los árboles complete, o complete binary trees, permiten cierta flexibilidad en el último nivel del árbol. En estos árboles, el último nivel puede no estar completamente lleno, es decir, algunos nodos pueden faltar, pero siempre respetando que si un nodo tiene un hijo derecho, debe tener también un hijo izquierdo. Esto garantiza que los nodos se llenen de izquierda a derecha, manteniendo una estructura compacta y eficiente para ciertas operaciones.

Es importante destacar que un árbol binario puede ser full, complete, ambos o ninguno de los dos, lo que da lugar a cuatro categorías diferentes dentro de los árboles binarios. Esta clasificación nos ayuda a entender mejor cómo se comportan y qué propiedades tienen, especialmente cuando trabajamos con algoritmos que dependen de la estructura del árbol.

Un caso especial dentro de los árboles binarios son los árboles degenerados. Estos son árboles en los que cada nodo tiene solo un hijo, lo que los hace comportarse como listas enlazadas. Aunque siguen siendo técnicamente árboles, su eficiencia se ve comprometida porque pierden las ventajas de las estructuras balanceadas, especialmente en operaciones como la búsqueda. Por eso, en muchos casos, buscamos mantener los árboles balanceados para minimizar la profundidad y optimizar el rendimiento.

En cuanto a la representación de un árbol binario en código, la estructura se simplifica notablemente. Cada nodo contiene un valor y dos referencias, una al hijo izquierdo y otra al hijo derecho. Si un nodo no tiene un hijo en alguna de estas posiciones, esa referencia es nula. Esto facilita la implementación y el manejo de los árboles binarios en comparación con árboles de orden superior, donde se requieren listas para almacenar múltiples hijos.

Cuando hablamos de operaciones en árboles binarios, las principales son la inserción, eliminación, localización y recorrido. Aunque las tres primeras se explican mejor en el contexto de árboles binarios de búsqueda, podemos centrarnos ahora en los recorridos, que son fundamentales para procesar y analizar los datos almacenados en el árbol.

Existen dos grandes categorías de recorridos: en anchura y en profundidad. El recorrido en anchura, también conocido como recorrido por niveles, consiste en procesar todos los nodos de un nivel antes de pasar al siguiente. Esto se logra utilizando una estructura de datos tipo cola para mantener el orden de los nodos a procesar. Este tipo de recorrido es especialmente útil en aplicaciones como la inteligencia artificial, donde se exploran árboles que pueden ser infinitos o muy grandes.

Por otro lado, los recorridos en profundidad se basan en la idea de procesar primero un nodo y luego sus subárboles, siguiendo un orden específico. Hay tres formas principales de recorrido en profundidad: preorden, inorden y postorden. En el preorden, se procesa primero la raíz, luego el subárbol izquierdo y finalmente el derecho. En el inorden, se procesa primero el subárbol izquierdo, luego la raíz y después el derecho. Finalmente, en el postorden, se procesan primero ambos subárboles y la raíz al final.

Para implementar estos recorridos en profundidad, es común utilizar la recursividad, aprovechando que un árbol es una estructura recursiva compuesta por subárboles. La condición base para detener la recursión es cuando se llega a un nodo nulo, es decir, cuando no hay más nodos que procesar en esa rama. Así, el algoritmo evita procesar nodos inexistentes y termina correctamente el recorrido.

Un ejemplo sencillo en código C para un recorrido en preorden podría ser el siguiente:

void preorden(Nodo* raiz) {
    if (raiz != NULL) {
        procesar(raiz->valor);
        preorden(raiz->izquierdo);
        preorden(raiz->derecho);
    }
}

De manera similar, el recorrido inorden y postorden solo cambian el orden en que se llama a las funciones recursivas y se procesa la raíz.

Estos conceptos básicos sobre árboles binarios y sus recorridos nos preparan para entender estructuras más complejas y eficientes, como los árboles binarios de búsqueda y los árboles balanceados, que optimizan operaciones como la inserción y la búsqueda. Pero antes de avanzar hacia esos temas, es fundamental dominar estas bases para manejar árboles con claridad y eficiencia.

Lista de reproducción
  1. 1
    Qué son las estructuras de datos
    3 minutos
  2. 2
    Listas enlazadas: Teoría
    12 minutos
  3. 3
    Listas enlazadas en C
    18 minutos
  4. 4
    Listas enlazadas en Java
    17 minutos
  5. 5
    Colas: teoría
    6 minutos
  6. 6
    Colas en C
    12 minutos
  7. 7
    Colas en Java
    10 minutos
  8. 8
    Pilas: teoría
    7 minutos
  9. 9
    Pilas en C
    11 minutos
  10. 10
    Pilas en Java
    10 minutos
  11. 11
    Introducción a los árboles
    11 minutos
  12. 12
    Árboles binarios
    9 minutos
  13. 13
    Árboles binarios de búsqueda (parte 1)
    10 minutos
  14. 14
    Árboles de búsqueda binaria (parte 2)
    8 minutos
  15. 15
    BSTs en C (parte 1)
    13 minutos
  16. 16
    BSTs en C (parte 2)
    8 minutos
  17. 17
    BSTs en C (y parte 3)
    18 minutos
  18. 18
    BSTs en Java (parte 1)
    9 minutos
  19. 19
    BSTs en Java (parte 2)
    11 minutos
  20. 20
    BSTs en Java (parte 3)
    10 minutos
  21. 21
    Árbol desde preorden e inorden
    8 minutos
  22. 22
    Árbol desde inorden y postorden
    8 minutos
  23. 23
    Árboles k-arios
    6 minutos
  24. 24
    Árboles B (parte 1)
    11 minutos
  25. 25
    Árboles B (parte 2)
    20 minutos
  26. 26
    Árboles B (parte 3)
    39 minutos