Está en la página 1de 9

Árbol binario de búsqueda1

Contenido
Árbol binario de búsqueda
Contenido
Introducción
Definición
Interfaz IArbolBinarioBusqueda
Implementación
Eficiencia de los ABB
El peor caso
El mejor caso
El caso promedio

Introducción
Una actividad común en todos los sistemas de cómputo es el mantenimiento de un conjunto de
datos, sobre el cual deseamos hacer las siguientes operaciones:

1. Insertar un elemento.
2. Eliminar un elemento.
3. Buscar un elemento.

Un ejemplo es un diccionario, en donde algunas veces se añaden palabras nuevas al idioma,


se eliminan palabras antiguas que están en desuso y en donde buscamos si una palabra existe
o queremos saber su significado.

Otro ejemplo es la lista de alumnos inscritos en un curso, en donde servicios escolares puede
inscribir o dar de baja alumnos y en donde podemos buscar si un alumno está inscrito en un
curso en específico.

En general, a un conjunto de datos sobre el cual podemos ejecutar operaciones de inserción,


eliminación y búsqueda, se le conoce como diccionario. ¿Cómo podríamos representar un
diccionario? La representación más simple, probablemente, es una lista, en donde ya están
definidas las operaciones arriba mencionadas.

1
El contenido de estas notas está basado principalmente en: A. V. Aho y J. Ullman. Foundations of
Computer Science. W.H. Freeman and Company, 1992.
Una forma más eficiente de representar un diccionario es mediante un árbol binario de
búsqueda (ABB), que es un árbol binario con cumple con ciertas propiedades.

Definición
La propiedad que hace a un árbol binario un ABB es que, para cada nodo n en el árbol, la
información almacenada en todos los nodos del subárbol izquierdo de n es menor a la
información almacenada en n y la información que guardan todos los nodos del subárbol
derecho de n es mayor que la de n. La Figura 1 muestra algunos ejemplos de esta propiedad.

Figura 1. Ejemplo de árboles binarios de búsqueda.

Veamos el siguiente ejemplo: ¿Cómo saber si un árbol es un ABB? Para saberlo, tenemos que
asegurarnos que todos los nodos del subárbol izquierdo de la raíz contienen información que
es menor a la almacenada en la raíz y que todos los nodos del subárbol derecho contienen
información que es mayor. Además, los subárboles izquierdo y derecho de la raíz son ABB. Los
siguientes son los métodos que se podrían utilizar para implementar esta operación.

def sonTodosMenores(n, x):


return (n is None)
or ((n.obtener() < x)
and (sonTodosMenores(n.getHijoIzquierdo(), x))
and (sonTodosMenores(n.getHijoDerecho(), x)))

def sonTodosMayores(n, x):


return (n is None)
or ((n.obtener() > x)
and (sonTodosMayores(n.getHijoIzquierdo(), x))
and (sonTodosMayores(n.getHijoDerecho(), x)))
def esABB(n):
return (n is None)
or (sonTodosMenores(n.getHijoIzquierdo(), n.obtener())
and sonTodosMayores(n.getHijoDerecho(), n.obtener())
and esABB(n.getHijoIzquierdo())
and esABB(n.getHijoDerecho()))

Ejercicio. Proporciona un ABB para los siguientes tenistas: Djokovic, Murray, Federer, Nadal,
Ferrer, Berdych, Del Potro, Tsonga.

Una vez construido el ABB, ¿en dónde se ubicaría a Gasquet? ¿Y, después, en dónde a
Wawrinka?

Interfaz IArbolBinarioBusqueda
Un ABB, por ser un árbol, hereda las operaciones sobre árboles, además de definir algunas
otras que son propias del ABB, las cuales consideran a x de tipo Object.

1. buscar(x). Busca a x en el ABB. Devuelve el nodo en donde se encontró a x o None


en caso contrario.
2. insertar(x). Inserta a x en el ABB. Devuelve False si x ya existe en el árbol, en
cuyo caso x no se inserta, o True en caso contrario.
3. eliminar(x). Elimina a x del ABB. Devuelve True si x fue eliminado o False si x no
existe en el ABB.

El método buscar tiene que considerar los siguientes casos. Si el árbol está vacío, entonces x
no existe en el árbol y se devuelve None. Si el árbol no está vacío y x está en la raíz, entonces
se devuelve el nodo raíz. Si x no está en la raíz, sea y la información en la raíz. Si, mediante
algún orden lineal x < y, entonces se busca a x en el subárbol izquierdo de la raíz. Si x > y,
buscaremos a x en el subárbol derecho de la raíz. La propiedad del ABB garantiza que x no
puede existir en el subárbol que no se está considerando.

La inserción de información nueva en un ABB es bastante sencilla, ya que siempre se ubicará


