Está en la página 1de 54

Árboles

de búsqueda
Juan Manuel Dodero Beardo

P06/75001/00581
Módulo 7
© FUOC • P06/75001/00581 • Módulo 7 Árboles de búsqueda

Índice

Introducción ............................................................................................ 5

Objetivos ................................................................................................... 6

1. Los árboles de búsqueda .................................................................. 7


1.1. Árboles binarios de búsqueda (ABB) ............................................... 9
1.2. Árboles multicamino de búsqueda ................................................. 10

2. Implementación de colecciones ordenadas


con árboles binarios de búsqueda ................................................ 12
2.1. Árboles binarios de búsqueda ......................................................... 12
2.1.1. Búsqueda en un ABB ........................................................... 13
2.1.2. Inserción en un ABB ............................................................ 14
2.1.3. Supresión en un ABB ........................................................... 16
2.1.4. Complejidad en las búsquedas ............................................ 20
2.2. Árboles binarios equilibrados ......................................................... 21
2.2.1. Algoritmo de equilibrado perfecto ...................................... 23
2.2.2. Cuándo es rentable el equilibrio perfecto ........................... 24
2.2.3. Equilibrio en altura ............................................................. 25
2.3. Árboles AVL .................................................................................... 26
2.3.1. Árboles de Fibonacci ........................................................... 26
2.3.2. Factor de equilibrio ............................................................. 27
2.3.3. Inserción en un árbol AVL .................................................. 27
2.3.4. Supresión en un árbol AVL ................................................. 31
2.3.5. Implementación en la biblioteca de TAD ........................... 33
2.4. Implementación de diccionarios con árboles binarios
de búsqueda .................................................................................... 34
2.5. Implementación de conjuntos con árboles binarios
de búsqueda .................................................................................... 36
2.6. Ejemplo de aplicación: implementación de un paginador ............ 38

3. Árboles multicamino y árboles B ................................................. 41


3.1. Estructura de un árbol B ................................................................. 43
3.2. Inserción en un árbol B .................................................................. 44
3.3. Supresión en un árbol B ................................................................. 44

4. Los árboles de búsqueda en la Java Collections Framework . 47

Resumen .................................................................................................... 48

Ejercicios de autoevaluación ............................................................... 49


© FUOC • P06/75001/00581 • Módulo 7 Árboles de búsqueda

Solucionario ............................................................................................. 51

Glosario ..................................................................................................... 52

Bibliografía .............................................................................................. 52

Anexo ......................................................................................................... 53
© FUOC • P06/75001/00581 • Módulo 7 5 Árboles de búsqueda

Introducción

En este módulo se presentan los árboles binarios de búsqueda como estructuras


enlazadas que mejoran la eficiencia de las búsquedas en colecciones ordenadas
de elementos. También se muestran los árboles multicamino de búsqueda, que
se utilizan cuando hay que almacenar grandes colecciones ordenadas de ele-
mentos en dispositivos de memoria secundaria como los discos.
© FUOC • P06/75001/00581 • Módulo 7 6 Árboles de búsqueda

Objetivos

En los materiales didácticos correspondientes a este módulo se encuentran los


elementos indispensables para alcanzar los siguientes objetivos:

1. Saber implementar colecciones ordenadas de elementos con árboles bina-


rios de búsqueda.

2. Comprender la utilidad de los árboles binarios de búsqueda para mejorar


la eficiencia en las búsquedas.

3. Conocer los árboles multicamino y comprender su utilidad para mejorar la


eficiencia del almacenamiento de colecciones ordenadas de elementos en
dispositivos de memoria secundaria.
© FUOC • P06/75001/00581 • Módulo 7 7 Árboles de búsqueda

1. Los árboles de búsqueda

En módulos anteriores se han tratado algunas estructuras de datos que sirven


para mejorar la eficiencia de las operaciones de búsqueda en una colección de
elementos. Por ejemplo, las implementaciones del TAD Diccionario basadas en
Podéis ver el TAD Diccionario en el
módulo “El TAD Tabla” de esta
tablas de dispersión permiten alcanzar un coste asintótico muy bueno en las asignatura.

operaciones de búsqueda sobre elementos individuales.

No obstante, cuando se trata de listar todos los elementos de una tabla siguien-
do un orden determinado, el comportamiento de las tablas de dispersión no es
todo lo bueno que sería deseable. Por ello se plantean estructuras de datos alter-
nativas que, manteniendo una complejidad aceptable para las búsquedas indi-
viduales, mejoren la eficiencia de las operaciones de listado sobre una parte
considerable de los elementos de la colección. En este módulo se estudiarán los
árboles de búsqueda como una solución alternativa a éstos y otros problemas.

Los árboles de búsqueda son una variante de los árboles ya estudiados. Los árboles El aprovechamiento de los árboles
como estructura de datos jerárquica se
estudia en el módulo “Árboles”
son estructuras de datos que reflejan relaciones jerárquicas entre sus elementos. de esta asignatura.

Así como las listas son lineales, se suele hablar de los árboles como estructuras
multidimensionales, dado que el número de relaciones jerárquicas entre cada ele-
mento y sus descendientes en el árbol se pueden ver como las distintas dimensio-
nes de una tabla. De hecho, cualquier árbol puede expresarse en forma de tabla
multidimensional, aunque en forma de tabla sea más difícil de dibujar.

Una colección estructurada de datos que puede representarse fácilmente en


forma de árbol es el conjunto de cruces de una competición deportiva por eli-
minatorias, como por ejemplo la de la figura 1. Dicha estructura también pue-
de representarse en forma de tabla, aunque en tal caso es más complicado de
dibujar y resulta menos claro.

Figura 1. Conjunto de eliminatorias de una competición deportiva representadas en forma de árbol


© FUOC • P06/75001/00581 • Módulo 7 8 Árboles de búsqueda

Una segunda utilidad de los árboles es la de guardar colecciones lineales de ele-


mentos ordenados. Este segundo propósito es el motivo de la nueva clase de
árboles que se estudiarán en este módulo. En tal caso se habla de árboles de bús-
queda, precisamente porque su cometido es mejorar la eficiencia de las opera-
ciones de búsqueda que se puede alcanzar con estructuras lineales enlazadas.

Un árbol de búsqueda es un árbol normal cuyos elementos están orde-


nados según uno o más criterios, que constituyen la clave de ordenación.

El orden de los elementos almacenados en los nodos de un árbol de búsqueda


se define del siguiente modo:

• Todos los elementos situados por debajo y a la izquierda de otro deben te-
ner una clave de ordenación menor que éste.

• Todos los elementos situados por debajo y a la derecha de otro deben tener
una clave de ordenación mayor que éste.

Las relaciones jerárquicas entre los elementos del árbol se aprovechan para re-
presentar el orden de los elementos, tal y como ilustra el ejemplo de la figura
2, que refleja un árbol binario de búsqueda que utiliza números enteros como
clave de ordenación. En dicho árbol, todos los nodos situados por debajo y a
la izquierda del nodo 6 son números menores que él (nodos 2, 4 y 5); todos
los nodos situados por debajo y a la derecha del nodo 6 son números mayores
que él (nodos 7, 9 y 11); esta misma condición puede comprobarse para los
nodos 4 y 11. El árbol está ordenado y, por lo tanto, es un árbol de búsqueda.

Figura 2. Ejemplo de árbol binario de búsqueda

Así, es fácil saber cuáles son menores y cuáles mayores a partir de un elemento
cualquiera, y se puede restringir considerablemente el espacio de búsqueda so-
© FUOC • P06/75001/00581 • Módulo 7 9 Árboles de búsqueda

bre una colección de elementos ordenados, de manera parecida a como hace


la búsqueda binaria o búsqueda dicotómica.

Los árboles de búsqueda sirven para guardar estructuras lineales de elementos or- Complejidad asintótica
denados mejorando la complejidad de las búsquedas. Las búsquedas en un árbol
La mejora en la eficiencia que
se realizarán comparando la clave buscada con las claves de los elementos alma- se produce en los árboles de
búsqueda no siempre se tra-
cenados en cada nodo, empezando desde la raíz y descendiendo por el árbol. De- duce en una mejora de la com-
pendiendo de si la clave buscada es mayor o menor que la encontrada en cada plejidad asintótica. Para
asegurar esto, el árbol debe te-
nodo, se elige la rama del árbol por la que se desciende en la búsqueda. ner una cierta forma equilibra-
da que se estudiará más
adelante en este módulo.

Existen muchos tipos de árboles de búsqueda, pero, en general, pueden clasi-


ficarse en dos grandes grupos, según el grado del árbol: los árboles binarios de
búsqueda y los árboles multicamino de búsqueda.

En el resto del módulo se estudiarán estos dos tipos de árboles de búsqueda


por separado, así como el equilibrio en árboles binarios de búsqueda, un pro-
blema fundamental que justifica la utilidad de los árboles como estructuras
eficientes para hacer búsquedas.

1.1. Árboles binarios de búsqueda (ABB)

Sobre repetición de claves


En un árbol binario de búsqueda (en adelante, ABB), cada nodo tiene dos
En principio, los ABB no están
hijos como máximo (es decir, su grado es dos) y, además, se cumple que: pensados para almacenar ele-
mentos repetidos. Sin embargo,
pueden definirse variantes de un
1) El subárbol izquierdo de cualquier nodo sólo puede contener nodos ABB para manejar claves repe-
tidas. Un método típico de ha-
cuya clave es menor, o bien estará vacío. cerlo consiste en asociar a cada
nodo una lista de colisiones
que guarde los elementos con
2) El subárbol derecho de cualquier nodo sólo puede contener nodos clave repetida.
cuya clave es mayor, o bien estará vacío.

Los elementos de un ABB están ordenados de acuerdo con una clave de or-
denación, que puede ser cualquier atributo del elemento. La clave de orde- Podéis ver la clave de ordenación en
el módulo “El TAD Tabla” de esta
asignatura.
nación puede estar constituida por uno o más atributos, como ya sabemos.
En un árbol binario de búsqueda, en principio, no puede haber elementos de
clave repetida, a menos que se amplíe la estructura del árbol con estructuras
adicionales que den soporte a esta circunstancia.

El ejemplo de la figura 3 muestra la diferencia entre un árbol binario normal


y otro de búsqueda. El árbol de la figura 3a no es un árbol de búsqueda porque,
entre otras razones, el nodo 7 es de clave mayor que el nodo 2, pero está situa-
do a su izquierda. Asimismo, el 6 es menor que el 7 y está situado a su derecha,
de manera que no cumple las condiciones de ordenación.
© FUOC • P06/75001/00581 • Módulo 7 10 Árboles de búsqueda

Figura 3. Ejemplos de árbol binario y de árbol binario de búsqueda

Por otro lado, el árbol de la figura 3b sí es un ABB, ya que todos los nodos tie- Árboles binario y binario
de búsqueda
nen por debajo y a su izquierda elementos menores que ellos, y por debajo y
Como los elementos que for-
a su derecha elementos mayores. Para comprobarlo, simplemente hay que ge- man el árbol a no están orde-
nados, este árbol no es de
nerar el recorrido en inorden del árbol: búsqueda.
Como los elementos que for-
• Recorrido en inorden del árbol 3a: {2, 7, 5, 6, 11, 5, 4, 9} man el árbol b están ordena-
dos, este árbol es de búsqueda.
• Recorrido en inorden del árbol 3b: {2, 4, 5, 6, 7, 9, 11}
Como resultado de recorrer un ABB
en inorden se obtiene una
colección ordenada de los
elementos del árbol.
1.2. Árboles multicamino de búsqueda

Los tipos de recorrido de un árbol se


Los árboles multicamino de búsqueda son árboles de búsqueda con gra- estudiaron en el módulo “Árboles”.

do superior a 2. Cada uno de sus nodos sirve para almacenar un conjun-


to ordenado de claves, en vez de una sola, como sucedía con los árboles
binarios. De cada nodo parten varias ramas y forman subárboles.

Estos subárboles tienen sus claves ordenadas como se indica en la figura 4.

Figura 4. Criterio de ordenación de los nodos de un árbol


multicamino de búsqueda
© FUOC • P06/75001/00581 • Módulo 7 11 Árboles de búsqueda

En la figura 4 se muestra un nodo cualquiera de un árbol multicamino con ca-


