Árboles de búsqueda /
Conjuntos ordenados Pt.1
Iván Yesid Castellanos
Motivación
Una de las operaciones mas importantes en las estructuras de datos
consiste en la búsqueda elementos específicos
Ejemplo: buscar si un estudiante existe en la base de datos de la cafetería de la
universidad para darle el descuento del almuerzo
Si nuestra estructura la manejamos como una lista cualquiera tendríamos
que recorrerla toda para hacer la búsqueda (inserción O(1), consulta
O(N))
Otra opción para manejar esta situación sería mantener una lista
ordenada, es decir, insertar los elementos de forma que la lista siempre
esté ordenada y por cada consulta realizar una búsqueda binaria
(consulta O(log N), inserción O(N))
Si además queremos eliminar elementos en nuestra estructura sea que se
implemente con arreglos o con apuntadores tenemos una complejidad de
O(N)
Árbol de búsqueda binario
También hay forma de implementar la estructura anterior utilizando árboles, en este
caso se usarán árboles de búsqueda binarios (BST - Binary Search Tree)
Un BST es un árbol binario con la propiedad de que para cada nodo todos los
elementos del subárbol del hijo izquierdo (del nodo) son menores al elemento en el
nodo y todos los elementos del subárbol del hijo derecho (del nodo) son mayores al
elemento en el nodo
Conjunto ordenado
En un árbol de búsqueda binario estándar todos los elementos insertados
son diferentes, pues de haber varios iguales no tendría sentido la
operación principal que es la de búsqueda
Esto motiva el uso de arboles de búsqueda binarios para representar
conjuntos (pues en un conjunto solo hay una aparición por elemento)
Esto se ha de tener en cuenta para considerar que al insertar no se puede
insertar un elemento igual a otro que ya esté (igual desde el punto de vista
de la comparación)
De igual forma sabremos que al consultar un elemento este es único
dentro de ese conjunto
TDA conjunto ordenado
Instancias: un conjunto de elementos comparables
Operaciones:
EsVacio(): retorna true si el conjunto está vacío
Buscar(x): busca si algún elemento del conjunto coincide con x y retorna la
referencia de ese elemento en la colección o null en caso de que no esté en el
conjunto
Consultar(x): retorna true si x está en el conjunto, false en otro caso
Eliminar(x): retorna true si el elemento x está en el conjunto y además lo elimina
de este, en caso de que x no esté en el conjunto retorna false
Insertar(x): inserta el elemento x en el conjunto
Definición BinarySearchTree
Constructor, EsVacio
Insertar
Se utilizará la propiedad de orden en el árbol para insertar el elemento en
el lugar que le debería corresponder en el árbol, para esto vamos a
realizar un procedimiento recursivo
Si estamos en un nodo y el elemento que queremos insertar es menor al
elemento en el nodo entonces inserto el elemento en el subárbol del hijo
izquierdo del nodo
Si estamos en un nodo y el elemento que queremos insertar es mayor al
elemento en el nodo entonces inserto el elemento en el subárbol del hijo
derecho del nodo
Si estamos en un nodo y el elemento que queremos insertar es igual al
elemento en el nodo entonces no hacemos nada
Complejidad O(h), donde h es la altura del árbol
Ejemplo Insertar
Insertar 20, 43, 16, 18, 35, 48, 45, 6 (en ese orden)
Insert, auxInsert
Buscar / Consultar
Para buscar o consultar la existencia de un elemento en el conjunto
utilizamos fuertemente la propiedad del ordenamiento
Iniciamos explorando desde la raíz
Si el elemento está en el nodo que estemos explorando entonces
terminamos, en caso contrario, si es menor al que estamos explorando nos
vamos al subárbol izquierdo y si es mayor nos vamos al árbol izquierdo
Realizamos esto hasta encontrar el elemento o hasta llegar a alguna hoja
sin haberlo encontrado
Complejidad O(h), donde h es la altura del árbol
Ejemplo Buscar / Consultar
Buscar(19) Buscar(48)
Search, contains
Encontrar Mínimo, Máximo
Para encontrar el elemento mínimo del árbol basta con ir al descendiente
mas izquierdo posible, es decir, voy recorriendo por la izquierda hasta no
tener mas hijos izquierdos
En caso de que el árbol sea vacío retorna null
Nótese que esto también funciona para encontrar el mínimo de cualquier
subárbol del árbol, dado que cada subárbol de un BST es un BST
La misma idea funciona para encontrar el máximo del árbol, pero
haciendo el recorrido a la derecha
Complejidad O(h), h es la altura del arbol
Ejemplo Encontrar Mínimo
FindMin
Eliminar
Para eliminar un elemento del árbol consideraremos 3 posibles
casos:
El elemento está en una hoja: en este caso es sencillo eliminar pues
solamente hacemos que la referencia al nodo se vuelva nula
El elemento está en un nodo con un hijo: este caso también es sencillo,
pues hacemos que la referencia a este nodo sea la referencia al hijo
El elemento está en un nodo con 2 hijos: En este caso reemplazaremos
al elemento con el supremo (el mínimo de los mayores) o el infímo (el
máximo de los menores) del elemento y eliminamos luego ese nodo
Ese nodo o bien será hoja o tendrá un único hijo (la demostración de este
hecho queda como tarea del estudiante), por lo que entra en los 2 primeros
casos de eliminación
Complejidad O(h), donde h es la altura del árbol
Ejemplo Eliminar caso 1 y 2
Eliminar(2)
Eliminar(45)
Ejemplo Eliminar caso 3
Eliminar 20
Remove
Altura
También puede ser de nuestro interés (como desarrolladores) conocer la
altura del árbol, dado que las complejidades está en orden de esta
cantidad
De igual forma que con el encontrar mínimo o máximo podemos calcular
altura de subárboles del árbol original, utilizaremos polimorfismo para esto
De forma recursiva podemos calcular la altura de un árbol con el máximo
de las alturas de los subárboles de los hijos izquierdo y derecho
La altura de un árbol vacío es 0
Complejidad O(N), donde N es la cantidad de elementos en el arbol
Height
test BinarySearchTree
test BinarySearchTree
test BinarySearchTree
test BinarySearchTree
Test BinarySearchTree
Test BinarySearchTree
Árboles de búsqueda generales
En general la noción de árboles de búsqueda binarios se puede extender
a árboles de búsqueda n-arios, donde los hijos representan un orden
lexicográfico de los elementos
Para cada nodo cuyos hijos son a1, a2,…, ak entonces todos los elementos
del subárbol a1 son menores a todos los elementos del subárbol a2, los
cuales son menores a todos los del árbol a3 y así sucesivamente, los
elementos del subárbol ak son mayores a los de los demás subárboles
Existen varias estructuras especializadas que manejan este tipo de árboles
para almacenamiento de datos y realizar búsquedas eficientes
Estructuras Avanzadas basadas en
árboles de búsqueda
Árbol de sufijos (Suffix
Tree): Es un árbol que
contiene todos los sufijos
de un texto, tener este
árbol puede ayudar a
resolver problemas como
el de Longest Common
Substring de forma
eficiente o el Longest
Palindrome, de igual
forma tiene aplicaciones
en bioinformática y en
búsqueda de patrones
de texto
Estructuras Avanzadas basadas en
árboles de búsqueda
Árbol de prefijos (Prefix
Tree) / Trie: Es un árbol que
contiene varias palabras,
pero la búsqueda se da a
partir re prefijos, y los nodos
se construyen con las letras
de las palabras, es muy
eficiente para predicción
de texto, y para almacenar
bases de datos de palabras
(como el diccionario de las
palabras en español o en
inglés)
Estructuras Avanzadas basadas en
árboles de búsqueda
Radix Tree: Es un Trie
optimizado, en el cual se
comprimen nodos con
un único hijo y así utilizar
menos memoria y
decrementar la altura
del árbol, funciona igual
que el Trie, pero se debe
tener cuidado con el
manejo de estos nodos
‘comprimidos’ en la
implementación,
recomendado cuando
la memoria sea limitada