en una nueva hoja del árbol. El procedimiento es similar al de búsqueda, sólo que si x no se
encuentra en el árbol, se crea un nodo en la posición correcta, se almacena a x en ese nodo y
se devuelve True. Si el árbol está vacío, se crea un nuevo nodo que será la raíz del árbol y se
almacena a x en este nuevo nodo. Si el árbol no está vacío y su raíz contiene a x, entonces no
se hacen cambios y se devuelve False. Si x no está en la raíz, sea y la información en la
raíz. Si x < y, se inserta a x en el subárbol izquierdo de la raíz o, si x > y, se inserta en el
subárbol derecho.

Para eliminar un nodo de un ABB podemos considerar tres casos. El primero y más sencillo es
cuando el nodo es una hoja. En este caso, simplemente se elimina el nodo y se devuelve
True. Los otros dos casos suceden cuando el nodo que se desea eliminar es un nodo interno.
En estos casos no podemos simplemente eliminar el nodo porque el ABB podría quedar
desconectado. Entonces debemos modificar el árbol de tal forma que se mantenga la
propiedad del ABB. El segundo caso es cuando el nodo que se requiere eliminar sólo tiene un
hijo. Lo que tenemos que hacer entonces es asignar como hijo del padre del nodo eliminado, al
hijo del nodo eliminado, como se muestra en la Figura 2(a), en donde las líneas discontinuas
representan la conexión original de los nodos, antes de eliminar al nodo que contiene 8.

(a) (b) (c)


Figura 2. La operación eliminar en dos casos diferentes.

El tercer caso es cuando el nodo n que se quiere eliminar tiene a sus dos hijos, como el que se
muestra en la Figura 2(b). Una estrategia es encontrar al nodo que contiene la información
mínima y dentro del subárbol derecho de n y moverlo al lugar del nodo n. En este caso, la
propiedad del ABB se sigue manteniendo porque x es mayor a toda la información del subárbol
izquierdo y, dado que x < y, y también será mayor. En el ejemplo que se muestra en la Figura
2(c), el nodo que contenía a 8 fue eliminado y reemplazado por el nodo que contiene a 9, la
información mínima del subárbol derecho del nodo eliminado.

Implementación
La implementación de la clase ArbolBinarioBusqueda es mediante la extensión de la clase
Arbol, más la implementación de los métodos de la interfaz IArbolBinarioBusqueda.
Esto se vería más o menos de la siguiente forma.

class ArbolBinarioBusqueda:
...

La implementación de los métodos podría ser de la siguiente forma.

def buscar(self, x, n):


if n is None:
return None
if x == n.getDatos():
return n
elif x < n.getDatos():
return self.__buscar(x, n.getHijoIzquierdo())
else:
return self.__buscar(x, n.getHijoDerecho())

def insertar(self, x, n):


if x == n.getDatos():
return False
elif x < n.getDatos():
if n.getHijoIzquierdo() is not None:
return self.insertar(x, n.getHijoIzquierdo())
else:
n.setHijoIzquierdo(NodoBinario(x, None, None))
return True
else:
if n.getHijoDerecho() is not None:
return self.insertar(x, n.getHijoDerecho())
else:
n.setHijoDerecho(NodoBinario(x, None, None))
return True

def nodoElementoMinimo(self, n):


if n.getHijoIzquierdo() is None:
return n
else:
return self.nodoElementoMinimo(n.getHijoIzquierdo())

def eliminar(self, x, n):


if n is None:
return None

# Si el elemento x a eliminar es menor que el elemento


contenido # en el nodo n, entonces x deberia estar en el
subarbol izquierdo
if x < n.getDatos():
n.setHijoIzquierdo(self.__eliminar(x,
n.getHijoIzquierdo()))
# Si el elemento x a eliminar es mayor que el elemento
contenido # en el nodo n, entonces x deberia estar en el
subarbol derecho
elif x > n.getDatos():
n.setHijoDerecho(self.__eliminar(x, n.getHijoDerecho()))

# Si el elemento x a eliminar es igual al elemento contenido en


# el nodo n, entonces n es el nodo a eliminar
else:

# El nodo n solo tiene un hijo o no tiene hijos


if n.getHijoIzquierdo() is None:
temp = n.getHijoDerecho()
n = None
return temp

elif n.getHijoDerecho() is None:


temp = n.getHijoIzquierdo()
n = None
return temp

# El nodo n tiene dos hijos, por lo que hay que encontrar


el
# elemento menor en el subarbol derecho
else:
nodoMinimo =
self.nodoElementoMinimo(n.getHijoDerecho()
)

# Copiar el elemento minimo en el nodo n


n.setDatos(nodoMinimo.getDatos())

# Eliminar el nodo que contiene el elemento minimo en