bida para 5 claves, con nombre desde k1 hasta k5. Los nodos de su primer subár-
bol por la izquierda guardarán sólo claves menores que k1, los del segundo hijo
por la izquierda guardarán claves situadas entre k1 y k2, y así sucesivamente hasta
llegar al hijo del extremo derecho, que guarda las claves mayores que k5.
© FUOC • P06/75001/00581 • Módulo 7 12 Árboles de búsqueda

2. Implementación de colecciones ordenadas


con árboles binarios de búsqueda

Los árboles binarios de búsqueda sirven para almacenar colecciones ordena- Otra posibilidad de los árboles
binarios de búsqueda es proporcionar
una implementación adicional para los
das de elementos y mejorar la complejidad de la operación de búsqueda en diccionarios estudiados en el módulo “El
TAD Tabla” de esta asignatura.
una lista ordenada. Por lo tanto, para estudiar la utilidad de los árboles bina-
rios de búsqueda, éstos se han de usar como implementación de algún TAD
que represente una colección ordenada de elementos (por ejemplo, una lista
ordenada por una cierta clave).

En este apartado se estudiarán los árboles binarios de búsqueda como conte-


nedores de cualquier colección de elementos ordenados por una cierta clave.
En tal caso, no existe diferencia entre la implementación de una lista orde-
nada y la de un diccionario, excepto por el tipo de datos de la clave. Por lo
tanto, en el resto del apartado se hablará de colecciones ordenadas, indepen-
dientemente del tipo concreto de éstas.

2.1. Árboles binarios de búsqueda La complejidad


de las búsquedas
Supongamos que se quiere guardar la colección ordenada de números enteros La complejidad de las bús-
siguientes {2, 6, 7, 9, 10, 12} en forma del árbol de la figura 5. quedas en colecciones orde-
nadas suele variar entre
O(log n) y O(n). Se consigue
Figura 5. Un ejemplo de árbol de búsqueda con complejidad una complejidad O(log n) cu-
cercana a O(log n) ando la implementación de la
colección permite realizar bús-
quedas binarias, como es el
caso de los vectores. Sin em-
bargo, en las implementacio-
nes de listas enlazadas, la
mejor complejidad que se pue-
de alcanzar es de O(n). Los ABB
intentan mejorar esta comple-
jidad.

¿Cuál es el coste de buscar un elemento cualquiera en este árbol? Depende


del tamaño máximo del camino de búsqueda. El camino de búsqueda es la
secuencia de nodos recorridos durante una búsqueda en un ABB. La búsque-
da comenzará comparando el elemento buscado con la raíz y descendiendo
hacia las hojas. Tras cada comparación, progresamos por la rama de la iz-
quierda o por la de la derecha, pero nunca por ambas. La búsqueda se deten-
drá cuando encontremos el elemento, o si llegamos a una hoja y el elemento
buscado no está en el árbol.

Intuitivamente puede comprobarse que, como máximo, será necesario hacer 3


operaciones de comparación (es decir, el tamaño máximo del camino de bús-
© FUOC • P06/75001/00581 • Módulo 7 13 Árboles de búsqueda

queda) para encontrar cualquier elemento en el árbol del ejemplo. Dado que un
árbol binario de esa altura puede contener como máximo n = 7 nodos, la com-
plejidad será aproximadamente log n.

Sin embargo, este coste no se alcanza en todas las situaciones, sino que depende
de la “forma” del árbol. Por ejemplo, el árbol de la figura 6 contiene los mismos
elementos que el anterior, pero realizar una búsqueda puede necesitar hasta 5
comparaciones (es decir, el tamaño máximo del camino de búsqueda). En este
caso, la complejidad es más cercana a O(n) que a O(log n)

Figura 6. Un ejemplo de árbol de búsqueda, con los mismos


elementos de la figura 5, pero con una complejidad mayor,
cercana a O(n)

En resumen, para garantizar que los ABB alcancen la mejora deseada en la


complejidad de las búsquedas, es necesario exigirle al árbol una cierta forma
“equilibrada”. En un árbol de n nodos que tenga una forma equilibrada pare-
cida a la del árbol de la figura 5, la complejidad de las búsquedas será aproxi-
madamente O(log n).

A continuación se describirán las operaciones más importantes del TAD Ar-


bolBinarioBusqueda más importantes: la búsqueda, la inserción y la elimina-
ción. Finalmente se hará una breve disquisición sobre la complejidad de las
búsquedas en un ABB.

2.1.1. Búsqueda en un ABB

La búsqueda en un ABB consiste en comparar el elemento buscado con los


nodos del árbol, empezando por la raíz y descendiendo hacia las hojas. Tras
cada comparación, se avanza por la rama izquierda o por la derecha, pero nun-
ca por ambas. La búsqueda se detendrá cuando se encuentre el elemento, o
bien cuando se llegue a una hoja sin haberlo encontrado (el elemento buscado
no está en el árbol).
© FUOC • P06/75001/00581 • Módulo 7 14 Árboles de búsqueda

El algoritmo de búsqueda del elemento buscado en el árbol que tiene la raíz en


posicion puede describirse de la siguiente manera:

1) Mientras la Posicion no sea nula:

a) Comparar el elemento en Posicion y el elemento buscado.

• Si ambos son iguales, el elemento en Posicion es el elemento buscado y la bús-


queda se detiene.
• Si el elemento en Posicion es menor, se ha de actualizar Posicion para que
apunte a su hijo de la derecha.
• Si el elemento Posicion es mayor, se debe actualizar Posicion para que apunte
a su hijo de la izquierda.

b) Continuar la búsqueda.

2) Si el algoritmo ha terminado porque Posicion es nula, el elemento no se ha-


lla en el árbol y la búsqueda ha sido infructuosa.

La búsqueda progresa por uno de los dos lados de la posición de partida, pero no Búsqueda en un ABB
por ambos. En caso de que el elemento buscado sea menor que la raíz de dicha
De manera similar a como se
posición, sabemos que no estará en el subárbol derecho, así que podemos descar- hacía en la búsqueda binaria o
dicotómica sobre un vector or-
tar todos sus elementos y sólo hemos de buscar en el izquierdo; sucede lo mismo denado, en la búsqueda en un
con el subárbol izquierdo si el elemento buscado es mayor que la raíz. ABB también somos capaces
de descartar una parte de los
elementos para delimitar el es-
El algoritmo de la operación de búsqueda, abstraído de la operación consultar pacio de búsqueda.
La diferencia radica en el
disponible en la implementación del TAD ArbolBinarioBusquedaEncadenado, es hecho de que en la búsqueda
el siguiente: binaria se usan índices enteros
que delimitan el espacio de
búsqueda en el vector y, en
uoc.ei.tads.ArbolBinarioBusquedaEncadenado cambio, en un ABB se emplean
los hijos izquierdo y derecho
para descartar los subárboles
en los que es seguro que no se
Elem consultar(Posicion padre, Elem buscado) { halla el elemento.
boolean encontrado = false;
int comp;
while ( padre!=null && !encontrado ) {
comp = comparar(buscado, padre.getElem());
encontrado = comp==0;
if (comp <0) padre = hijoIzquierdo(padre);
else if (comp >0) padre = hijoDerecho(padre);
}
return encontrado ? padre.getElem() : null;
}

2.1.2. Inserción en un ABB

La inserción debe garantizar que el árbol resultante siga siendo un ABB, es de-
cir, que sus nodos continúen ordenados. Para ello, la inserción comienza bus-
cando la posición en la que debe insertarse el nuevo elemento. Si la posición
© FUOC • P06/75001/00581 • Módulo 7 15 Árboles de búsqueda

en la que se ha de insertar ya está ocupada por otro elemento con la misma


clave, éste será sustituido. En cambio, si la posición donde hay que insertar
está vacía, el elemento se añadirá como una hoja nueva.

Primero hay que buscar la posición en la que se ha de insertar el nuevo elemento.


Si el árbol está vacío, el elemento se coloca en su raíz. Si no está vacío, se desciende
por el árbol “visitando” sus hijos y comparándolos con el nuevo elemento, y se
decide para cada nodo visitado en qué lugar hay que insertar. Hay tres casos:

• Si el nuevo elemento es igual al del nodo visitado, se sustituye el contenido Repetición de elementos
del nodo por el nuevo elemento y la inserción termina.
Tal como ya hemos comenta-
do, los ABB no están diseñados
• Si el nuevo elemento es menor que el del nodo visitado, se desciende por para almacenar elementos re-
petidos. Por ello, en caso de
su izquierda. igualdad del elemento por in-
sertar con alguno del árbol, se
opta por sustituir este último.
• En cambio, si el nuevo elemento es mayor, se desciende por su derecha. Sin embargo, es posible cam-
biar este comportamiento y
La búsqueda se detiene si se llega al primer caso, o bien se alcanza un nodo hacer que el nuevo elemento
se acumule en una lista de coli-
vacío, que será justo la posición en la que hay que insertar el nuevo elemento. siones asociada al nodo, y así
poder almacenar las repeticio-
Una vez encontrada la posición, simplemente se inserta el elemento como un nes.
nodo nuevo y el algoritmo finaliza.

El algoritmo de la operación de inserción, abstraído de la operación insertar


disponible en la implementación del TAD ArbolBinarioBusquedaEncadenado, es
el siguiente:

Posicion<E> insertar (Posicion<E> padre, E elem) {


if (padre == null) return super.insertar(padre, elem);
Posicion<E> nodo = null;
int comp = comparar(elem, padre.getElem());
if (comp == 0) {
super.sustituir(padre, elem);
nodo = padre;
}
else if (comp <0) {
if (hijoIzquierdo(padre) == null)
nodo = super.insertarHijoDerecho(padre, elem);
else
nodo = insertar(hijoIzquierdo(padre), elem);
}
else {
if (hijoDerecho(padre) == null)
nodo = super.insertarHijoDerecho(padre, elem);
else
nodo = insertar(hijoDerecho(padre), elem);
}
equilibrar(padre);
return nodo;
}
© FUOC • P06/75001/00581 • Módulo 7 16 Árboles de búsqueda

En realidad, la inserción no supone más que una operación de búsqueda se-


guida de la sustitución de un nodo, o bien la creación del nuevo nodo y la in-
tegración con el árbol.

De momento, no nos hemos preocupado del equilibrio de los ABB. Sin embar-
go, observad cuándo hay que equilibrar el árbol tras cada inserción: en la llama-
da a la operación equilibrar(padre). La operación de inserción puede provocar la
aparición de un nuevo nodo en el árbol. Si se producen muchas inserciones, y
todas ellas van a parar a la misma zona del árbol, es posible que crezcan más
unas ramas que otras y el árbol acabe desequilibrándose. Recordad que es desea-
ble mantener el árbol equilibrado para que las búsquedas se mantengan con una
complejidad más cercana a logarítmica que a lineal. Más adelante estudiaremos
las operaciones de equilibrado, que serán necesarias después de insertar o borrar
elementos de un ABB.

Ejemplo

En el ejemplo de la figura 7 se inserta el elemento de clave 16 en el árbol de la izquierda


para obtener el árbol de la derecha. El proceso se realiza de forma similar a la búsqueda.
Como el elemento 16 no se encuentra en el árbol, se inserta como hijo izquierdo del úl-
timo elemento visitado, que es el 18, por ser menor que éste.

Figura 7. Ejemplo de inserción en un ABB

2.1.3. Supresión en un ABB

La supresión de un elemento en un ABB consiste en buscar la posición del padre


del nodo ocupado por dicho elemento y, a continuación, eliminar del padre la
referencia al nodo encontrado. Esta manera de proceder tiene una excepción:
cuando el nodo que se borra es la raíz, además hay que actualizar la referencia a
ésta en el árbol.
© FUOC • P06/75001/00581 • Módulo 7 17 Árboles de búsqueda

Para describir más detalladamente la operación de supresión, se dividirá en


dos fases. En la primera fase, se buscará el nodo del elemento que se borra; y,
en la segunda, se eliminará o sustituirá el nodo encontrado. Durante estas dos
fases hay que tener en cuenta algunas cuestiones:

a) La supresión no siempre se acaba con una simple eliminación del nodo con-
tenedor del elemento encontrado, pues puede suceder que dicho nodo ocupe
una posición intermedia en el árbol, y su simple eliminación también elimina-
ría todos los nodos que cuelgan de él.

