Articulo de referencia

Árbol de raíz

Un ejemplo de árbol de raíces de palabras de un trabalenguas En informática , un árbol radix (también llamado trie radix , árbol de prefijos compacto o trie comprimido ) es una ...

Un ejemplo de árbol de raíces de palabras de un trabalenguas

En informática , un árbol radix (también llamado trie radix , árbol de prefijos compacto o trie comprimido ) es una estructura de datos que representa un trie (árbol de prefijos) optimizado en espacio, donde cada nodo que es el único hijo se fusiona con su padre. El número de hijos de cada nodo interno es como máximo el valor absoluto r del árbol radix, donde r = 2 x para algún entero x ≥ 1. A diferencia de los árboles regulares, las aristas pueden etiquetarse con secuencias de elementos, así como con elementos individuales. Esto hace que los árboles radix sean mucho más eficientes para conjuntos pequeños (especialmente si las cadenas son largas) y para conjuntos de cadenas que comparten prefijos largos.

A diferencia de los árboles regulares (donde las claves completas se comparan en masa desde su inicio hasta el punto de desigualdad), la clave en cada nodo se compara fragmento a fragmento, donde la cantidad de bits en ese fragmento en ese nodo es la base r del árbol de prefijos. Cuando r es 2, el árbol de prefijos es binario (es decir, se compara la porción de 1 bit de la clave de ese nodo), lo que minimiza la dispersión a expensas de maximizar la profundidad del árbol, es decir, maximizar hasta la fusión de cadenas de bits no divergentes en la clave. Cuando r ≥ 4 es una potencia de 2, entonces el árbol de prefijos es un árbol r -ario, lo que disminuye la profundidad del árbol de prefijos a expensas de la posible dispersión.

Como optimización, las etiquetas de los bordes se pueden almacenar en tamaño constante utilizando dos punteros a una cadena (para el primer y último elemento). [ 1 ]

Tenga en cuenta que, si bien los ejemplos de este artículo muestran cadenas como secuencias de caracteres, el tipo de los elementos de la cadena se puede elegir arbitrariamente; por ejemplo, como un bit o un byte de la representación de la cadena cuando se utilizan codificaciones de caracteres multibyte o Unicode .

Aplicaciones

Los árboles radix son útiles para construir matrices asociativas con claves que pueden expresarse como cadenas. Encuentran una aplicación particular en el área de enrutamiento IP , [ 2 ] [ 3 ] [ 4 ] donde la capacidad de contener amplios rangos de valores con pocas excepciones es particularmente adecuada para la organización jerárquica de direcciones IP . [ 5 ] También se utilizan para índices invertidos de documentos de texto en la recuperación de información .

Operaciones

Los árboles de radix admiten operaciones de inserción, eliminación y búsqueda. La inserción añade una nueva cadena al trie, procurando minimizar la cantidad de datos almacenados. La eliminación borra una cadena del trie. Las operaciones de búsqueda incluyen (pero no se limitan necesariamente a) la búsqueda exacta, la búsqueda del predecesor, la búsqueda del sucesor y la búsqueda de todas las cadenas con un prefijo. Todas estas operaciones son de complejidad O( k ), donde k es la longitud máxima de todas las cadenas del conjunto, medida en bits igual a la base del trie de radix.

Buscar

Encontrar una cadena en un árbol Patricia

La operación de búsqueda determina si una cadena existe en un trie. La mayoría de las operaciones modifican este enfoque de alguna manera para gestionar sus tareas específicas. Por ejemplo, el nodo donde termina una cadena puede ser importante. Esta operación es similar a la de los tries, excepto que algunas aristas consumen varios elementos.

El siguiente pseudocódigo presupone que estos métodos y miembros existen.

Borde

  • Nodo targetNode
  • etiqueta de cadena

Nodo

  • Matriz de aristas aristas
  • función esHoja()