# el subarbol derecho
n.setHijoDerecho(self.eliminar(nodoMinimo.getDatos(),
n.getHijoDerecho()))

return n

Ejercicios. Contesta los siguientes ejercicios.


1. Considera un ABB vacío, cuyos nodos almacenan números enteros.
a. Inserta, en este orden, los números: 4, 2, 1, 9, 3, 7, 6, 8, 5.
b. Elimina, en este orden, a: 4, 2, 7.
2. Considera un ABB vacío, cuyos nodos almacenan números enteros.
a. Inserta, en este orden, los números: 7, 2, 9, 0, 5, 6, 8, 1.
b. Elimina, en este orden, a: 7, 2.
3. Considera el siguiente ABB, cuyos nodos almacenan cadenas de caracteres.

Inserta, en este orden: Wozniacki, Azarenka, Radwanska, Errani, Williams, Kvitova,


Kerber, Stosur.

Eficiencia de los ABB


Comenzaremos esta sección preguntándonos ¿cuántas llamadas recursivas se hacen cuando
ejecutamos cualquiera de las operaciones buscar, insertar y eliminar sobre un ABB de
tamaño n? En general, podemos decir que el número de llamadas recursivas será igual a la
longitud del camino desde la raíz hasta el nodo relacionado con cada operación y esta longitud
será, a lo más, la altura del árbol. Pero, ¿cuál es la altura típica de un ABB con k nodos?

El peor caso
Supongamos que tenemos una lista ordenada de elementos 1, 2, …, k-1, k y queremos
crear un ABB a partir de esta lista. El resultado será un árbol como el que se muestra en la
Figura 3. Claramente podemos ver que la altura de este árbol es k-1. Entonces, podríamos
esperar que las operaciones buscar, insertar y eliminar se ejecuten en a lo más k-1
llamadas recursivas.

Figura 3. El peor caso de un ABB.

El mejor caso
Por otro lado, consideremos un ABB en donde cada nodo interior tiene ambos hijos, es decir,
un ABB perfecto. Ya hemos visto que un árbol de este tipo con k = 2h+1-1, tiene una altura a
≈ log2 k. En este caso, el número de llamadas recursivas será, a lo más, log2 k.

El caso promedio
¿Cuál de los dos casos anteriores es el caso promedio? En realidad, ninguno de los dos casos
es común en la práctica, pero el árbol completo se acerca a la eficiencia de un caso promedio.

No haremos una comprobación rigurosa, sino que haremos un análisis intuitivo. La raíz de un
árbol divide al resto de los nodos en dos subárboles. La división más pareja de un árbol con k
nodos tendrá dos subárboles de aproximadamente k/2 nodos cada uno. Este caso ocurre si la
raíz es el elemento que se encuentra exactamente a la mitad de la lista ordenada de los
elementos. La división más dispareja sucede cuando la raíz es el primero o el último de los
elementos y en este caso sólo tendrá un subárbol con k-1 nodos.

En un caso promedio, esperaríamos que la raíz sea uno de los elementos entre el de enmedio
y uno de los extremos y podríamos esperar que, en promedio, k/4 nodos vayan de un lado del
árbol y 3k/4 nodos del otro. Consideremos que cuando nos movemos dentro del árbol,
siempre lo hacemos hacia el subárbol más grande y encontramos la misma distribución de
nodos. Entonces, en el primer nivel encontramos 3k/4 nodos, en el segundo (3/4)(3k/4)
nodos y así sucesivamente. De esta forma, en el h-ésimo nivel encontramos (3/4)h k nodos.

Cuando h es suficientemente grande, (3/4)h k está muy cerca de 1 y esperaríamos que en


este nivel el subárbol más grande tenga una hoja. Ahora, ¿para qué valor de h tenemos que
(3/4)h k ≤ 1? Tomando logaritmos tenemos

log2 ((3/4)h k) ≤ log2 1


log2 (3/4)h + log2 k ≤ log2 1
h log2 (3/4) + log2 k ≤ log2 1
h (-0.4) + log2 k ≤ 0
log2 k ≤ 0.4h
h ≥ 2.5 log2 k

Lo que quiere decir que a una profundidad de 2.5 veces el logaritmo del número de nodos,
esperamos ver sólo hojas. Este argumento justifica, pero no comprueba, que un ABB típico
tiene una altura que es proporcional al logaritmo del número de nodos contenido en el árbol.

Ejercicios. Contesta los siguientes ejercicios.

1. Dado un arreglo A de tamaño n que contiene n números enteros, diseña un algoritmo


que ordena estos números utilizando un ABB.
2. Después de un tiempo, un ABB que inicialmente era perfecto se ha deteriorado.
Diseña un algoritmo que, al insertar y eliminar nodos, deje al ABB lo más parecido a
un ABB perfecto.

También podría gustarte