b) La supresión de un nodo a quien realmente afecta es al padre de dicho


nodo, pues una vez hallado el nodo que se elimina, hay que actualizar la refe-
rencia que su padre tiene de él.

c) Cuando se actualiza la mencionada referencia del padre del nodo borrado,


hay que conocer si el nodo borrado es su hijo izquierdo o derecho, pues el pa-
dre tiene dos referencias y hay que saber cuál hay que actualizar. Esto es fácil
de averiguar si se tiene en cuenta que el árbol es de búsqueda y, por tanto, sus
elementos deben estar ordenados. Entonces sólo será necesario comparar el ele-
mento borrado con la clave del padre, y se sabrá si éste ocupaba el nodo a su
izquierda (la clave del elemento es menor que la del padre) o a su derecha (la
clave es mayor).

1) Fase de búsqueda

El algoritmo de supresión de un elemento comienza realizando una operación


de búsqueda del elemento en el árbol. Si el árbol está vacío, no es posible bo-
rrar ningún nodo y, en consecuencia, la operación de supresión finaliza. Si el
árbol no está vacío, hay que descender por el árbol visitando cada nodo por
una de las dos ramas. Se avanza por la rama izquierda o la derecha según re-
sulte de comparar el elemento que se borra con la clave de la raíz. Para ello hay
tres casos:

a) Si el elemento que se borra es igual al del nodo visitado, se detiene la bús-


queda y se guarda la posición del padre de dicho nodo.

b) Si el elemento que se borra es menor que el del nodo visitado, se baja por
su izquierda.

c) En cambio, si el elemento que se borra es mayor, se baja por su derecha.

La búsqueda se detiene si se alcanza el primer caso, o se llega a un nodo vacío


en algún punto del descenso por el árbol.
© FUOC • P06/75001/00581 • Módulo 7 18 Árboles de búsqueda

2) Fase de eliminación

Si en la búsqueda de la fase anterior se alcanzó un nodo vacío, es que el ele-


mento que se quiere borrar no se encontraba en el árbol, y la supresión termi-
na infructuosamente. En cambio, si la búsqueda se detiene en una posición no
nula, entonces es necesario borrar el nodo que ocupa aquella posición no nu-
la. Esto afectará al padre del nodo, y pueden producirse tres casos, que comen-
tamos a continuación de menor a mayor complejidad:

a) Que el nodo no tenga ningún hijo (es decir, es una hoja): entonces simple-
mente se elimina la referencia al nodo que guarda el padre.

b) Que el nodo sólo tenga un hijo (es totalmente indiferente que sea el iz-
quierdo o el derecho, pero no ambos a la vez): entonces se actualiza la referen-
cia en el padre para que guarde la del “nieto”, y se salte el hijo que se quiere
eliminar

c) Que el nodo tenga dos hijos no vacíos. Éste es el caso más complicado, pues
no se puede elegir simplemente uno de los dos hijos como sustituto, como en
el caso anterior (puede ser que el sustituto tenga también dos hijos y no ten-
dríamos sitio para colocarlos). En tal caso, hay que buscar el sustituto ideal. Si
se piensa en que el ABB guarda una colección ordenada de elementos, hay dos
sustitutos posibles: el elemento inmediatamente anterior o el posterior en di-
cho orden. En consecuencia, en este caso la supresión debe avanzar hacia la
subfase que consiste en localizar el mencionado sustituto con el que reempla-
zar el nodo borrado.

3) Subfase de búsqueda de sustituto

La búsqueda de sustituto tiene dos posibilidades:

a) Buscar el elemento inmediatamente anterior, que ocupará la posición del


elemento mayor del subárbol izquierdo.

b) Buscar el elemento inmediatamente posterior, que ocupará la posición del


elemento menor del subárbol derecho.

Cualquiera de las dos posibilidades es válida, pero sólo se puede elegir una de
ellas. Por ejemplo, si se opta por la primera opción, hay que buscar el elemento
mayor del subárbol izquierdo. Esta búsqueda consiste simplemente en bajar
por la rama derecha de todos los descendientes, hasta que se encuentre un
nodo sin hijo derecho. Una vez encontrado, dicho nodo será el sustituto, y
sólo queda que reemplace el nodo que se quiere borrar.
© FUOC • P06/75001/00581 • Módulo 7 19 Árboles de búsqueda

El algoritmo de la operación de supresión, abstraído de la operación borrar dis-


ponible en la implementación del TAD ArbolBinarioBusquedaEncadenado, es el
que se muestra a continuación:

E borrar ( Posicion<E> donde, Posicion<E> padre, E elem ) {

E elemBorrado=null;
if (padre == null) return null;
int comp = comparar(elem, padre.getElem());
if (comp <0)
elemBorrado = borrar(padre, hijoIzquierdo(padre), elem);
else if (comp >0)
elemBorrado = borrar(padre, hijoDerecho(padre), elem);
else {
elemBorrado = padre.getElem();
if (esHoja(padre))
super.borrar(donde, padre);
else if ((hijoIzquierdo(padre)!=null) &&
(hijoDerecho(padre)==null) )
sustituir(donde, padre, hijoIzquierdo(padre));
else if ((hijoIzquierdo(padre)==null) &&
(hijoDerecho(padre)!=null) )
sustituir(donde, padre, hijoDerecho(padre));
else {
Posicion<E> masPequeno = hijoDerecho(padre);
while (hijoIzquierdo(masPequeno)!=null)
masPequeno=hijoIzquierdo(masPequeno);
intercambiar(padre, masPequeno);
borrar(padre, hijoDerecho(padre), masPequeno.getElem());
}
}
equilibrar(donde);
return elemBorrado;
}

Como con la operación de inserción, la supresión puede provocar un desequi- Podéis ver el equilibrado de árboles
en el subapartado 2.2 de este
librio en el árbol que habrá que arreglar. Para ello se ha situado oportunamen- módulo didáctico.

te la llamada a equilibrar una vez terminada la labor de borrar. Este método se


define en subclases de ArbolBinarioBusquedaEncadenado, donde se añade al
comportamiento de árbol de búsqueda el de árbol equilibrado.

Ejemplo

En el árbol de la figura 8a se ha borrado el elemento 12, que constituía un nodo hoja, y


se ha obtenido el árbol de la figura 8b; posteriormente, sobre el árbol de la figura 8b se
ha eliminado el elemento 10 (que además era la raíz). En tal caso hay dos posibilidades:
sustituirlo por el 8 (figura 8c) o sustituirlo por el 13 (figura 8d). En ambos casos resulta-
rían ABB distintos, pero igualmente válidos.
© FUOC • P06/75001/00581 • Módulo 7 20 Árboles de búsqueda

Figura 8. Ejemplos de supresiones en un ABB

2.1.4. Complejidad en las búsquedas

Este módulo ha comenzado afirmando que los ABB sirven para guardar colec- Recordad que para la medida de la
complejidad asintótica se tiene en
cuenta el peor de los casos (podéis ver el
ciones ordenadas de elementos, pero mejorando la eficiencia de las búsque- módulo “Complejidad algorítmica” de esta
asignatura).
das. Esto no siempre se traduce en una mejora de la complejidad asintótica de
las operaciones de búsqueda. A continuación se verá el porqué de este hecho.

Al medir la complejidad asintótica de una búsqueda en un ABB que contiene


n nodos, puede observarse que, en general, no es necesario recorrerlos todos.
En cada nodo que se recorre, hay que decidir si se progresa por la rama izquier-
da o por la derecha, y así se descartan muchos de los nodos. Cuantos más
nodos se descarten, más eficiente será la búsqueda. Entonces la pregunta es:
¿cuántos nodos se descartan? Estudiemos dos casos por separado:

1) En el mejor de los casos, en cada comparación se descartará la mitad de los


nodos que quedan por procesar. En este caso, el árbol tendría una forma equi-
© FUOC • P06/75001/00581 • Módulo 7 21 Árboles de búsqueda

librada similar a la de la figura 5. La complejidad sería de O(log n) porque sólo


Complejidad del mejor
sería necesario hacer tantas operaciones de comparación como número de ni- y del peor de los casos
veles tenga el árbol. En el caso de un árbol binario
completo, si h es la altura de
un ABB y n es el número de
2) El peor de los casos se produce cuando hay que recorrer todos los elemen- nodos que contiene, se puede
demostrar que n = 2h +1 −1, es
tos del árbol, es decir, cuando éste ha degenerado en algo parecido a una lista, decir h = log2(n + 1) − 1.
como es el caso de la figura 6. En este caso, la complejidad sería O(n), es decir, Por tanto, el mejor de los casos
(sin tener en cuenta la
no se mejora nada con respecto a la búsqueda en una lista enlazada normal. posición del nodo buscado) se-
ría cuando h = log2 n. Y por ello
la complejidad en el mejor de
los casos es O(log n).
Observad que un árbol puede llegar a tener una forma más parecida al mejor o
En cambio, el peor de los casos
al peor caso dependiendo del orden en el que se inserten sus elementos, siguien- se producirá para el valor ma-
yor que h puede tomar, es de-
do el algoritmo visto en el anterior apartado. Por ejemplo, si sobre un árbol va- cir, h = n, y se obtendrá una
complejidad de O(n).
cío llega una secuencia de inserciones en el orden {9, 6, 2, 10, 7, 12}, se obtiene
el árbol de la figura 5. En cambio, si llega la secuencia {10, 12, 9, 7, 6, 2}, se ob-
tiene el árbol de la figura 6. Dada la aleatoriedad con la que pueden llegar los
elementos que se insertan en el árbol, no se puede asegurar con certeza si ten- Recordad que la medida de la
complejidad que propone la notación
O(·) se toma en el caso peor.
dremos una situación parecida a uno u otro extremo. Por este motivo, hay que
pensar en el peor de los casos. Por lo tanto, para poder asegurar que la comple-
jidad asintótica de las búsquedas es O(log n), hay que procurar que la estructura
del ABB se parezca tanto como sea posible a la de la figura 5, es decir, mantener
el árbol equilibrado tras cada operación de inserción o de supresión.

En el siguiente subapartado se tratará con mayor detalle la cuestión del equi-


librado en ABB, que afecta especialmente a la implementación de las operacio-
nes insertar y borrar vistas anteriormente.

2.2. Árboles binarios equilibrados

Un árbol binario está equilibrado cuando, en términos generales, todos


sus nodos están equitativamente repartidos a lo largo de sus ramas.
Gráficamente, un árbol equilibrado mantiene una cierta simetría en el
tamaño de todas sus ramas, a cualquier altura del árbol. Dicho de otro
modo, las longitudes de las ramas izquierda y derecha de cualquier
nodo no son muy distintas

El equilibrio en un ABB es una cualidad deseable para que la complejidad


asintótica de las búsquedas se vea realmente mejorada. Mantener un árbol
siempre equilibrado es una labor costosa, ya que hay que asegurar que éste si-
gue estando equilibrado tras cada inserción y cada supresión.

Estudio de Wirth

En la obra Algoritmos + estructuras de datos = Programas (1976), N. Wirth demostró analí-


ticamente que la longitud media del camino de búsqueda en un ABB aleatorio es 1,386
veces la del camino de búsqueda en uno perfectamente equilibrado.
© FUOC • P06/75001/00581 • Módulo 7 22 Árboles de búsqueda

Dicho de otro modo, la búsqueda en un ABB aleatorio exige como media un 39% más de
comparaciones que en uno perfectamente equilibrado. La mejora puede ser incluso ma-
yor en el caso más desfavorable en el que un ABB sin equilibrar degenera en una lista.

A la hora de equilibrar un árbol, hay varias posibilidades. El mejor caso es el


denominado equilibrio perfecto. Pero también hay otros niveles de equilibrio
que, sin ser perfectos, proporcionan una eficiencia aceptable, y son menos
costosos de implementar. A continuación se describirán dos de estos tipos de
equilibrio: el equilibrio perfecto y el equilibrio en altura.

Se dice que un ABB está perfectamente equilibrado si, para cada nodo,
el número de nodos de sus subárboles izquierdo y derecho difiere como
máximo en una unidad.

La figura 9 muestra algunos ejemplos de árboles perfectamente equilibrados y


