Un árbol binario de búsqueda (ABB o BST) organiza los datos de modo que cada nodo cumple la regla: los valores del subárbol izquierdo son menores que el valor del nodo y los del subárbol derecho son mayores o iguales. Esto permite localizar elementos en tiempo logarítmico cuando el árbol está balanceado y facilita algoritmos de ordenación y filtrado.
Utilizamos el mismo modelo de nodo presentado en los temas anteriores, lo que nos permite compartir funciones de inserción y recorridos sin modificaciones.
typedef struct TreeNode {
int valor;
struct TreeNode *izquierdo;
struct TreeNode *derecho;
unsigned char nivel;
} TreeNode;
typedef struct {
TreeNode *raiz;
unsigned int cantidad;
} ArbolBusqueda;
La inserción sigue el mismo patrón que en el tema 3: avanzamos según la comparación y colocamos el nodo en el primer puntero nulo.
TreeNode *insertarABB(ArbolBusqueda *arbol, int valor) {
if (!arbol->raiz) {
TreeNode *nodo = crearNodo(valor);
if (!nodo) return NULL;
arbol->raiz = nodo;
arbol->cantidad = 1;
return nodo;
}
TreeNode *actual = arbol->raiz;
TreeNode *anterior = NULL;
while (actual) {
anterior = actual;
if (valor < actual->valor) {
actual = actual->izquierdo;
} else {
actual = actual->derecho;
}
}
TreeNode *nuevo = crearNodo(valor);
if (!nuevo) return NULL;
nuevo->nivel = (unsigned char)(anterior->nivel + 1);
if (valor < anterior->valor) {
anterior->izquierdo = nuevo;
} else {
anterior->derecho = nuevo;
}
arbol->cantidad++;
return nuevo;
}
Encontrar un valor aprovecha la misma regla. Si el valor buscado es menor que el nodo actual, exploramos el subárbol izquierdo; de lo contrario, el derecho.
TreeNode *buscarABB(TreeNode *nodo, int valor) {
while (nodo) {
if (valor == nodo->valor) {
return nodo;
}
if (valor < nodo->valor) {
nodo = nodo->izquierdo;
} else {
nodo = nodo->derecho;
}
}
return NULL;
}
El mínimo se obtiene siguiendo la rama izquierda hasta encontrar una hoja. El máximo aplica el mismo principio hacia la derecha.
TreeNode *minimo(TreeNode *nodo) {
if (!nodo) return NULL;
while (nodo->izquierdo) {
nodo = nodo->izquierdo;
}
return nodo;
}
TreeNode *maximo(TreeNode *nodo) {
if (!nodo) return NULL;
while (nodo->derecho) {
nodo = nodo->derecho;
}
return nodo;
}
Eliminar un nodo requiere manejar tres casos clásicos:
TreeNode *eliminarABB(TreeNode *raiz, int valor) {
if (!raiz) return NULL;
if (valor < raiz->valor) {
raiz->izquierdo = eliminarABB(raiz->izquierdo, valor);
} else if (valor > raiz->valor) {
raiz->derecho = eliminarABB(raiz->derecho, valor);
} else {
if (!raiz->izquierdo) {
TreeNode *derecho = raiz->derecho;
free(raiz);
return derecho;
}
if (!raiz->derecho) {
TreeNode *izquierdo = raiz->izquierdo;
free(raiz);
return izquierdo;
}
TreeNode *sucesor = minimo(raiz->derecho);
raiz->valor = sucesor->valor;
raiz->derecho = eliminarABB(raiz->derecho, sucesor->valor);
}
return raiz;
}
Cuando el código proviene de fuentes externas conviene validar que el árbol respete la regla de ordenamiento. Esto se logra comprobando rangos permitidos en cada nodo.
int esABB(TreeNode *nodo, int min, int max) {
if (!nodo) return 1;
if (nodo->valor < min || nodo->valor > max) return 0;
return esABB(nodo->izquierdo, min, nodo->valor - 1) &&
esABB(nodo->derecho, nodo->valor + 1, max);
}
Un ABB puede degradarse a una lista cuando los datos llegan casi ordenados. Para mitigar el problema:
El siguiente programa integra inserción, búsqueda, obtención de extremos y eliminación en un ABB.
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int valor;
struct TreeNode *izquierdo;
struct TreeNode *derecho;
} TreeNode;
typedef struct {
TreeNode *raiz;
unsigned int cantidad;
} ArbolBusqueda;
TreeNode *crearNodo(int valor) {
TreeNode *nodo = malloc(sizeof(TreeNode));
if (!nodo) return NULL;
nodo->valor = valor;
nodo->izquierdo = NULL;
nodo->derecho = NULL;
return nodo;
}
TreeNode *insertarABB(ArbolBusqueda *arbol, int valor) {
if (!arbol->raiz) {
TreeNode *nodo = crearNodo(valor);
if (!nodo) return NULL;
arbol->raiz = nodo;
arbol->cantidad = 1;
return nodo;
}
TreeNode *actual = arbol->raiz;
TreeNode *anterior = NULL;
while (actual) {
anterior = actual;
if (valor < actual->valor) actual = actual->izquierdo;
else actual = actual->derecho;
}
TreeNode *nuevo = crearNodo(valor);
if (!nuevo) return NULL;
if (valor < anterior->valor) anterior->izquierdo = nuevo;
else anterior->derecho = nuevo;
arbol->cantidad++;
return nuevo;
}
TreeNode *buscarABB(TreeNode *nodo, int valor) {
while (nodo) {
if (valor == nodo->valor) return nodo;
nodo = (valor < nodo->valor) ? nodo->izquierdo : nodo->derecho;
}
return NULL;
}
TreeNode *minimo(TreeNode *nodo) {
if (!nodo) return NULL;
while (nodo->izquierdo) nodo = nodo->izquierdo;
return nodo;
}
TreeNode *eliminarABB(TreeNode *raiz, int valor) {
if (!raiz) return NULL;
if (valor < raiz->valor) {
raiz->izquierdo = eliminarABB(raiz->izquierdo, valor);
} else if (valor > raiz->valor) {
raiz->derecho = eliminarABB(raiz->derecho, valor);
} else {
if (!raiz->izquierdo) {
TreeNode *derecho = raiz->derecho;
free(raiz);
return derecho;
}
if (!raiz->derecho) {
TreeNode *izquierdo = raiz->izquierdo;
free(raiz);
return izquierdo;
}
TreeNode *sucesor = minimo(raiz->derecho);
raiz->valor = sucesor->valor;
raiz->derecho = eliminarABB(raiz->derecho, sucesor->valor);
}
return raiz;
}
void imprimirInorden(TreeNode *nodo) {
if (!nodo) return;
imprimirInorden(nodo->izquierdo);
printf("%d ", nodo->valor);
imprimirInorden(nodo->derecho);
}
void liberarArbol(TreeNode *nodo) {
if (!nodo) return;
liberarArbol(nodo->izquierdo);
liberarArbol(nodo->derecho);
free(nodo);
}
int main(void) {
ArbolBusqueda abb = {0};
int valores[] = {15, 8, 20, 4, 10, 17, 25};
unsigned int total = (unsigned int)(sizeof(valores)/sizeof(valores[0]));
for (unsigned int i = 0; i < total; i++) {
insertarABB(&abb, valores[i]);
}
puts("Inorden:");
imprimirInorden(abb.raiz);
int objetivo = 10;
TreeNode *encontrado = buscarABB(abb.raiz, objetivo);
printf("\nBuscar %d: %s\n", objetivo, encontrado ? "hallado" : "no encontrado");
eliminarABB(abb.raiz, 15);
puts("Inorden tras eliminar la raiz:");
imprimirInorden(abb.raiz);
liberarArbol(abb.raiz);
return 0;
}