función buscar( cadena x) { // Comienza en la raíz sin elementos encontrados Nodo traverseNode := raíz ; int elementsFound := 0; // Recorre hasta que se encuentre una hoja o no sea posible continuar mientras (traverseNode != null && !traverseNode.isLeaf() && elementsFound < x.length) { // Obtener la siguiente arista a explorar en función de los elementos aún no encontrados en x Edge nextEdge := seleccionar arista de traverseNode.edges donde edge.label es un prefijo de x.suffix(elementsFound) // x.suffix(elementsFound) devuelve los últimos (x.length - elementsFound) elementos de x// ¿Se encontró una arista? if (nextEdge != null ) { // Establecer el siguiente nodo a explorar traverseNode := nextEdge.targetNode; // Incrementa los elementos encontrados en función de la etiqueta almacenada en el borde. elementosEncontrados += nextEdge.label.length; } demás { // Finalizar el bucle traverseNode := null ; } } // Se encuentra una coincidencia si llegamos a un nodo hoja y hemos utilizado exactamente x.length elementos return (traverseNode != null && traverseNode.isLeaf() && elementsFound == x.length); }

Inserción

Para insertar una cadena, recorremos el árbol hasta que no podamos avanzar más. En ese momento, añadimos una nueva arista saliente etiquetada con todos los elementos restantes de la cadena de entrada, o, si ya existe una arista saliente que comparte un prefijo con la cadena de entrada restante, la dividimos en dos aristas (la primera etiquetada con el prefijo común) y continuamos. Este paso de división garantiza que ningún nodo tenga más hijos que elementos posibles en la cadena.

A continuación se muestran varios casos de inserción. Nótese que r simplemente representa la raíz. Se asume que las aristas pueden etiquetarse con cadenas vacías para finalizar las cadenas cuando sea necesario y que la raíz no tiene aristas entrantes. (El algoritmo de búsqueda descrito anteriormente no funcionará al usar aristas con cadenas vacías).

Supresión

Para eliminar una cadena x de un árbol, primero localizamos la hoja que representa a x. Luego, suponiendo que x existe, eliminamos el nodo hoja correspondiente. Si el padre de nuestro nodo hoja tiene solo otro hijo, la etiqueta de entrada de ese hijo se agrega a la etiqueta de entrada del padre y el hijo se elimina.

Operaciones adicionales

  • Encuentra todas las cadenas con un prefijo común: Devuelve una matriz de cadenas que comienzan con el mismo prefijo.
  • Buscar predecesor: Localiza la cadena más larga menor que una cadena dada, según el orden lexicográfico.
  • Buscar sucesor: Localiza la cadena más pequeña mayor que una cadena dada, según el orden lexicográfico.

Historia

La estructura de datos fue inventada en 1968 por Donald R. Morrison, [ 6 ] con quien se asocia principalmente, y por Gernot Gwehenberger. [ 7 ]

Donald Knuth , en las páginas 498-500 del volumen III de The Art of Computer Programming , los denomina "árboles de Patricia", presumiblemente por el acrónimo del título del artículo de Morrison: "PATRICIA - Algoritmo práctico para recuperar información codificada en alfanumérico". Hoy en día, los árboles de Patricia se consideran árboles de base 2, lo que significa que cada bit de la clave se compara individualmente y cada nodo es una rama bidireccional (es decir, izquierda frente a derecha).

Comparación con otras estructuras de datos

(En las siguientes comparaciones, se supone que las claves tienen una longitud k y que la estructura de datos contiene n miembros).

A diferencia de los árboles equilibrados , los árboles radix permiten la búsqueda, inserción y eliminación en tiempo O( k ) en lugar de O(log n ). Esto no parece una ventaja, ya que normalmente k ≥ log n , pero en un árbol equilibrado cada comparación es una comparación de cadenas que requiere un tiempo de O( k ) en el peor de los casos, muchas de las cuales son lentas en la práctica debido a los largos prefijos comunes (en el caso de que las comparaciones comiencen al inicio de la cadena). En un trie, todas las comparaciones requieren un tiempo constante, pero se necesitan m comparaciones para buscar una cadena de longitud m . Los árboles radix pueden realizar estas operaciones con menos comparaciones y requieren muchos menos nodos.

Los árboles Radix también comparten las desventajas de los tries: dado que solo se pueden aplicar a cadenas de elementos o a elementos con una asignación reversible eficiente a cadenas, carecen de la generalidad completa de los árboles de búsqueda balanceados, que se aplican a cualquier tipo de dato con un orden total . Una asignación reversible a cadenas puede utilizarse para producir el orden total requerido para los árboles de búsqueda balanceados, pero no al revés. Esto también puede ser problemático si un tipo de dato solo proporciona una operación de comparación, pero no una operación de (des) serialización .

Se suele decir que las tablas hash tienen tiempos de inserción y eliminación esperados de O(1), pero esto solo es cierto si se considera que el cálculo del hash de la clave es una operación de tiempo constante. Al tener en cuenta el hash de la clave, las tablas hash tienen tiempos de inserción y eliminación esperados de O( k ), pero pueden tardar más en el peor de los casos dependiendo de cómo se gestionen las colisiones. Los árboles radix tienen tiempos de inserción y eliminación en el peor de los casos de O( k ). Las operaciones de sucesor/predecesor de los árboles radix tampoco están implementadas en las tablas hash.

Variantes

Una extensión común de los árboles radix utiliza nodos de dos colores: "negro" y "blanco". Para comprobar si una cadena dada está almacenada en el árbol, la búsqueda comienza desde la raíz y sigue las aristas de la cadena de entrada hasta que no se puede avanzar más. Si la cadena de búsqueda se ha procesado y el nodo final es negro, la búsqueda ha fallado; si es blanco, la búsqueda ha tenido éxito. Esto permite añadir al árbol un amplio rango de cadenas con un prefijo común, utilizando nodos blancos, y luego eliminar un pequeño conjunto de "excepciones" de forma eficiente en cuanto a espacio, insertándolas mediante nodos negros.

El HAT-trie es una estructura de datos optimizada para caché basada en árboles radix que ofrece almacenamiento y recuperación de cadenas eficientes, así como iteraciones ordenadas. Su rendimiento, tanto en tiempo como en espacio, es comparable al de la tabla hash optimizada para caché . [ 8 ] [ 9 ]

Un trie PATRICIA es una variante especial del trie binario (de base 2), en el que, en lugar de almacenar explícitamente cada bit de cada clave, los nodos almacenan solo la posición del primer bit que diferencia dos subárboles. Durante el recorrido, el algoritmo examina el bit indexado de la clave de búsqueda y elige el subárbol izquierdo o derecho según corresponda. Entre las características notables del trie PATRICIA se incluye que solo requiere la inserción de un nodo por cada clave única almacenada, lo que lo hace mucho más compacto que un trie binario estándar. Además, dado que las claves reales ya no se almacenan explícitamente, es necesario realizar una comparación completa de claves en el registro indexado para confirmar una coincidencia. En este sentido, PATRICIA guarda cierta semejanza con la indexación mediante una tabla hash. [ 6 ]

El árbol radix adaptativo es una variante del árbol radix que integra tamaños de nodo adaptativos. Una desventaja importante de los árboles radix convencionales es el uso de espacio, ya que utilizan un tamaño de nodo constante en cada nivel. La principal diferencia entre el árbol radix y el árbol radix adaptativo radica en el tamaño variable de cada nodo, que depende del número de elementos hijos y aumenta al añadir nuevas entradas. Por lo tanto, el árbol radix adaptativo permite un mejor uso del espacio sin reducir su velocidad. [ 10 ] [ 11 ] [ 12 ]

Una práctica común es flexibilizar el criterio de no permitir padres con un solo hijo en situaciones donde el padre representa una clave válida en el conjunto de datos. Esta variante del árbol radix logra una mayor eficiencia espacial que la que solo permite nodos internos con al menos dos hijos. [ 13 ]

Véase también

Referencias

  1. Morin, Patrick. "Estructuras de datos para cadenas" (PDF) . Consultado el 15 de abril de 2012 .
  2. "rtfree(9)" . www.freebsd.org . Consultado el 23-10-2016 .
  3. Los Regentes de la Universidad de California (1993). "/sys/net/radix.c" . Referencia cruzada de BSD . NetBSD . Consultado el 25 de julio de 2019. Rutinas para construir y mantener árboles radix para búsquedas de enrutamiento.
  4. "Árboles Radix/Patricia genéricos, atómicos y sin bloqueo" . NetBSD . 2011.
  5. Knizhnik, Konstantin. "Patricia Tries: Un mejor índice para búsquedas de prefijos" , Dr. Dobb's Journal , junio de 2008.
  6. 1 2 Morrison, Donald R. PATRICIA -- Algoritmo práctico para recuperar información codificada en caracteres alfanuméricos
  7. G. Gwehenberger, Anwendung einer binären Verweiskettenmethode beim Aufbau von Listen. Elektronische Rechenanlagen 10 (1968), págs. 223-226
  8. Askitis, Nikolas; Sinha, Ranjan (2007). HAT-trie: Una estructura de datos basada en Trie con gestión de caché para cadenas de caracteres . Vol. 62. pp. 97–105 . ISBN   978-1-920682-43-9.{{cite book}}: |journal=ignorado ( ayuda )
  9. Askitis, Nikolas; Sinha, Ranjan (octubre de 2010). "Ingeniería de tries escalables, eficientes en caché y espacio para cadenas". The VLDB Journal . 19 (5): 633– 660. doi : 10.1007/s00778-010-0183-9 . S2CID 432572 . 
  10. ^ Kemper, Alfons; Eickler, André (2013). Datenbanksysteme, Eine Einführung . vol. 9. Oldenburgo. págs. 604–605 . ISBN   978-3-486-72139-3.
  11. "armon/libart: árboles radix adaptativos implementados en C" . GitHub . Consultado el 17 de septiembre de 2014 .
  12. Viktor Leis; et al. (2013). "El árbol radix adaptativo: indexación ARTful para bases de datos en memoria principal". 2013 IEEE 29th International Conference on Data Engineering (ICDE) . pp. 38–49 . doi : 10.1109/ICDE.2013.6544812 . ISBN   978-1-4673-4910-9. S2CID 14030601 . 
  13. ¿Puede un nodo de un árbol Radix que representa una clave válida tener un hijo?

Implementaciones

  • Implementación de FreeBSD , utilizada para paginación, reenvío y otras funciones.
  • Implementación del núcleo de Linux , utilizada para la caché de páginas, entre otras cosas.
  • La biblioteca estándar de GNU C++ tiene una implementación de trie.
  • Implementación en Java de un árbol radix concurrente , por Niall Gallagher
  • Implementación en C# de un árbol Radix
  • Biblioteca de plantillas de algoritmos prácticos , una biblioteca de C++ basada en PATRICIA tries (VC++ >=2003, GCC G++ 3.x), por Roman S. Klyujkov.
  • Implementación de la clase plantilla Patricia Trie en C++ , por Radu Gruian
  • Implementación de la biblioteca estándar de Haskell "basada en árboles Patricia big-endian". Código fuente navegable por la web .
  • Implementación de Patricia Trie en Java , por Roger Kapsi y Sam Berlin
  • Árboles de bits críticos derivados del código C por Daniel J. Bernstein
  • Implementación de Patricia Trie en C , en libcprops
  • Árboles Patricia  : conjuntos y mapas eficientes sobre enteros (módulo ptmap) en OCaml , por Jean-Christophe Filliâtre
  • Implementación de Radix DB (Trie Patricia) en C , por GB Versiani
  • Libart - Árboles de radix adaptativos implementados en C , por Armon Dadgar con otros colaboradores (Código abierto, licencia BSD de 3 cláusulas)
  • Implementación en Nim de un árbol de bits críticos
  • rax , una implementación de árbol radix en ANSI C por Salvatore Sanfilippo (el creador de REDIS )