árboles sin equilibrio perfecto.

Figura 9. Ejemplos de árboles binarios equilibrados y sin equilibrar


© FUOC • P06/75001/00581 • Módulo 7 23 Árboles de búsqueda

2.2.1. Algoritmo de equilibrado perfecto

El algoritmo para equilibrar un árbol binario consiste en desplazar la mitad de los


nodos que sobran de un lado hacia el otro de un nodo. Hay que aplicar la opera-
ción a todos los subárboles, comenzando por la raíz y siguiendo hacia las hojas.

Figura 10. Ejemplo del proceso de equilibrado perfecto de un árbol no equilibrado

Para describir la operación equilibrar, nos basaremos en otras tres operaciones


auxiliares definidas sobre los nodos del árbol:

• NodoArbol.desplazarDerecha(int cuantos): desplaza tantos nodos como indi-


que el valor de cuantos hacia el subárbol derecho del nodo.

• NodoArbol.desplazarIzquierda(int cuantos): desplaza tantos nodos como in-


dique el valor de cuantos hacia el subárbol izquierdo del nodo.
© FUOC • P06/75001/00581 • Módulo 7 24 Árboles de búsqueda

• NodoArbol.equilibrar(): equilibra un nodo, es decir, hace tantos desplaza-


mientos de nodos a izquierda y/o derecha como sean necesarios para que
quede equilibrado el subárbol que cuelga del nodo.

En primer lugar se verá mediante un ejemplo en qué consiste la operación de


equilibrado, y luego pasaremos a describirla más detalladamente.

En la figura 10 se muestra un ejemplo de cómo se consigue el equilibrio per-


fecto de un árbol. Inicialmente, el árbol no está equilibrado porque la distri-
bución de las claves hijas de la raíz está descompensada (hay 5 a la izquierda
y 1 a la derecha). El proceso comienza intentando resolver este primer desequi-
librio, para lo cual hay que traspasar dos o tres claves a la derecha. Se trasladan
sólo dos, ya que resulta menos costoso; éstas serán las claves 10 y 12, porque
son las mayores por la izquierda. De ellas, la clave 10 quedará como nueva raíz.
A continuación, hay que aplicar recursivamente el criterio de equilibrio perfecto
a los subárboles hijos del árbol resultante, tomando como bases las claves 6 y
20, respectivamente. El proceso termina cuando se desplazan la clave 5 (la ma-
yor de las menores que 6) en el subárbol de la izquierda y la clave 15 (la mayor
de las menores que 20) en el subárbol de la derecha.

En cada desplazamiento de un nodo hacia el subárbol derecho se ha trasladado


el nodo de clave mayor del subárbol izquierdo, y éste ha pasado a ocupar la raíz
del nuevo subárbol. A la vez, la antigua raíz del subárbol se coloca en el lugar
que le corresponda según su clave en el subárbol derecho. La colocación de la
raíz en su nueva ubicación se realiza de manera parecida a la operación normal
de inserción de un nodo en un ABB.

Aunque en el ejemplo anterior sólo han hecho falta desplazamientos a la dere-


cha para equilibrar el árbol, también pueden producirse hacia la izquierda. En
tal caso, la operación es simétrica: en cada desplazamiento a la izquierda se tras-
ladará el nodo menor del subárbol derecho, y éste ocupará la raíz del nuevo
subárbol y hará bajar por la izquierda a la antigua raíz.

2.2.2. Cuándo es rentable el equilibrio perfecto

El equilibrio perfecto es una condición bastante costosa de exigir, pues aunque


mejora la eficiencia en las búsquedas, empeora las actualizaciones. Hay que apli-
car el algoritmo de equilibrado cada vez que se inserta o se elimina algún nodo en
el árbol. Y el algoritmo de equilibrado no es trivial ni tiene complejidad constante.

Por lo tanto, la mejora que supone mantener un árbol equilibrado no se con-


sidera suficientemente buena excepto si se da alguno de los siguientes casos:

• Cuando el caso más desfavorable se presenta con asiduidad. Por ejemplo,


cuando los nodos que se quieren insertar en el árbol llegan en secuencias
ordenadas de una cierta longitud. Esto provocaría que el árbol quedase
muy degenerado con frecuencia.
© FUOC • P06/75001/00581 • Módulo 7 25 Árboles de búsqueda

• Cuando el número de búsquedas es muy grande en relación con el número


de actualizaciones. En este caso, el coste de equilibrado adicional impuesto
por las inserciones y supresiones no es tan alto porque estas operaciones
son poco frecuentes.

2.2.3. Equilibrio en altura

El equilibrio perfecto es una condición bastante estricta que hay que exigir
a un ABB para conseguir una complejidad en las búsquedas de O(log n).
Cuando el número de actualizaciones es muy grande en comparación con las
búsquedas, habrá que ejecutar el algoritmo de equilibrado perfecto muchas
veces. En estas situaciones, la complejidad de O(log n) que tiene cada inserción
o supresión empeora, pues hay que sumarle la complejidad de la operación de
equilibrado, que es O(n).

Sin embargo, hay condiciones más laxas de equilibrio que pueden exigírsele a
un ABB para conseguir la mejora pretendida de eficiencia en las búsquedas, sin
por ello empeorar en demasía las operaciones de actualización. Uno de estos
casos es el denominado equilibrio en altura.

Un árbol está equilibrado en altura cuando, para cada uno de sus no-
dos, las alturas de sus subárboles izquierdo y derecho difieren como má-
ximo en una unidad.

Ejemplo

El árbol de la figura 11 está equilibrado en altura, ya que la diferencia de alturas entre las
ramas izquierda y derecha no supera la unidad en ningún subárbol.

Figura 11. Árbol equilibrado en altura

Cuando un árbol está equilibrado en altura, la longitud del camino de búsque-


da es menor incluso en el caso peor, debido a que la condición de equilibrio
impone ciertas restricciones al crecimiento desordenado del árbol y evita ha-
cer que degenere en una lista.
© FUOC • P06/75001/00581 • Módulo 7 26 Árboles de búsqueda

2.3. Árboles AVL

Los árboles AVL son un tipo particular de árboles binarios de búsqueda equilibra-
dos en altura. La definición de equilibrio en altura se debe a G. M. Adelson-Velskii
y E. M. Landis, en cuyo honor estos árboles se conocen como árboles AVL.

Observación
Un árbol AVL es un árbol binario de búsqueda en el que las alturas de los
Fijaos en que la diferencia má-
subárboles de cualquier nodo difieren como máximo en una unidad. xima entre los subárboles iz-
quierdo y derecho en un AVL
se da en su altura, no en el nú-
mero de nodos, que puede ser
La ventaja principal que se obtiene de los árboles AVL es que las operaciones mayor.

de equilibrado son más sencillas y, sobre todo, eficientes. La propia estructu-


ra de los árboles AVL provoca que la longitud media del camino de búsqueda
sea similar a la de los árboles perfectamente equilibrados. La comprobación
de por qué la complejidad de la búsqueda en un AVL es inferior a O(n) se hará
sobre el tipo de árbol AVL más desequilibrado que se pueda definir: los llama-
dos árboles de Fibonacci.

2.3.1. Árboles de Fibonacci

Los árboles de Fibonacci son un tipo de árbol AVL, cuyo nombre proviene de Árboles de Fibonacci
la manera en que se construyen, similar a los conocidos números de Fibonacci.
Los árboles de Fibonacci son
los AVL más desequilibrados
La definición de árbol de Fibonacci es la siguiente: a que existen porque, exceptu-
ando las hojas, todos los
demás nodos presentan un
1) El árbol vacío es un árbol de Fibonacci de altura 0 y se denota por A0. desequilibrio exacto de una
unidad (bien a la izquierda o
bien a la derecha).
2) Un único nodo es un árbol de Fibonacci de altura 1 y se denota por A1.

3) Si Ah-1 y Ah-2 son árboles de Fibonacci de alturas h − 1 y h − 2, respectivamente,


entonces el árbol Ah resultante de anexar Ah-1 y Ah-2 como hi-
jos izquierdo y derecho de un nodo raíz es árbol de Fibo- Figura 12. Árboles de Fibonacci de distintas alturas

nacci de altura h.

4) No hay más árboles de Fibonacci que los construidos


como indican las reglas anteriores.

La figura 12 muestra los árboles de Fibonacci de distintas al-


turas, desde 0 hasta 4. Fijaos en que el árbol de altura h se
construye anexando los dos árboles de alturas h −1 y h − 2
bajo una raíz común. Esto impide que el árbol pueda crecer
mucho más por cualquier rama izquierda que por la derecha:
la diferencia de alturas entre ambas siempre será como máxi-
mo una unidad.

La complejidad de las búsquedas en un árbol AVL dependerá de la altura máxi-


ma que pueda alcanzar dicho árbol, es decir, O(h), donde h es la altura del árbol.
En los árboles de Fibonacci, se puede demostrar (Rosen, 2004, cap. 3) que el nú-
© FUOC • P06/75001/00581 • Módulo 7 27 Árboles de búsqueda

mero de nodos n y la altura del árbol están relacionadas mediante la siguiente


inecuación:

n ≤ 2h+1 − 1

Tomando logaritmos en ambos lados de la desigualdad:

log n ≤ h +1

Por lo tanto, la complejidad de las búsquedas en un árbol de Fibonacci será O(log


n). Como los árboles de Fibonacci son los AVL más desequilibrados que existen,
la clase general de los árboles AVL no hará sino mejorar esta complejidad.

2.3.2. Factor de equilibrio

El único elemento añadido a la estructura de datos de un árbol AVL con res-


pecto a otros ABB es la medida del factor de equilibrio. Todos los nodos de un
árbol AVL tienen un campo entero que mide el factor de equilibrio.

El factor de equilibrio de un nodo es la diferencia entre las alturas de Medida del factor
sus subárboles derecho e izquierdo: de equilibrio

Por convenio, el factor de


Fe(n) = h(hijoIzquierdo(n)) − h(hijoDerecho(n)) equilibrio se mide como la di-
ferencia entre las alturas de los
subárboles derecho e izquier-
do, aunque podría haberse
elegido la medida contraria.
El factor de equilibrio de cualquier nodo de un AVL es un valor entero com-
Si el factor de equilibrio de un
prendido entre −1 y +1. Cuando el valor del factor de equilibrio se salga de ese nodo vale −1, el subárbol iz-
quierdo será un nivel mayor que
rango, habrá que realizar alguna operación adicional de equilibrado. La ven- el derecho; si vale +1, el subár-
bol derecho será un nivel más
taja de los AVL radica en el hecho de que el coste de esta operación de equili- alto; y si vale 0, ambos subárbo-
brado es constante y no depende del tamaño del árbol. les tendrán la misma altura.

El factor de equilibrio de un nodo hay que actualizarlo con cada inserción o su-
presión que se haga en alguno de sus subárboles. Esto se realizará en puntos con-
cretos de los algoritmos de inserción y supresión, como se verá más adelante.

2.3.3. Inserción en un árbol AVL

La operación de inserción en un árbol AVL comienza con el mismo algoritmo


que en los ABB regulares. En cuanto se decide el lugar en el que se produce la
inserción, hay que actualizar el factor de equilibrio del padre que lo acoge y
comprobar si su factor de equilibrio se sale de los límites permisibles:

• Si se sale, se realiza una operación de rotación que vuelva a equilibrar el ár-


bol y la operación termina.

• Si no se sale, se propaga la variación del factor de equilibrio hacia arriba en


la jerarquía del árbol. Esta propagación acaba en cuanto se detecta y se arre-
gla un desequilibrio, o bien cuando se alcanza la raíz del árbol.
© FUOC • P06/75001/00581 • Módulo 7 28 Árboles de búsqueda

Por supuesto, si el árbol estaba vacío y el nodo insertado se convierte en raíz,


también hay que actualizar la referencia correspondiente a la raíz del árbol.

La inserción en un árbol AVL tiene dos fases, que se tratarán seguidamente por
separado: la actualización del factor de equilibrio de los nodos afectados y las ro-
taciones (si procede) para resolver desequilibrios provocados en la fase anterior.

1) Actualización del factor de equilibrio

Cuando el factor de equilibrio de un nodo se sale de los límites, estaremos ante


uno de los casos siguientes, en los que habrá que proceder de una manera distinta:

