Un árbol de van Emde Boas ( pronunciación en neerlandés: [ vɑn ˈɛmdə ˈboːɑs ] ), también conocido como árbol vEB o cola de prioridad de van Emde Boas , es una estructura de datos de árbol que implementa una matriz asociativa con claves enteras de m bits. Fue inventado por un equipo liderado por el científico informático neerlandés Peter van Emde Boas en 1975. [ 1 ] Realiza todas las operaciones en tiempo O (log m ) (suponiendo que La operación de bits se puede realizar en tiempo constante), o equivalentemente entiempo, dondees el elemento más grande que se puede almacenar en el árbol. El parámetroNo debe confundirse con el número real de elementos almacenados en el árbol, que es con lo que a menudo se mide el rendimiento de otras estructuras de datos de árbol.
El árbol vEB estándar tiene una eficiencia espacial no ideal de. Por ejemplo, para almacenar enteros de 32 bits (es decir, cuando), requierebits de almacenamiento. Para resolver esto, los árboles vEB se pueden modificar para lograrespacio, o estructuras de datos similares con eficiencia de tiempo asintótica y eficiencia de espacio equivalentes(dóndees el número de elementos almacenados) se puede utilizar.
Operaciones apoyadas
Un árbol vEB admite las operaciones de una matriz asociativa ordenada , que incluye las operaciones habituales de matrices asociativas junto con dos operaciones de orden más , FindNext y FindPrevious : [ 2 ]
- Insertar : inserta un par clave/valor con una clave de m bits.
- Eliminar : elimina el par clave/valor con una clave determinada.
- Búsqueda : encuentra el valor asociado a una clave determinada.
- FindNext : encuentra el par clave/valor con la clave más pequeña que sea mayor que un valor k dado.
- FindPrevious : encuentra el par clave/valor con la clave más grande que sea menor que un valor k dado.
Un árbol vEB también admite las operaciones Mínimo y Máximo , que devuelven el elemento mínimo y máximo almacenado en el árbol respectivamente. [ 3 ] Ambas se ejecutan entiempo, ya que el elemento mínimo y máximo se almacenan como atributos en cada árbol.
Función

