Tras implementar inserciones y eliminaciones, el siguiente paso es vigilar el balance del árbol. Un ABB degradado puede comportarse como una lista enlazada, elevando el costo de búsqueda a O(n). En este tema explicamos cómo detectar desequilibrios, aplicar rotaciones simples o dobles y rearmar el árbol a partir de un recorrido inorden para recuperar el rendimiento.
Partimos del mismo TreeNode utilizado en los temas previos. Si ya cuentas con funciones de inserción y recorrido, puedes integrarlas sin cambios.
typedef struct TreeNode {
int valor;
struct TreeNode *izquierdo;
struct TreeNode *derecho;
} TreeNode;
El factor de balance se define como la diferencia entre la altura del subárbol izquierdo y el derecho. Valores muy grandes (por ejemplo, mayores a 1 o menores a -1) indican que la estructura necesita una rotación.
int altura(TreeNode *nodo) {
if (!nodo) return 0;
int izq = altura(nodo->izquierdo);
int der = altura(nodo->derecho);
return 1 + (izq > der ? izq : der);
}
int factorBalance(TreeNode *nodo) {
if (!nodo) return 0;
return altura(nodo->izquierdo) - altura(nodo->derecho);
}
Una rotación simple a la derecha compensa un exceso de nodos en la rama izquierda. La rotación a la izquierda actúa de manera simétrica.
TreeNode *rotarDerecha(TreeNode *y) {
TreeNode *x = y->izquierdo;
TreeNode *subDerecho = x->derecho;
x->derecho = y;
y->izquierdo = subDerecho;
return x;
}
TreeNode *rotarIzquierda(TreeNode *x) {
TreeNode *y = x->derecho;
TreeNode *subIzquierdo = y->izquierdo;
y->izquierdo = x;
x->derecho = subIzquierdo;
return y;
}
Si la rama pesada está inclinada en dirección opuesta (casos LR o RL), primero se aplica una rotación simple sobre el hijo y luego una rotación en sentido contrario sobre el nodo problemático.
TreeNode *balancearNodo(TreeNode *nodo) {
int balance = factorBalance(nodo);
if (balance > 1) {
if (factorBalance(nodo->izquierdo) < 0) {
nodo->izquierdo = rotarIzquierda(nodo->izquierdo);
}
return rotarDerecha(nodo);
}
if (balance < -1) {
if (factorBalance(nodo->derecho) > 0) {
nodo->derecho = rotarDerecha(nodo->derecho);
}
return rotarIzquierda(nodo);
}
return nodo;
}
A diferencia del ABB simple, algunas variantes (como AVL) balancean cada nodo durante la inserción. La función siguiente muestra una versión sencilla que llama a balancearNodo al regresar de la recursión.
TreeNode *insertarBalanceado(TreeNode *nodo, int valor) {
if (!nodo) {
return crearNodo(valor);
}
if (valor < nodo->valor) {
nodo->izquierdo = insertarBalanceado(nodo->izquierdo, valor);
} else {
nodo->derecho = insertarBalanceado(nodo->derecho, valor);
}
return balancearNodo(nodo);
}
Cuando el árbol está muy degradado podemos rearmarlo tomando todos los valores, ordenándolos y montando un nuevo ABB balanceado.
void guardarInorden(TreeNode *nodo, int *destino, int *indice) {
if (!nodo) return;
guardarInorden(nodo->izquierdo, destino, indice);
destino[(*indice)++] = nodo->valor;
guardarInorden(nodo->derecho, destino, indice);
}
TreeNode *armarBalanceado(int *valores, int inicio, int fin) {
if (inicio > fin) return NULL;
int medio = (inicio + fin) / 2;
TreeNode *nodo = crearNodo(valores[medio]);
nodo->izquierdo = armarBalanceado(valores, inicio, medio - 1);
nodo->derecho = armarBalanceado(valores, medio + 1, fin);
return nodo;
}
En la práctica combinamos ambas técnicas:
El archivo siguiente muestra una inserción balanceada estilo AVL y verifica que el inorden permanezca ordenado tras varias operaciones.
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int valor;
struct TreeNode *izquierdo;
struct TreeNode *derecho;
} TreeNode;
TreeNode *crearNodo(int valor) {
TreeNode *n = malloc(sizeof(TreeNode));
if (!n) return NULL;
n->valor = valor;
n->izquierdo = NULL;
n->derecho = NULL;
return n;
}
int altura(TreeNode *nodo) {
if (!nodo) return 0;
int izq = altura(nodo->izquierdo);
int der = altura(nodo->derecho);
return 1 + (izq > der ? izq : der);
}
int factorBalance(TreeNode *nodo) {
if (!nodo) return 0;
return altura(nodo->izquierdo) - altura(nodo->derecho);
}
TreeNode *rotarDerecha(TreeNode *y) {
TreeNode *x = y->izquierdo;
TreeNode *subDerecho = x->derecho;
x->derecho = y;
y->izquierdo = subDerecho;
return x;
}
TreeNode *rotarIzquierda(TreeNode *x) {
TreeNode *y = x->derecho;
TreeNode *subIzquierdo = y->izquierdo;
y->izquierdo = x;
x->derecho = subIzquierdo;
return y;
}
TreeNode *balancearNodo(TreeNode *nodo) {
int balance = factorBalance(nodo);
if (balance > 1) {
if (factorBalance(nodo->izquierdo) < 0) {
nodo->izquierdo = rotarIzquierda(nodo->izquierdo);
}
return rotarDerecha(nodo);
}
if (balance < -1) {
if (factorBalance(nodo->derecho) > 0) {
nodo->derecho = rotarDerecha(nodo->derecho);
}
return rotarIzquierda(nodo);
}
return nodo;
}
TreeNode *insertarBalanceado(TreeNode *nodo, int valor) {
if (!nodo) return crearNodo(valor);
if (valor < nodo->valor) nodo->izquierdo = insertarBalanceado(nodo->izquierdo, valor);
else nodo->derecho = insertarBalanceado(nodo->derecho, valor);
return balancearNodo(nodo);
}
void imprimirInorden(TreeNode *nodo) {
if (!nodo) return;
imprimirInorden(nodo->izquierdo);
printf("%d ", nodo->valor);
imprimirInorden(nodo->derecho);
}
void liberar(TreeNode *nodo) {
if (!nodo) return;
liberar(nodo->izquierdo);
liberar(nodo->derecho);
free(nodo);
}
int main(void) {
int valores[] = {30, 20, 40, 10, 25, 35, 50, 5, 27};
unsigned int total = (unsigned int)(sizeof(valores)/sizeof(valores[0]));
TreeNode *raiz = NULL;
for (unsigned int i = 0; i < total; i++) {
raiz = insertarBalanceado(raiz, valores[i]);
}
puts("Inorden tras inserciones balanceadas:");
imprimirInorden(raiz);
printf("\nAltura final: %d\n", altura(raiz));
liberar(raiz);
return 0;
}