a) Inserción en un nodo con Fe = 0. En este caso se inserta el nuevo nodo, se ac-


tualiza el Fe del padre y se propaga la variación hacia sus ascendientes en el árbol.

Figura 13. Ejemplo de inserción en un nodo con Fe = 0

b) Inserción en el subárbol más pequeño de un nodo con Fe ≠ 0. En este caso


se inserta el nuevo nodo y se actualiza el Fe del padre.

Figura 14. Ejemplo de inserción en el subárbol más pequeño de un nodo con Fe ≠ 0

c) Inserción en el subárbol más grande de un nodo con Fe ≠ 0. En este caso se


inserta el nuevo nodo, se actualiza el Fe del padre y se propaga la variación ha-
cia los ascendientes, hasta que se detecta un nodo cuyo factor de equilibrio se
sale de los límites permitidos.

Figura 15. Ejemplo de inserción en el subárbol más grande de un nodo con Fe ≠ 0


© FUOC • P06/75001/00581 • Módulo 7 29 Árboles de búsqueda

En el ejemplo de la figura 15, el resultado de esta primera fase de actualización


Inserción por la derecha
no es un árbol AVL. Esto es así porque la operación aún no ha acabado (falta
En los tres casos anteriores se
realizar la fase de rotaciones que veremos a continuación). ha representado la inserción de
un hijo por la izquierda. La in-
serción por la derecha es
Una vez detectado un desequilibrio (caso 3), hay que realizar una operación análoga.
de rotación sobre los nodos del árbol.

Rotaciones en un árbol AVL

Cuando haya que realizar alguna rotación, el nodo en el que hayamos detec-
tado el desequilibrio lo denominaremos nodo pivote. El nodo pivote es el an-
tecedente más cercano del nodo insertado que tenga Fe ≠ 0.

Se pueden aplicar cuatro tipos de rotaciones. El nombre de las rotaciones viene


dado por el camino inicial que se debe seguir para llegar desde el pivote hasta el
nuevo nodo insertado (I = izquierda; D = derecha), según se desciende en el ár-
bol. La figura 16 representa gráficamente estas rotaciones.

Figura 16. Tipos de rotaciones necesarias para equilibrar el árbol

Las líneas discontinuas horizontales representan los niveles de profundidad del árbol; los círculos son nodos individuales, y los cuadrados son subárboles (pueden representar más de
un nodo); el nodo sombreado representa el lugar de la inserción del nuevo nodo.
© FUOC • P06/75001/00581 • Módulo 7 30 Árboles de búsqueda

Los nodos afectados por una rotación simple son el pivote (P) y la raíz del ma-
yor subárbol hijo (H) del pivote. En una rotación doble se ven afectados estos
mismos nodos y, además, la raíz del mayor subárbol nieto (N) del nodo H.
Tanto en rotaciones simples como dobles también se ve afectado el padre del
pivote, que no se ha representado en la figura en aras de una mayor claridad.

La figura 17 muestra ejemplos de inserciones en un árbol AVL, en el que se


producen rotaciones simples I-I (figura 17) y D-D y rotaciones dobles I-D y D-I.

Figura 17. Ejemplos de inserciones con rotación


© FUOC • P06/75001/00581 • Módulo 7 31 Árboles de búsqueda

Leyenda de la figura 17

Se ha marcado el valor del Fe = ±1 en los nodos donde se producen desequilibrios, justo


antes de la operación (inserción) que lo provoca. Podría pensarse que dicho factor alcan-
zaría valores de ±2 tras cada inserción. Realmente, el valor que alcance el factor de equi-
librio de estos nodos es una manera interna de detectar el desequilibrio, pero la operación
de inserción más la rotación correspondiente deben ejecutarse como un todo indivisible,
sin dejar que el Fe alcance valores de ±2.

2.3.4. Supresión en un árbol AVL

La supresión en un árbol AVL comienza por la búsqueda del nodo que se quie-
re borrar. Al igual que en los ABB, si se encuentra en un nodo hoja, se elimina.
Si el nodo es intermedio, hay que sustituirlo por el menor de su subárbol iz-
quierdo (o el mayor del derecho).

Una vez eliminado el nodo, hay que recalcular el factor de equilibrio del padre
del nodo borrado o movido. Si no se provocó un desequilibrio, sólo hay que
ajustar algunos factores de equilibrio. En cambio, si se ha producido un desequi-
librio, también habrá que reequilibrar el árbol mediante las rotaciones introdu-
cidas para el caso de la inserción.

La eliminación de un nodo de un AVL puede provocar un desequilibrio más


grave que la inserción. En el caso de la inserción, una simple rotación puede
arreglar el desequilibrio. En la supresión, en cambio, puede ser necesario reali-
zar más de una rotación para arreglarlo. Para detectarlo, hay que analizar los
nodos ascendientes, siguiendo el camino que va desde el nodo borrado hasta
la raíz del árbol. En el análisis de cada uno de estos nodos se pueden distin-
guir los casos siguientes:

1) El nodo analizado tiene Fe = 0. Entonces se ajusta el factor de equilibrio a +1 o


–1, dependiendo de en qué rama se haya borrado el nodo, y no es necesario seguir
el análisis. La figura 18 muestra la supresión del nodo 85 en el árbol de la izquier-
da. En primer lugar, se elimina el nodo como si se tratara de un ABB normal. Una
vez desenganchado el nodo 85 de su padre, se analizan los nodos ascendientes de
abajo a arriba, comenzando por el 84. Como éste tiene Fe = 0 (caso 1), simplemen-
te se ajusta su nuevo valor Fe = –1 y no es necesario seguir el análisis.

Figura 18. Ejemplo de borrado en un AVL

2) El nodo analizado tiene Fe ≠ 0 y el nodo borrado pertenecía al subárbol más


grande. En tal caso, se ajusta el factor de equilibrio a cero y hay que seguir ana-
lizando los antecesores. Por ejemplo, en la figura 19, si se borra el nodo 75,
en el camino de análisis y actualización de los factores de equilibrio primeros
© FUOC • P06/75001/00581 • Módulo 7 32 Árboles de búsqueda

se encuentra el nodo 45, con su Fe no nulo. Una vez ajustado éste y puesto a
Fe = 0, es necesario seguir el análisis y actualización de Fe, porque el acorta-
miento que se ha producido en la rama más larga del nodo 45 puede influir
en el Fe de nodos situados más arriba en el árbol, como sucede con el 40 y el
36, cuyos Fe deben ser actualizados: Fe(40) = 0 y Fe(36) = –1.

Figura 19. Ejemplo de borrado en un AVL (caso 2)

3) El nodo analizado tiene Fe ≠ 0 y el nodo borrado pertenecía al subárbol


más pequeño. Ahora hay que aplicar una rotación y ajustar el factor de equi-
librio, como se indica a continuación:

a) Si la raíz del subárbol más grande tiene Fe = 0, se realiza una rotación simple
(que dependerá del Fe del nodo analizado: si Fe > 0, la rotación será D-D; y si Fe < 0,
la rotación será I-I). Después se ajusta el factor de equilibrio del nodo analizado y
se detiene el análisis. En el árbol de la figura 20 se ha borrado el nodo 38. Como
el factor de equilibrio del nodo analizado es Fe(40) = +1, se ha borrado por la rama
más corta, y como la raíz de la rama más larga tiene Fe(48) = 0, hay que aplicar el
caso 3a. La rotación aplicada es la D-D porque el Fe del nodo analizado es positivo.

Figura 20. Ejemplo de borrado en un AVL (caso 3a)

b) Si el nodo analizado y la raíz del subárbol más grande tienen el mismo Fe, se
realiza una rotación simple, se ajusta el factor de equilibrio de este último, pero el
análisis prosigue hacia los antecesores del nodo analizado (si los Fe son positivos,
la rotación será D-D, y si son negativos, la rotación será I-I). Por ejemplo, sobre el
árbol de la figura 21 se borrarará el nodo 38. Entonces se analiza el nodo 40, que
tiene un Fe no nulo. Además, se ha borrado por la rama más corta, y la rama más
larga es Fe(45) = Fe(40) = +1. Por lo tanto, hay que aplicar el caso 3b y realizar una
© FUOC • P06/75001/00581 • Módulo 7 33 Árboles de búsqueda

rotación D-D simple. Una diferencia notable con respecto al caso 3a es que ahora
el análisis no se detiene, sino que la variación del factor de equilibrio se sigue pro-
pagando hacia arriba en el árbol. Éste es el motivo por el que el Fe del nodo 36 fi-
nalmente valdrá –1, tras la aplicación del caso 1 en la propagación.

Figura 21. Ejemplo de borrado en un AVL (caso 3b)

c) Si el nodo analizado y la raíz del subárbol más grande tienen Fe de distinto va-
lor, se realiza una rotación doble (I-D o D-I, dependiendo de los factores de equi-
librio de ambos), se ajustan sus factores de equilibrio y se analizan los antecesores.
En el árbol de la izquierda de la figura 22 se borra el nodo de clave 6. El análisis
comienza por el nodo 12. Tanto en el nodo 12 como en el 3 se reproduce el caso
2. Por ello, tras actualizar sus Fe, hay que seguir analizando hacia arriba. El siguien-
te que se analiza es el nodo 13 (con un Fe, +1). Si consideramos que el Fe del nodo
40 es –1, nos encontramos en el caso 3c. Por lo tanto, hay que realizar una rota-
ción doble D-I, cuyo resultado es el árbol de la derecha de la figura.

Figura 22. Ejemplo de borrado en un árbol AVL (caso 3c)

Observad que, en todos los casos, el criterio para decidir si es necesario seguir
analizando los nodos del recorrido hasta la raíz es que se modifique la altura del
subárbol que parte del nodo analizado. Si la altura no se modifica, no hay que
seguir analizando los antecesores restantes.

2.3.5. Implementación en la biblioteca de TAD

Los AVL no proponen un TAD nuevo con respecto a los árboles binarios de
búsqueda, ya que no amplían el conjunto de operaciones de manejo que estos
© FUOC • P06/75001/00581 • Módulo 7 34 Árboles de búsqueda

últimos permiten. Un árbol AVL es una implementación particular de árbol bi-


nario de búsqueda equilibrado, que simplifica el modo de mantener el equili-
brio tras cada operación de inserción y supresión.

Los árboles AVL de la biblioteca de TAD están representados por la clase ArbolAVL, Patrón de diseño
que hereda la misma interfaz de la clase ArbolBinarioBusquedaEncadenado, y rede-
Es el modo como la superclase
fine sólo la implementación de ciertas operaciones de la superclase: en particular, delega la operación de equili-
brio hacia sus subclases es un
la inserción y la supresión. Afortunadamente, la clase ArbolBinarioBusquedaEnca- patrón de diseño muy común,
denado se ha diseñado teniendo en cuenta este factor, por lo que se ha concen- conocido como método planti-
lla (en inglés, template method)
trado en el método protegido equilibrar() toda la implementación de operaciones (Gamma y otros, 2002).

de equilibrio. De esta manera, la clase ArbolBinarioBusquedaEncadenado delega en


su subclase ArbolAVL para que ésta proporcione la implementación adecuada al
método equilibrar.

Por otra parte, la clase ArbolAVL proporciona, además, una implementación


de un árbol AVL en la que otras clases contenedoras (por ejemplo, Diccionario
y Conjunto) delegan su implementación basada en árboles AVL. En el caso de
los diccionarios, esto se hace por delegación, con la clase DiccionarioAVLimpl.
Para implementar el TAD Conjunto la clase ConjuntoAVLImpl proporciona el
comportamiento de los conjuntos y delega en ArbolAVL la gestión del árbol.

2.4. Implementación de diccionarios con árboles binarios


de búsqueda

A continuación se verá cómo se usa un árbol binario de búsqueda AVL para alber- El TAD Diccionario se estudió junto
con las tablas de dispersión en el
módulo “El TAD Tabla” de esta
gar la colección de elementos del TAD Diccionario. Este TAD puede implementarse asignatura.

con distintos tipos de contenedores. Entre ellos, cabe destacar los árboles binarios
de búsqueda y las tablas de dispersión. A continuación se describe una implemen-
tación del TAD Diccionario basada en un árbol AVL, que posteriormente se com-
parará con la implementación basada en tablas de dispersión.

