¿Qué es un ABB?
Un Árbol Binario de Búsqueda (ABB) es una estructura jerárquica que mantiene elementos ordenados según su clave. Cumple:
- Las claves del subárbol izquierdo son menores que la clave del nodo.
- Las claves del subárbol derecho son mayores que la clave del nodo.
- No permite claves duplicadas.
Esto permite realizar búsquedas, inserciones y eliminaciones de manera eficiente.
Propiedades
- La eficiencia de las operaciones depende de la altura (h) del árbol.
- En el mejor caso, si está equilibrado, las operaciones son O(log n).
- En el peor caso (árbol degenerado), las operaciones se degradan a O(n).
Un ABB no garantiza equilibrio; por eso existen árboles como los AVL que lo corrigen.
Estructura de datos
typedef struct tNodeBST {
tKey key;
struct tNodeBST *left;
struct tNodeBST *right;
} *tBST;
[ key ]
/ \
[left] [right]
Especificación en C
#define NULLBST NULL //Implementación dinámica
typedef int tKey; //Tipo clave del árbol
typedef struct tNodeBST {
tKey key; //clave del nodo
struct tNodeBST *left; //subárbol izquierdo
struct tNodeBST *right; //subárbol derecho
} *tBST; //puntero al nodo raíz del árbol
//Generadoras
/**
@brief Inicializa un árbol vacío
@param [out] tree Puntero al árbol a inicializar
@post El árbol queda inicializado como vacío
*/
void createEmptyTree(tBST *tree);
/**
@brief Inserta una clave en el árbol si no existe en su lugar correspondiente
@param [in,out] tree Puntero al árbol donde se quiere insertar la clave
@param[in] key Clave que se quiere insertar
@retval true Se insertó correctamente o ya existía
@retval false No se realizó la inserción
@post El árbol incorpora un nuevo nodo si este se ha podido insertar y no existía
*/
bool insertKey(tBST *tree, tKey key);
//Observadoras
/**
@brief Devuelve el subárbol izquierdo.
@param[in] tree Árbol no vacío.
@return Subárbol izquierdo, o NULLBST si no existe.
@pre El árbol no debe ser NULLBST.
*/
tBST leftChild(tBST tree);
/**
@brief Devuelve el subárbol derecho.
@param[in] tree Árbol no vacío.
@return Subárbol derecho, o NULLBST si no existe.
@pre El árbol no debe ser NULLBST.
*/
tBST rightChild(tBST tree);
/**
@brief Devuelve la clave del nodo raíz.
@param[in] tree Árbol no vacío.
@return Clave del nodo raíz.
@pre El árbol debe contener al menos un nodo.
*/
tKey root(tBST tree);
/**
@brief Comprueba si el árbol está vacío.
@param[in] tree Árbol a evaluar.
@retval true Si es NULLBST.
@retval false Si contiene al menos un nodo.
*/
bool isEmptyTree(tBST tree);
/**
@brief Busca una clave en el árbol.
@param[in] tree Árbol donde buscar.
@param[in] key Clave a localizar.
@return Subárbol cuya raíz contiene la clave, o NULLBST si no se encuentra.
*/
tBST findKey(tBST tree, tKey key);
// Destructoras
/**
@brief Elimina un nodo del árbol si contiene la clave.
@param[in,out] tree Puntero al árbol.
@param[in] key Clave a eliminar.
@retval true Si se eliminó con éxito.
@retval false Si la clave no estaba en el árbol.
@post El árbol no contiene la clave eliminada.
*/
bool removeKey(tKey key, tBST *tree);
Implementación en C
void createEmptyTree(tBST *tree) {
*tree = NULLBST;
}
static bool createBSTNode(tBST *node, tKey key) {
*node = malloc(sizeof(struct tNodeBST));
if (*node!=NULLBST)
{
(*node)->key = key;
(*node)->left = NULLBST;
(*node)->right = NULLBST;
}
return *node!=NULLBST;
}
bool insertKey(tBST *tree, tKey key) {
if (isEmptyTree(*tree)) { //caso raíz: el árbol está vacío
return createBSTNode(tree, key);
}
if (key < (*tree)->key) { //caso subárbol izquierdo: la clave es menor
return insertKey(&(*tree)->left, key);
}
if (key > (*tree)->key) { //caso subárbol derecho: la clave es mayor
return insertKey(&(*tree)->right, key);
}
if (key == (*tree)->key) { // caso clave duplicada
return true;
}
}
tBST findKey(tBST tree, tKey key) {
if (isEmptyTree(tree)) {
return NULLBST; // caso árbol vacío
} else if (key == tree->key) {
return tree; // caso raíz: clave encontrada
} else if (key < tree->key) {
return findKey(tree->left, key); // caso subárbol izquierdo
} else {
return findKey(tree->right, key); // caso subárbol derecho
}
}
tBST leftChild(tBST tree) {
return tree->left; // subárbol izquierdo
}
tBST rightChild(tBST tree) {
return tree->right; // subárbol derecho
}
tKey root(tBST tree) {
return tree->key; // valor de la raíz
}
bool isEmptyTree(tBST tree) {
return tree == NULLBST;
}
// Busca el nodo con la menor clave en el subárbol
static tBST findMin(tBST tree) {
while (tree->left != NULLBST) {
tree = tree->left;
}
return tree;
}
// ¡¡La implementación de remove no se va a pedir en el exámen!!
// Elimina el nodo con la clave indicada
bool removeKey(tKey key, tBST *tree) {
if (*tree == NULLBST) {
return false; // caso árbol vacío
}
if (key < (*tree)->key) {
return removeKey(key, &(*tree)->left); // caso subárbol izquierdo
} else if (key > (*tree)->key) {
return removeKey(key, &(*tree)->right); // caso subárbol derecho
} else {
// caso nodo encontrado
tBST temp;
if ((*tree)->left == NULLBST) {
// caso sin hijo izquierdo
temp = *tree;
*tree = (*tree)->right;
free(temp);
} else if ((*tree)->right == NULLBST) {
// caso sin hijo derecho
temp = *tree;
*tree = (*tree)->left;
free(temp);
} else {
// caso dos hijos
temp = findMin((*tree)->right);
(*tree)->key = temp->key;
return removeKey(temp->key, &(*tree)->right);
}
return true;
}
}
---
Inserción de claves
insertKey(&tree, 50);
insertKey(&tree, 30);
insertKey(&tree, 70);
insertKey(&tree, 20);
insertKey(&tree, 40);
insertKey(&tree, 60);
insertKey(&tree, 80);
Árbol tras inserciones:
50
/ \
30 70
/ \ / \
20 40 60 80
Tienes esta construcción paso a paso, además de explicaciones de la eliminación de claves en Ejercicios ABB.
Búsqueda: findKey(tree, 40)
devuelve el nodo con clave 40.
Eliminación de claves
Tienes una explicación de estos ejemplos en este enlace.
Caso 1: Eliminar una hoja
tBST tree;
createEmptyTree(&tree);
insertKey(&tree, 50);
insertKey(&tree, 30);
insertKey(&tree, 70);
insertKey(&tree, 20); // hoja
removeKey(20, &tree);
Árbol tras inserciones:
50
/ \
30 70
/
20
Árbol tras eliminar 20:
50
/ \
30 70
Caso 2: Eliminar un nodo con un solo hijo
insertKey(&tree, 25); // hijo único de 30
removeKey(30, &tree);
Árbol antes:
50
/ \
30 70
\
25
Árbol después:
50
/ \
25 70
Caso 3: Eliminar un nodo con dos hijos
insertKey(&tree, 60);
insertKey(&tree, 80);
removeKey(70, &tree); //tiene hijos 60 y 80
Árbol antes:
50
/ \
25 70
/ \
60 80
Árbol después (sucesor 80 reemplaza 70):
50
/ \
25 80
/
60
Caso 4: Eliminar la raíz con dos hijos
removeKey(50, &tree);
Árbol antes:
50
/ \
25 80
/
60
Árbol después (sucesor 60 reemplaza 50):
60
/ \
25 80
Observaciones clave
- Un ABB puede degenerar a lista si las claves se insertan ordenadas.
- El ABB no es equilibrado automáticamente.