Dejarpara algún número entero. DefinirUn árbol vEB T sobre el universotiene un nodo raíz que almacena un array T.children de longitud. T.children[i] es un puntero a un árbol vEB que es responsable de los valores. Además, T almacena dos valores T.min y T.max , así como un árbol vEB auxiliar T.aux .
Los datos se almacenan en un árbol vEB de la siguiente manera: El valor más pequeño que se encuentra actualmente en el árbol se almacena en T.min y el valor más grande se almacena en T.max . Tenga en cuenta que T.min no se almacena en ningún otro lugar del árbol vEB, mientras que T.max sí. Si T está vacío, entonces usamos la convención de que T.max=−1 y T.min=M . Cualquier otro valorse almacena en el subárbol T.children[i] dondeEl árbol auxiliar T.aux realiza un seguimiento de qué hijos no están vacíos, por lo que T.aux contiene el valorsi y solo si T.children[j] no está vacío.
FindNext
La operación FindNext(T, x) que busca el sucesor de un elemento x en un árbol vEB procede de la siguiente manera: Si x < T.min , la búsqueda ha finalizado y la respuesta es T.min . Si x ≥ T.max , el siguiente elemento no existe; devuelve M. En caso contrario,Si x < T.children[i].max , entonces el valor buscado está contenido en T.children[i] , por lo que la búsqueda procede recursivamente en T.children[i] . De lo contrario, buscamos el sucesor del valor i en T.aux . Esto nos da el índice j del primer subárbol que contiene un elemento mayor que x . El algoritmo devuelve entonces T.children[j].min . El elemento encontrado en el nivel de hijos debe combinarse con los bits más significativos para formar un elemento siguiente completo.
función FindNext(T, x) si x < T.min entonces devolver T.min si x ≥ T.max entonces // no hay siguiente elemento devolver M i = piso(x/) lo = x modSi lo < T.children[i].max entonces devolver (i) + FindNext(T.children[i], lo) j = FindNext(T.aux, i) devolver (j) + T.children[j].min fin
Tenga en cuenta que, en cualquier caso, el algoritmo realizatrabajo y luego posiblemente recursión en un subárbol sobre un universo de tamaño(ununiverso de bits). Esto proporciona una recurrencia para el tiempo de ejecución de, que se resuelve en.
Insertar
La llamada insert(T, x) que inserta un valor x en un árbol vEB T funciona de la siguiente manera:
- Si T está vacío, entonces establecemos T.min = T.max = x y hemos terminado.
- De lo contrario, si x < T.min , insertamos T.min en el subárbol i responsable de T.min y luego establecemos T.min = x . Si T.children[i] estaba previamente vacío, también insertamos i en T.aux.
- De lo contrario, si x > T.max , insertamos x en el subárbol i responsable de x y luego establecemos T.max = x . Si T.children[i] estaba previamente vacío, también insertamos i en T.aux.
- De lo contrario, T.min < x < T.max , por lo que insertamos x en el subárbol i responsable de x . Si T.children[i] estaba previamente vacío, entonces también insertamos i en T.aux .
En código:
función Insertar(T, x) si T.min == x || T.max == x entonces // x ya está insertado devolver si T.min > T.max entonces // T está vacío T.mín = T.máx = x; devolver si x < T.min entonces intercambio(x, T.min) si x > T.max entonces T.máx = x i = piso(x /) lo = x mod Insertar(T.children[i], lo) Si T.children[i].min == T.children[i].max entonces Insertar(T.aux, i) fin
La clave de la eficiencia de este procedimiento es que insertar un elemento en un árbol vEB vacío requiere un tiempo O (1) . Por lo tanto, aunque el algoritmo a veces realiza dos llamadas recursivas, esto solo ocurre cuando la primera llamada recursiva fue en un subárbol vacío. Esto da el mismo tiempo de ejecución de recurrencia de Como antes.
Borrar
La eliminación de árboles vEB es la más compleja de las operaciones. La llamada Delete(T, x) que elimina un valor x de un árbol vEB T funciona de la siguiente manera:
- Si T.min = T.max = x , entonces x es el único elemento almacenado en el árbol y establecemos T.min = M y T.max = −1 para indicar que el árbol está vacío.
- De lo contrario, si x == T.min, entonces necesitamos encontrar el segundo valor más pequeño y en el árbol vEB, eliminarlo de su ubicación actual y establecer T.min=y . El segundo valor más pequeño y es T.children[T.aux.min].min , por lo que se puede encontrar en tiempo O (1) . Eliminamos y del subárbol que lo contiene.
- Si x≠T.min y x≠T.max , entonces eliminamos x del subárbol T.children[i] que contiene x .
- Si x == T.max, entonces necesitaremos encontrar el segundo valor más grande y en el árbol vEB y establecer T.max=y . Comenzamos eliminando x como en el caso anterior. Entonces el valor y es T.min o T.children[T.aux.max].max , por lo que se puede encontrar en tiempo O (1) .
- En cualquiera de los casos anteriores, si eliminamos el último elemento x o y de cualquier subárbol T.children[i], también eliminamos i de T.aux .
En código:
función Eliminar(T, x) si T.min == T.max == x entonces T.min = M T.max = −1 Si x == T.min , entonces hi = T.aux.min * j = T.aux.min T.min = x = hi + T.children[j].min i = piso(x /) lo = x mod Eliminar(T.children[i], lo) si T.children[i] está vacío entonces Eliminar(T.aux, i) si x == T.max entonces si T.aux está vacío entonces T.máx = T.mín de lo contrario hola = T.aux.max * j = T.aux.max T.max = hi + T.children[j].max fin
Nuevamente, la eficiencia de este procedimiento depende del hecho de que eliminar un elemento de un árbol vEB que contiene un solo elemento requiere un tiempo constante. En particular, la segunda llamada a Delete solo se ejecuta si x era el único elemento en T.children[i] antes de la eliminación.
En la práctica
La suposición de que log m es un número entero es innecesaria. Las operacionesyse puede reemplazar tomando solo los bits de orden superior ⌈ m /2⌉ y los bits de orden inferior ⌊ m /2⌋ de x , respectivamente. En cualquier máquina existente, esto es más eficiente que los cálculos de división o resto.
En implementaciones prácticas, especialmente en máquinas con instrucciones de desplazamiento de bits (shift-by-k) y búsqueda del primer cero , el rendimiento puede mejorarse aún más cambiando a una matriz de bits una vez que m sea igual al tamaño de la palabra (o un múltiplo pequeño de este). Dado que todas las operaciones en una sola palabra son de tiempo constante, esto no afecta el rendimiento asintótico, pero evita la mayor parte del almacenamiento de punteros y varias desreferenciaciones de punteros, logrando un ahorro práctico significativo en tiempo y espacio con este truco.
Una optimización de los árboles vEB consiste en descartar los subárboles vacíos. Esto hace que los árboles vEB sean bastante compactos cuando contienen muchos elementos, ya que no se crean subárboles hasta que sea necesario añadirles algo. Inicialmente, cada elemento añadido crea aproximadamente log( m ) nuevos árboles que contienen en total unos m /2 punteros. A medida que el árbol crece, se reutilizan cada vez más subárboles, especialmente los más grandes.
La implementación descrita anteriormente utiliza punteros y ocupa un espacio total de O ( M ) = O (2m ) , proporcional al tamaño del universo de claves. Esto se puede ver de la siguiente manera. La recurrencia es. Se puede demostrar que S ( M ) = O ( M ) por inducción. [ 4 ]
Estructuras similares
El uso de espacio O ( M ) de los árboles vEB representa una sobrecarga enorme a menos que se almacene una gran fracción del universo de claves. Esta es una de las razones por las que los árboles vEB no son populares en la práctica. Esta limitación puede solucionarse cambiando el arreglo utilizado para almacenar los hijos por otra estructura de datos. Una posibilidad es usar solo un número fijo de bits por nivel, lo que resulta en un trie . Alternativamente, cada arreglo puede reemplazarse por una tabla hash , reduciendo el espacio a O ( n log log M ) (donde n es el número de elementos almacenados en la estructura de datos) a costa de hacer que la estructura de datos sea aleatoria.
Los intentos x-fast y los intentos y-fast más complejos tienen tiempos de actualización y consulta comparables a los de los árboles vEB y utilizan tablas hash aleatorias para reducir el espacio utilizado. Los intentos x-fast utilizan un espacio de O ( n log M ) mientras que los intentos y-fast utilizan un espacio de O ( n ) .
Los árboles de fusión son otro tipo de estructura de datos de árbol que implementa una matriz asociativa sobre enteros de w bits en un universo finito. Utilizan paralelismo a nivel de palabra y técnicas de manipulación de bits para lograr un tiempo de O (log w n ) para consultas y actualizaciones de predecesores/sucesores, donde w es el tamaño de la palabra. [ 5 ] Los árboles de fusión utilizan un espacio de O ( n ) y pueden hacerse dinámicos con funciones hash o árboles exponenciales.
Lenhof y Smid [ 6 ] presentan una variante del árbol vEB que utiliza espacio O ( n ) y toma O (1) tiempo amortizado esperado para insertar un elemento, bajo la restricción de que las inserciones son en orden creciente; en otras palabras, el elemento insertado es siempre el nuevo máximo. Esta estructura utiliza hash perfecto dinámico para implementar el árbol en un espacio pequeño y además reduce el tamaño del árbol en un factor log log M al mantener en las hojas cubetas de tamaño log log M .
Dietz y Raman [ 7 ] presentaron una versión de árboles vEB que es eficiente en espacio y parcialmente persistente . Esta versión utiliza un espacio de O ( n ) y admite una inserción en la versión actual en un tiempo amortizado y esperado de O (log log M ) , mientras que una consulta en cualquier versión sigue siendo de O (log log M ) .
Implementaciones
Existe una implementación verificada en Isabelle (asistente de pruebas) . [ 8 ] Se demuestran tanto la corrección funcional como los límites de tiempo. Se puede generar código ML estándar imperativo eficiente.
Véase también
Referencias
- ↑ Peter van Emde Boas : Preservación del orden en un bosque en tiempo inferior al logarítmico ( Actas del 16º Simposio Anual sobre Fundamentos de la Informática 10: 75-84, 1975)
- ↑ Gudmund Skovbjerg Frandsen : Algoritmos dinámicos: notas del curso sobre árboles de van Emde Boas (PDF) Archivado el 23 de septiembre de 2015 en Wayback Machine ( Universidad de Aarhus , Departamento de Ciencias de la Computación)
- ^ Thomas H. Cormen , Charles E. Leiserson , Ronald L. Rivest y Clifford Stein . Introducción a los algoritmos , tercera edición. Prensa del MIT , 2009. ISBN 978-0-262-53305-8Capítulo 20: El árbol de van Emde Boas, págs. 531–560.
- ↑ Rex, A. "Determinación de la complejidad espacial de los árboles de van Emde Boas" . Consultado el 27 de mayo de 2011 .
- ↑ "Árbol de fusión" . OpenGenus IQ: Computing Expertise & Legacy . 4 de abril de 2019. Consultado el 30 de agosto de 2023 .
- ↑ Lenhof, Hans-Peter; Smid, Michiel (1994). "Uso de estructuras de datos persistentes para añadir restricciones de rango a problemas de búsqueda". RAIRO-Theoretical Informatics and Applications . 28 (1): 25– 49. doi : 10.1051/ita/1994280100251 .
- ↑ Dietz, Paul F.; Raman, Rajeev (1991). Persistencia, amortización y aleatorización (Informe técnico). Universidad de Rochester. TR 353.
- ^ Ammer, Thomas; Lammich, Peter (23 de noviembre de 2021). "Árboles de van Emde Boas" . Archivo de Pruebas Formales . Consultado el 26 de noviembre de 2021 .
Lecturas adicionales
- Erik Demaine, Sam Fingeret, Shravas Rao, Paul Christiano. Instituto Tecnológico de Massachusetts. 6.851: Estructuras de datos avanzadas (Primavera de 2012). Apuntes de la clase 11. 22 de marzo de 2012.
- van Emde Boas, P. ; Kaas, R.; Zijlstra, E. (1976). "Diseño e implementación de una cola de prioridad eficiente". Mathematical Systems Theory . 10 : 99– 127. doi : 10.1007/BF01683268 .
- Colas de prioridad
- Buscar árboles