colección DiccionarioAVLImpl < C,E> implementa Diccionario<C,E> es

• constructor() O(1)
– Crea una instancia del árbol AVL que implementa el diccionario.

• void numElems() O(1)


– Devuelve el número de elementos del árbol.

• boolean estaVacio() O(1)


– Comprueba si el número de elementos es cero.

• void insertar(<C> clave, <E> elem) O(log n)


– Inserta en el árbol un nuevo nodo formado por la pareja de clave y elemento.

• <E> consultar (<C> clave>) O(log n)


– Busca la clave en el árbol.
– Si encuentra la clave, devuelve el elemento asociado a la clave en el mismo
nodo.
– Si no encuentra la clave, devuelve ‘null’.
© FUOC • P06/75001/00581 • Módulo 7 35 Árboles de búsqueda

• <E> borrar (<C> clave) O(log n)


– Busca la clave en el árbol.
– Si encuentra la clave, borra del árbol el nodo que la contiene, y devuelve el
elemento asociado.
– Si la clave no está en el árbol, devuelve ‘null’.

• boolean esta(<C> clave) O(log n)


– Comprueba si la clave está en el árbol mediante una llamada a consultar.

• Iterador<C> claves () O(n)


– Devuelve un iterador para recorrer en inorden todas las claves guardadas
en el árbol.

• Iterador<E> elementos () O(n)


– Devuelve un iterador para recorrer en inorden todos los elementos guardados
en el árbol.

Magnitudes del coste:

• n: Número de elementos del árbol

De la clase DiccionarioAVLImpl de la biblioteca de TAD se destaca el siguiente


código, que muestra cómo se implementa el diccionario basándose en la dele-
gación sobre un árbol AVL:

uoc.ei.tads.DiccionarioAVLImpl

public class DiccionarioAVLImpl<C,E> implements Diccionario<C,E> {


protected ArbolAVL<ClaveValor<C,E>> avl;

public DiccionarioAVLImpl() {
avl = new ArbolAVL<ClaveValor<C,E>>();
}

public int numElems() { return avl.numElems(); }

public boolean estaVacio() { return avl.estaVacio(); }

public void insertar(C clave, E elem) {


avl.insertar( new ClaveValor<C,E>(clave, elem));
}

public boolean esta(C clave) {


return avl.consultar(clave) != null;
}

public E consultar(C clave) {


E result=null;
ClaveValor<C,E> aux = new ClaveValor<C,E>(clave,null);
ClaveValor<C,E> claveValor = avl.consultar(aux);
if (claveValor!=null)
result=claveValor.getValor();
return result;
}
© FUOC • P06/75001/00581 • Módulo 7 36 Árboles de búsqueda

public E borrar(C clave) {


I result=null;
ClaveValor<C,E> aux=new ClaveValor<C,E>(clave,null);
ClaveValor<C,E> claveValor = avl.borrar(aux);
if (claveValor!=null)
result=claveValor.getValor();
return result;
}
}

Como puede observarse, la implementación de Diccionario delega la implementa-


ción de la colección ordenada de elementos en el atributo privado denominado
avl y el tipo ArbolAVL. La delegación consiste en el hecho de que las peticiones
que llegan al diccionario y que tienen que ver con el almacenamiento de sus ele- La técnica de delegación se explica
con más detalle en la asignatura
Programación orientada a objetos.
mentos se transforman en llamadas a métodos del objeto delegado, es decir, el
atributo avl. Esto puede observarse en el hecho de que casi todos los métodos de
DiccionarioAVLImpl se implementan como llamadas a métodos de ArbolAVL. Por
ejemplo, los métodos insertar, consultar y borrar de DiccionarioAVLImpl sólo deben
hacer una llamada al método del mismo nombre del ArbolAVL delegado.

En la biblioteca de TAD, la clase DiccionarioAVLImpl proporciona una implemen-


tación alternativa a TablaDispersion. Al elegir una de estas dos implementaciones,
hay que tener en cuenta el uso más frecuente que se va a dar al diccionario. Por
ejemplo, la elección no será la misma si es bastante común la necesidad de reco-
rrer todos los elementos dentro de un rango de claves, o si simplemente se nece-
sita el diccionario para hacer búsquedas esporádicas de claves. El comportamiento
de las tablas de dispersión no es todo lo bueno que sería deseable para la opera-
ción de recorrer todos los elementos de un rango de claves. Sin embargo, en un
Estas razones son las mismas que se
árbol de búsqueda, todas las claves del rango mantienen una cercanía de referen- esgrimen para la solución del
ejemplo de aplicación del subapartado
2.6 de este módulo didáctico.
cias en el árbol que le hacen especialmente valioso para este tipo de operaciones.

2.5. Implementación de conjuntos con árboles binarios


de búsqueda

Otra posible aplicación de los árboles AVL consiste en dotar de implementación


a la interfaz Conjunto. Los conjuntos abstractos estudiados hasta el momento res-
ponden al TAD ConjuntoAbstracto, cuya implementación en la biblioteca de TAD
la proporciona la clase ConjuntoAbstracto. Esta clase confía en una serie de méto-
dos abstractos que recibe de la interfaz Conjunto, y para los cuales no proporciona
ninguna implementación. En particular, estos métodos son:

• void insertar(E elem)


• boolean esta(E elem)
• E borrar(E elem)
© FUOC • P06/75001/00581 • Módulo 7 37 Árboles de búsqueda

La clase ConjuntoAVLImpl proporciona la implementación final deseada para


estos métodos, basándose en un árbol AVL privado que guarda ordenados los
elementos del conjunto. Aunque la interfaz de TAD Conjunto que presenta este
caso no permite el acceso a las características de ordenación que el árbol de
búsqueda permitiría, las búsquedas se realizarán internamente de manera más
óptima. A continuación se especifica la clase ConjuntoAVLImpl y se detallan las
partes más destacadas de dicha implementación.

colección ConjuntoAVLImpl <E> extiende ConjuntoAbstracto<E> es

• constructor() O(1)
– Crea una instancia del árbol AVL que implementa el conjunto.

• void numElems() O(1)


– Devuelve el número de elementos.

• boolean estaVacio() O(1)


– Comprueba si el número de elementos es cero.

• void insertar(<E> elem) O(log n)


– Inserta en el árbol un nuevo nodo para el elemento.

• <E> borrar (<E> elem) O(log n)


– Borra y devuelve el elemento borrado del árbol.

• boolean esta(<E> Elem) O(log n)


– Comprueba si el elemento está en el árbol.

• Iterador<E> elementos() O(n)


– Devuelve un iterador para recorrer en inorden todos los elementos del árbol.

Magnitudes del coste:

• n: Número de elementos del árbol

uoc.ei.tads.ConjuntoAVLImpl Adaptador

public class ConjuntoAVLImpl<E> extends El modo cómo ArbolAVL pro-


porciona la implementación de
ConjuntoAbstracto<E> { un conjunto, adaptando su pro-
pia interfaz (que queda oculta) a
protected ArbolAVL<E> avl; la de ConjuntoAbstracto, es un
patrón de diseño conocido
como Adaptador (adapter)
public ConjuntoAVLImpl() { (Gamma et al., 2002).
avl = new ArbolAVL<E>();
}

public int numElems() { return avl.numElems(); }

public boolean estaVacio() { return avl.estaVacio(); }


© FUOC • P06/75001/00581 • Módulo 7 38 Árboles de búsqueda

public void insertar(E elem) {


avl.insertar(elem);
}

public boolean esta(E elem) {


return avl.consultar(elem) != null;
}

public E borrar(E elem) {


return avl.borrar(elem);
}
}

2.6. Ejemplo de aplicación: implementación de un paginador

En multitud de aplicaciones y páginas web se muestran listados muy largos de


registros que se agrupan en trozos de tamaño fijo, de modo que estos trozos
quepan en la pantalla y se pueda navegar por ellos más cómodamente me-
diante una serie de controles como los que se muestran en la figura 26.

Figura 23. Ejemplo de un paginador de registros procedentes de una base de datos de películas

No se muestra toda la lista de una vez, sino sólo un trozo de ella. El listado se acompaña de controles para avanzar y retroceder
entre trozos.

El modo de desplegar “a trozos” los datos en pantalla se conoce como paginador.


En muchas ocasiones, un paginador puede verse como un TAD Diccionario<C,E>
que ordena los registros según una clave determinada. Por ejemplo, las películas
de la figura podrían estar ordenadas por un código de película; la clave C es el có-
digo y el elemento E será el registro con todos los datos de la película.

Fijaos en que, en este ejemplo, no suelen hacerse búsquedas esporádicas por cla-
ve en el diccionario que contiene todas las películas. Más bien, suelen hacerse
recorridos en bloques de k elementos, tantos como quepan en la pantalla. En tal
caso, una implementación basada en árboles AVL será más conveniente que
aquéllas basadas en tablas de dispersión.
© FUOC • P06/75001/00581 • Módulo 7 39 Árboles de búsqueda

¿Qué operaciones necesitaremos para implementar el paginador del ejemplo?


Sin duda, sólo tendremos en cuenta aquello que tenga que ver con la gestión
de los datos necesaria para el recorrido por toda la colección, y nos olvidare-
mos de cuestiones relativas a la interfaz de usuario:

• Una operación constructora del paginador.


• Una operación para insertar elementos en el paginador.
• Una operación paginar(inicial,final) que obtiene el rango de elementos del
diccionario comprendido entre el par de claves inicial y final.

La operación para paginar devolverá la colección de elementos del diccionario


comprendidos entre dos claves dadas. Esto es parecido a lo que hacen los métodos
para el recorrido de un TAD, que en la biblioteca de TAD están implementados
como iteradores. Por lo tanto, la operación paginar la implementaremos de modo
similar al método elementos() del TAD Diccionario. La diferencia radica en que este
último devuelve un iterador que recorre todos los elementos del diccionario, y el
método paginar(inicial,final) devolverá un iterador que actúa sólo sobre los ele-
mentos comprendidos entre la clave inicial y la clave final.

A continuación se ofrece parte de una posible implementación del paginador.


La interfaz Paginador es implementada por la clase PaginadorImpl, que a su vez
delega en DiccionarioExtendido la implementación del recorrido parcial sobre
los elementos del rango.

uoc.ei.ejemplos.modulo7.Paginador

...
public interface Paginador<C,E> {

void insertar(C clave,E element);


Iterador<E> paginar(C claveInicial, C claveFinal);
}

uoc.ei.ejemplos.modulo7.PaginadorImpl

...
public class PaginadorImpl<C,E> implements Paginador<C,E> {
private DiccionarioExtendido<C,E> diccionarioPaginas;

public PaginadorImpl() {
diccionarioPaginas = new DiccionarioExtendido<C,E>();
}
public Iterador<E> paginar(C claveInicial,C claveFinal) {
return diccionarioPaginas.elementos(claveInicial,claveFinal);
}
}
© FUOC • P06/75001/00581 • Módulo 7 40 Árboles de búsqueda

uoc.ei.ejemplos.modulo7.DiccionarioExtendido

...
public class DiccionarioExtendido<C,E> extends DiccionarioAVLImpl<C,E> {
public Iterador<E> elementos(C claveInicial,C claveFinal) {
Recorrido<ClaveValor<C,E>> rango= new
RecorridoRango<C,E>(avl.recorridoInorden(),claveInicial,claveFinal);
return new IteradorRecorridoValoresImpl<C,E>(rango);
}
}

uoc.ei.ejemplos.modulo7.RecorridoRango

...
public class RecorridoRango<C,E> implements Recorrido<ClaveValor<C,E>> {
private Recorrido<ClaveValor<C,E>> recorridoGlobal;
private C claveInicial,claveFinal;
private Posicion<ClaveValor<C,E>> siguiente;

public RecorridoRango(Recorrido<ClaveValor<C,E>> r,C claveInicial,C claveFinal) {


this.recorridoGlobal=r;
this.claveInicial=claveInicial;
this.claveFinal=claveFinal;
siguiente=null;
}

public boolean haySiguiente() {


if (siguiente!=null)
return true;
while (recorridoGlobal.haySiguiente() && siguiente==null) {
Posicion<ClaveValor<C,E>>aux=recorridoGlobal.siguiente();
if (esClaveValida(aux.getElem().getClave()))
siguiente=aux;
}
return Siguiente!=null;
}

public Posicion<ClaveValor<C,E>> siguiente()throws ExcepcionPosicionInvalida {


Posicion<ClaveValor<C,E>> aux=siguiente;
siguiente=null;
return aux;
}

protected boolean esClaveValida(C clave) {


return
((Comparable<C>)claveInicial).compareTo(clave)<=0 &&
((Comparable<C>)clave).compareTo(claveFinal)<=0;
}
}
© FUOC • P06/75001/00581 • Módulo 7 41 Árboles de búsqueda

3. Árboles multicamino y árboles B

Un árbol de búsqueda sirve para guardar colecciones ordenadas de elementos.


Esta capacidad de los árboles de búsqueda también tiene su utilidad en aplica-
ciones con exigencias de almacenamiento masivo, como las bases de datos o
los sistemas de ficheros. En estos casos, los árboles suelen ser tan extremada-
mente grandes que no caben completos en la memoria principal, por lo que
se almacenan en disco o en algún otro dispositivo de memoria secundaria. El
acceso a disco es considerablemente más lento que el acceso a la memoria
principal. Por ello es muy conveniente poder reducir el número de accesos a
disco a la hora de buscar un elemento.

Las bases de datos y los sistemas de ficheros suelen emplear índices para acce-
der más rápidamente a los elementos almacenados. Los índices suelen cons-
truirse empleando como clave un subconjunto de atributos de la colección de
elementos (por ejemplo, los nombres de los ficheros, o las claves de las tablas
de una base de datos). Los árboles de búsqueda multicamino están pensados
para construir estos índices de manera que se aprovechen las ventajas de los
árboles de búsqueda a la vez que se minimiza el número de accesos al disco.

En un árbol de búsqueda multicamino, los nodos se almacenan en memoria


secundaria o disco. Estos nodos contienen tanto valores del índice, como los
propios elementos de información indexada. Si nos despreocupáramos de la
ubicación de los nodos en el disco, cada acceso a un nodo exigiría un acceso
a disco, que es una operación relativamente costosa.

Los discos están organizados en bloques de un cierto tamaño (normalmente


2k para valores de k > 9) cuyo contenido total es leído con un solo acceso a dis-
co. Sin embargo, para almacenar cantidades de información de mayor tama-
ño, se necesitarán varios bloques, que no tienen por qué ser contiguos y, por
lo tanto, exigirán más de un acceso al disco. La ventaja de los árboles multica-
mino no es reducir el número de operaciones, sino reducir el número de acce-
sos a disco, de la siguiente manera:

• En cada nodo del árbol se agrupa un conjunto de elementos de datos.

• Estos datos suelen leerse secuencialmente, si bien no en su totalidad, sí en


subsecuencias ordenadas de cierto tamaño.

• Para aprovechar la forma secuencial de los accesos, los datos se almacenan


contiguos en el disco. Por lo tanto, con un único acceso al disco tendremos
en memoria todos los elementos de un nodo.
© FUOC • P06/75001/00581 • Módulo 7 42 Árboles de búsqueda

• Cuando, más adelante, se deba acceder al siguiente elemento de la secuen-


cia, si éste estaba en el mismo nodo del árbol, estará en memoria, por lo
que ya no será necesario otro acceso a disco.

Como consecuencia, aunque el número de operaciones en un árbol multicami-


no será parecido al de un ABB, el número de accesos a disco será mucho menor.

Ejemplo

Supongamos que se quiere almacenar una colección ordenada de 220 elementos en un ár-
bol binario de búsqueda situado en un disco organizado en bloques con cabida para 100
nodos. Aun cuando el árbol esté equilibrado, dado su tamaño, en el peor de los casos se
necesitarían 20 accesos a disco para recuperar un elemento (el peor de los casos se da
cuando el elemento se sitúa en el nivel más profundo de la jerarquía y ningún par de
nodos de los recorridos para llegar a él comparten un mismo bloque del disco). Esto se
debe a que con un ABB, el número de accesos a disco es igual que el número de elementos
consultados, es decir, 20 en el peor de los casos.

Si conseguimos agrupar los nodos del ABB en bloques o páginas de nodos, como mues-
tra la figura 24, el número de accesos a disco será de log100 210 ∼ 1,5. Esto significa que,
con un máximo de dos accesos a disco, podemos alcanzar cualquier nodo.

Figura 24. Un árbol binario de búsqueda organizado en bloques

El tipo de árbol de búsqueda multicamino más común es el denomina-


do árbol B. Un árbol B puede guardar en sus nodos más de un elemento,
y cada nodo puede tener más de dos hijos. Además, por definición, son
árboles equilibrados en altura, y esto queda asegurado por el modo
como se actualizan.

Estas propiedades permiten que el árbol almacene grandes cantidades de in-


formación sin crecer demasiado en altura. Como el número máximo de acce-
sos a disco depende de la altura del árbol, los accesos a memoria secundaria
serán muy eficientes y el equilibrio del árbol no podrá degenerar creciendo por
alguna rama más que por otras.
© FUOC • P06/75001/00581 • Módulo 7 43 Árboles de búsqueda

Un árbol B de orden n es aquel que tiene las siguientes características: a


La raíz de un árbol B

1) Cada nodo (excepto la raíz) tiene como mínimo n/2 elementos. La raíz de un árbol B debe po-
der guardar un número de ele-
mentos por debajo del mínimo
2) Cada nodo tiene como máximo n elementos. permitido, ya que inicialmente
el árbol está vacío, o bien no
hay nodos suficientes, ni para
3) Si un nodo tiene m elementos, o bien es una hoja, o bien tiene m + 1 hijos. hacerlo crecer, ni para rellenar
la raíz.

4) Los elementos de un mismo nodo están ordenados linealmente.

5) Los nodos del árbol están ordenados como un árbol de búsqueda normal
(es decir, los que guardan elementos menores a la izquierda y los mayores a la
Orden de un árbol B
derecha).
Algunos autores definen el or-
den de un árbol B como el nú-
6) Todas las hojas se encuentran en el mismo nivel en el árbol. mero máximo de elementos de
un nodo. Otros lo definen
como la mitad de la capacidad
La figura 25 presenta un árbol B de orden 4 en el que se observan las caracte- máxima de elementos de un
nodo. Nosotros utilizamos la
rísticas de la definición.
primera definición.

Figura 25. Árbol B de orden 4

Las operaciones básicas sobre un árbol B son la inserción y la supresión de ele-


Árboles B de orden par
mentos. El hecho de ser un árbol multicamino reduce considerablemente el espa-
Para árboles B de orden par, se
cio de búsqueda con respecto a un ABB equivalente (es decir, aquel que contenga asegura una ocupación míni-
ma en cada nodo (excepto la
los mismos elementos), pues esto hace que disminuya la profundidad máxima al- raíz) del 50%.
canzable. Además, la estructura del árbol y la manera como se realizan la inser-
ción y la supresión aseguran que el árbol está siempre equilibrado en altura.

Sin embargo, el número de accesos en un árbol B es similar al de un ABB equi-


Sentido de los árboles B
valente, ya que, si bien se visitan menos nodos, para cada nodo se hacen varias
Los árboles B sólo tienen senti-
comparaciones (en el peor de los casos, con todos los elementos agrupados en do para órdenes grandes (es
decir, un tamaño suficiente de
el nodo). los bloques de disco). Sin em-
bargo, en los ejemplos presen-
tados a continuación se
emplearán órdenes menores
por motivos de claridad.
3.1. Estructura de un árbol B

La estructura de datos básica de un árbol B de orden n es la de un nodo ocu-


pado por m claves, reproducida gráficamente en la secuencia ordenada de la
figura 26. En dicha estructura se cumple que n/2 ≤ m ≤ n.
© FUOC • P06/75001/00581 • Módulo 7 44 Árboles de búsqueda

Figura 26. Estructura de un nodo de un árbol B

En la figura 26, ∀i = 1, ..., m se cumple que ki < ki+1, y pi es una referencia a un nodo
cuyas claves son mayores que ki y menores que ki+1. Dicha secuencia se correspon-
de con un TAD ListaOrdenada, aunque la implementación más común es con una
matriz (array) estática, pues el orden del árbol no suele cambiar.

3.2. Inserción en un árbol B

La inserción comienza por colocar la clave en el lugar que le corresponde, des-


cendiendo por el árbol y comparando con las claves de cada nodo. Una vez
encontrado el nodo que le toca, si hay sitio en éste, simplemente se inserta en
la colección ordenada de claves. En cambio, si se excede el número de claves
permitidas, hay que dividir el nodo en dos como indica la figura 27, y promo-
cionar hacia el nodo padre la clave central (la que ocupa la mediana de la se-
cuencia de claves del nodo junto con la nueva clave).

Figura 27. Esquema de la inserción en un árbol B

La clave que sube hacia el padre puede dar lugar a una nueva división, y esta si-
tuación se puede repetir hasta llegar a la raíz. Por ello se suele decir que los árboles
B crecen por la raíz, al contrario que los ABB, que lo hacen por las hojas.

3.3. Supresión en un árbol B

Al borrar una clave se pueden dar los siguientes casos:

1) La clave está en un nodo hoja. Entonces hay que desplazar a la izquierda el


resto de las claves del nodo.
© FUOC • P06/75001/00581 • Módulo 7 45 Árboles de búsqueda

2) La clave no está en un nodo hoja. Entonces hay dos alternativas:

a) Sustituir la clave por el mayor elemento del nodo apuntado por la referen-
cia al anterior (podéis ver la figura 28 para un ejemplo de esta alternativa).

b) Sustituir la clave por el menor elemento del nodo apuntado por la referen-
cia al siguiente.

Figura 28. Ejemplo de supresión de una clave en un árbol B

Puede suceder que, al borrar un nodo, el nodo correspondiente quede sub-


ocupado. En tal circunstancia hay que proceder como en el caso 3.

3) Si el nodo en el que se borra el elemento queda desocupado y no es la raíz,


habrá que reorganizar el árbol. Entonces hay dos posibilidades:

a) Equilibrado: se añaden elementos de uno de los dos nodos vecinos (izquierdo


o derecho) que tenga claves suficientes por encima del mínimo, de modo que se
redistribuyan de manera equitativa los elementos de ambos nodos, tal y como in-
dica la figura 29. Consideramos primero el izquierdo y, si no tiene claves suficien-
tes, el derecho (podéis ver como ejemplo de esto la figura 30).

Figura 29. Esquema del equilibrado de nodos al borrar una clave en un árbol B
© FUOC • P06/75001/00581 • Módulo 7 46 Árboles de búsqueda

Figura 30. Ejemplo de equilibrado al borrar en un árbol B

b) Fusión: si no se puede equilibrar ni con el izquierdo ni con el derecho porque


ninguno de los nodos vecinos tiene elementos de sobra para prestar, entonces se
unirá con uno de los nodos vecinos (considerando primero el izquierdo y, des-
pués, el derecho), y añadirá también la clave que los enlaza en el nodo padre
(podéis ver un ejemplo de esto en la figura 32).

Figura 31. Esquema de la fusión de nodos al borrar una clave en un árbol B

Figura 32. Ejemplo de fusión de nodos al borrar en un árbol B


© FUOC • P06/75001/00581 • Módulo 7 47 Árboles de búsqueda

4. Los árboles de búsqueda en la Java Collections


Framework

La implementación que la Java Collections Framework (JCF) proporciona


para los árboles de búsqueda no es directamente visible y pública. La deci-
sión de los diseñadores de la JCF fue ofrecer solamente las interfaces “útiles”
de los TAD que estos árboles implementan: Diccionario y Conjunto. Dichos
TAD aparecen implementados en clases como TreeSet y TreeMap:

• TreeMap es una implementación del TAD Diccionario, que internamente


guarda sus elementos ordenados en un árbol binario de búsqueda.

• TreeSet es una implementación del TAD Conjunto, basada en una instancia


privada de TreeMap que alberga los elementos del conjunto. Por otra parte,
la clase TreeSet implementa la interfaz SortedSet, que asegura que los ele-
mentos del conjunto serán recorridos de manera ordenada.

Los árboles binarios de búsqueda usados por la JCF son una variedad conocida Web recomendada
como árboles rojinegros. Esta implementación proporciona una complejidad de
Puede encontrarse una
O(log n) para las operaciones de búsqueda (containsKey y get), inserción (put) y prueba de la eficiencia de
estos árboles en Wikipedia:
supresión (remove).
<http://en.wikipedia.org/
wiki/Red-black_tree>
© FUOC • P06/75001/00581 • Módulo 7 48 Árboles de búsqueda

Resumen

En este módulo se han estudiado los árboles binarios de búsqueda y varias im-
plementaciones que aseguran que las búsquedas en colecciones enlazadas de
elementos ordenados se pueden hacer de manera compleja O(log n). Para ello
se ha visto el concepto de equilibrio y dos de sus variantes: el equilibrio perfec-
to y el equilibrio en altura. Como ejemplo de árboles equilibrados en altura se
han estudiado los árboles AVL y sus operaciones de inserción y supresión.

También se han estudiado los árboles B, útiles para almacenar grandes colec-
ciones ordenadas de elementos en un disco. De estos árboles se ha visto su es-
tructura y cómo se realizan las operaciones de inserción y supresión.
© FUOC • P06/75001/00581 • Módulo 7 49 Árboles de búsqueda

Ejercicios de autoevaluación

1. En un árbol binario de búsqueda inicialmente vacío se insertan los valores de clave 1 al n,


en orden creciente. Posteriormente se realizan operaciones de búsqueda sobre el árbol resul-
tante. ¿Cuál es el número de operaciones de comparación en el peor de los casos? ¿Para qué
valores de clave ocurre? ¿Y si el árbol está equilibrado? Rellenad la siguiente tabla para medir
intuitivamente la complejidad de las operaciones de búsqueda en uno y otro caso:

Números de comparaciones en el
Número de comparaciones en el peor
n peor de los casos (árbol no
de los casos (árbol equilibrado)
equilibrado)

15

31

2. Borrad el nódulo de clave 5 en el árbol AVL de la figura siguiente:

3. A partir del siguiente árbol AVL, borrad el nodo clave 10 e indicad las operaciones de ro-
tación necesarias y los factores de equilibrio antes y después de cada rotación.

4. Si definimos el grado de desequilibrio de un árbol AVL como el número de nodos cuyo factor
de equilibrio es diferente de cero, reflexionad y razonad sobre el porqué de la afirmación si-
guiente: “Los árboles de Fibonacci son árboles AVL con el grado máximo de desequilibrio po-
sible”.

5. Insertad en un árbol AVL inicialmente vacío las siguientes claves y en este mismo orden:
3, 8, 9, 2, 1, 5, 4, 6, 10 y 7. Indicad las rotaciones y los cambios de factor de equilibrio en
cada operación. Posteriormente, extraed los mismos valores en el orden inverso en el que
se van insertando.
© FUOC • P06/75001/00581 • Módulo 7 50 Árboles de búsqueda

6. Insertad en un árbol B de orden 4, inicialmente vacío, las claves siguientes y en este mismo
orden: 20, 40, 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45 y 25. Sobre
el árbol resultante, borrad la secuencia de claves siguiente: 25, 45, 24, 38, 32, 8, 27, 46, 13,
42, 5, 22, 18, 26, 7, 35 y 15. Indicad a cada paso el estado inicial y final del árbol.

7. Los árboles 2-3 son un tipo de árbol de búsqueda no binario en el cual cada nodo tiene
dos (nodo-2) o tres hijos (nodo-3). Un nodo-2 contiene un elemento y dos enlaces hijos
que, como en los árboles binarios de búsqueda, apuntan hacia elementos más pequeños
(los del hijo izquierdo) y más grandes (los del hijo derecho). Los nodos-2 pueden no tener
hijos, pero no pueden tener sólo uno. Un nodo-3 guarda exactamente dos elementos y tres
hijos (o bien ninguno). Si el nodo-3 tiene hijos, el hijo izquierdo contiene elementos más
pequeños que el primer elemento del padre; el hijo central contiene elementos más gran-
des que el primer elemento del padre, pero más pequeños que el segundo elemento; y el
hijo derecho contiene elementos más grandes que el segundo elemento del padre. La pro-
piedad más interesante de los árboles 2-3 es que, por construcción, todos los nodos hoja
están en un mismo nivel, por lo que no es necesario hacer operaciones de equilibrado adi-
cionales, siempre que se respete la definición de la estructura citada. Programad una im-
plementación alternativa DiccionarioArbol23Impl para el TAD Diccionario.

8. En el paginador diseñado en el apartado 2.6, podríamos haber optado por definir opera-
ciones para navegar por la colección de datos, como avanzar(k), que avanza y devuelve los
siguientes k elementos del diccionario, y análogamente retroceder(k). Implementad estas
operaciones y ampliad en consecuencia la interfaz de la clase DiccionarioExtendido.

9. La solución implementada para el paginador del subapartado 2.6 recorre todos los elemen-
tos del árbol AVL, para posteriormente filtrar aquellos que están comprendidos entre las
claves inicial y final pedidas por el método paginar. Una solución más idónea evitaría tener
que recorrer todos los elementos, filtrando directamente los solicitados en un solo recorri-
do, a medida que se construye el iterador devuelto por el método recorridoInorden() de la
clase ArbolAVL. Modificad la solución propuesta para que optimice la implementación del
método paginar de la manera descrita. (Nota: la clase ArbolAVL se puede ampliar con algún
método auxiliar para el recorrido que tenga en cuenta el rango de claves que se puede re-
correr.)
© FUOC • P06/75001/00581 • Módulo 7 51 Árboles de búsqueda

Solucionario

1.
Número de comparaciones en el peor Número de comparaciones en el peor
n
de los casos (árbol no equilibrado) de los casos (árbol equilibrado)

3 3 2

7 7 3

15 15 4

31 31 5

Intuitivamente, se puede calcular el número de comparaciones T(n) en el peor de los casos:

– para un árbol no equilibrado, T(n) = n; por lo tanto, su complejidad es O(n)


– para un árbol equilibrado es T(n) = log2 n + 1; por lo tanto, su complejidad es O(log n)

2. El análisis comienza por el nodo 10, antiguo padre del nodo 5 borrado. En el nodo 10 se da el
caso 2 de la supresión en un AVL, por lo que se anula su Fe y hay que continuar analizando.
El siguiente es el nodo 84 (que tenía un Fe de +1), que se ha borrado por la rama más corta. Por
lo tanto, nos encontramos en el caso 3. Para saber qué rotación hay que efectuar, cabe consi-
derar el Fe del nodo 86, que tiene el mismo valor que el del 10 (los dos son +1). Por lo tanto,
estamos en el caso 3b y hay que hacer una rotación simple (D-D en este caso, porque los dos
Fe son iguales a +1). El resultado es el árbol que vemos al margen:

3. Son necesarias dos rotaciones consecutivas: primero, una rotación D-D para equilibrar el
subárbol con raíz en la clave 50; y, después, una rotación D-I sobre el nodo 62, para equi-
librar el árbol resultante de la primera rotación.
© FUOC • P06/75001/00581 • Módulo 7 52 Árboles de búsqueda

4. Los árboles de Fibonacci, por la manera de construirse, son árboles AVL que no tienen nin-
gún nodo con factores de equilibrio diferente de cero, por lo que son árboles AVL con el gra-
do de desequilibrio más grande posible.

Glosario

árbol AVL m Tipo de árbol binario de búsqueda en el cual las alturas de los subárboles de
cualquier nodo difieren como máximo en una unidad.

árbol B m Tipo de árbol multicamino de búsqueda que limita el número de claves de cada
nodo y asegura que el árbol se mantenga siempre equilibrado en altura.

árbol binario de búsqueda m Árbol binario empleado para almacenar colecciones orde-
nadas de elementos enlazados de manera que se agilicen las operaciones de búsqueda.
sigla ABB

ABB m Véase árbol binario de búsqueda.

equilibrio en altura m Equilibrio que mantiene un ABB cuando, para cada nodo, las altu-
ras de sus subárboles izquierdo y derecho difieren como máximo en una unidad.

equilibrio perfecto m Equilibrio que mantiene un ABB cuando, para cada nodo, el núme-
ro de nodos de sus subárboles izquierdo y derecho difieren como máximo en una unidad.

factor de equilibrio m Diferencia entre las alturas de los subárboles derecho e izquierdo
de un nodo.

Bibliografía

Bibliografía básica

Aho, A. V.; Hopcroft, J. E.; Ullman, J. D. (1988). Estructuras de datos y algoritmos. Buenos
Aires: Addison Wesley Iberoamericana.

Bayer, R.; McCreight, E. (1972). “Organization and maintenance of large ordered in-
dexes”. Acta informatica (núm. 1, pág. 173-189).

Cormen, T. H.; Leiserson, C. E.; Rivest. R. L. (1996). Introduction to algorithms. Cambridge /


Nueva York / etc.: The MIT Press.

Gamma, E.; Helm, R.; Johnson, R.; Vlissides, J. (2002). Patrones de diseño. Addison-
Wesley.

Goodrich, M. T.; Tamassia, R. (2003). Data structures and algorithms in Java (3.a ed.). John
Wiley & Sons.

Preiss, B. R. (1999). Data structures and algorithms with object-oriented design patterns in Java.
John Wiley & Sons.

Rosen, K. (2004). Matemática discreta y sus aplicaciones (5.a ed.). McGraw-Hill.

Weiss, M. A. (1995). Estructuras de datos y algoritmos. Upper Saddle River: Addison Wesley.

Wirth, N. (1999): Algoritmos + Estructuras de datos = Programas (2.a ed.). Dossat.


© FUOC • P06/75001/00581 • Mòdul 7 53 Árboles de búsqueda

Anexo

Para saber más


Los árboles rojinegros son un tipo de árbol binario de bús- búsquedas. También puede demostrarse que la altura de un ár-
queda que añaden un atributo a cada nodo para indicar su co- bol 2-3 es menor o igual que log n (cf. Aho et al., 1988).
lor (rojo o negro) e imponen ciertas restricciones a la forma
como se encadenan nodos rojos y negros. Puede encontrarse En cuanto a los árboles multicamino, existen algunas varian-
una definición más precisa de estos árboles en Goodrich y Ta- tes de los árboles B que pretenden mejorar dos aspectos. Por
massia (2003). Lo interesante de los árboles rojinegros es que un lado, los árboles B* aprovechan mejor el espacio en disco
aseguran una altura máxima de 2 × log(n + 1) para un árbol de ocupado por un árbol B, así aseguran una ocupación mínima
n nodos (cf. Cormen et al., 1996). Esto hace que dichos árboles de cada nodo de las dos terceras partes de sus elementos. Esto
sean buenos árboles binarios de búsqueda. lo consiguen realizando de una manera distinta la operación
de inserción, que incluye en tal caso una redistribución o una
Por otra parte, los árboles 2-3 son una manera de aprovechar división de nodos. Por otro lado, los árboles B+ mejoran la
un árbol B para implementar árboles binarios de búsqueda. operación de recorrido, gracias al hecho de que enlazan todos
Los árboles 2-3 fueron inventados por J. E. Hopcroft incluso los nodos hoja entre sí y convierten los nodos intermedios en
antes de que Bayer y McCreight (1972) hicieran lo propio con índices que guardan duplicados de las claves de las hojas.
los árboles B.
La ocupación mínima del 66% sólo se cumple para árboles de
Un árbol 2-3 puede guardar un máximo de dos claves en cada orden superior a cuatro. Esto se debe a que cada clave (que es
nodo. Cuando se intenta insertar una tercera, el nodo se divi- indivisible) representa en un nodo de orden menor que cuatro
de como se hacía en los árboles B. Además, las inserciones su- un porcentaje muy elevado de su capacidad (al menos el 25%)
ceden siempre en las hojas, nunca en los nodos intermedios. que se acumula en un nodo a costa del vecino. Pero éste no es
De esta manera se consigue, como en los árboles B, que el ár- el caso común, pues los árboles B* están pensados para órde-
bol esté siempre equilibrado en altura, lo que facilita poner un nes grandes, tantas como quepan en un sector de un disco.
límite al desequilibrio que desmejoraba la complejidad de las (los sectores de disco tienen tamaños a partir de 29 bytes).

También podría gustarte