Un árbol de sufijos es un árbol que contiene todos los sufijos de alguna cadena (y solo ellos). Le permite averiguar si la cadena w está incluida en la cadena original t en tiempo O (|w|) , donde |w| es la longitud de la cadena w .
|
Lema. La ubicación de w es explícita en un árbol de sufijos compacto si y solo si w es un sufijo no anidado de t o w es una rama derecha.
Prueba. . Si es explícito, entonces puede ser una hoja, un vértice de rama o una raíz (en este caso , w también es un sufijo anidado de t ).
Si es una hoja, entonces también es un sufijo t . Por lo tanto, no debe ser un sufijo anidado, porque de lo contrario aparecería en otro lugar de la cadena t : v es un sufijo de t tal que w es un prefijo de v . Este nodo no puede ser una hoja.
Si es un nodo de rama, debe haber al menos dos bordes salientes con etiquetas diferentes. Esto significa que hay dos sufijos diferentes u , v , que w es un prefijo de u y w es un prefijo de v , donde v = wxs , u = , x . Por lo tanto , w es una rama derecha.
. Si w es un sufijo no anidado de t , debe ser una hoja. Si w es una rama derecha, entonces hay dos sufijos u y v , u = wxs , v = , x , entonces w es un nodo de rama. El lema está probado .
Ahora es fácil ver por qué la respuesta a la pregunta de si la palabra w está en la cadena t se puede encontrar en el tiempo O(|w|) : uno solo necesita verificar si w es una ubicación (explícita o implícita) en cst( t ).
Las etiquetas de borde deben ser punteros a la posición en la cadena para que el árbol de sufijos consuma memoria O(n) . La etiqueta de borde (p, q) significa una subcadena o la cadena vacía si p > q.
Ukkonen introduce el nombre de bordes abiertos para los bordes que terminan en hojas. Las etiquetas de borde abierto se escriben como (p, ) en lugar de (p, |t|) , donde es la longitud, siempre mayor que |t| .
Sea T un -árbol. Sea un nodo T , v sea el sufijo w más largo tal que también sea un nodo T . Un borde sin etiqueta de a se llama enlace de sufijo . Si v = w , se llama atómico .
Declaración. En ast( t ) y cst( t$ ), donde $ t , todos los enlaces de sufijos son atómicos.
Prueba. El símbolo $ se llama símbolo de guardia . La primera parte (para ast( t )) se deriva de la definición, ya que las ubicaciones son explícitas. Para probar la segunda parte (caso cst( t )) tenemos que demostrar que para todo nodo también hay un nodo cst( t ). Si es un nodo cst( t ), entonces es un nodo hoja o rama. Si es una hoja, entonces aw no es un sufijo anidado de t . Gracias al símbolo de guardia, del lema se deduce que todos los sufijos (incluida la raíz, el sufijo vacío) son explícitos, ya que solo la raíz es un sufijo incrustado. Por tanto , w es una hoja o una raíz. Si es un nodo de rama, entonces aw es una rama derecha, al igual que w . Por lo tanto, la ubicación es explícita por el lema. La afirmación ha sido probada.
Como se desprende de esta prueba, el símbolo de guardia garantiza la existencia de hojas para todos los sufijos. Con tal carácter, no puede haber sufijos anidados que no sean vacíos. Si omitimos el carácter de guardia, algunos sufijos pueden anidarse y sus ubicaciones quedar implícitas.
Declaración. Un árbol de sufijos compacto se puede representar en una forma que requiere memoria O(n) .
Prueba. El árbol de sufijos contiene como máximo una hoja por sufijo (exactamente una con carácter de guardián). Cada nodo interno debe ser un nodo de rama, por lo que un nodo interno tiene al menos dos hijos. Cada rama aumenta el número de hojas en al menos una, por lo que tenemos como máximo n nodos internos y como máximo n hojas.
Para representar cadenas que son etiquetas de borde, usamos la indexación en la cadena de origen, como se describe anteriormente. Cada nodo tiene como máximo un padre y, por lo tanto, el número total de aristas no supera los 2n .
De manera similar, cada nodo tiene como máximo un enlace de sufijo, entonces el número total de enlaces de sufijo también está limitado a 2n . La afirmación ha sido probada.
Como ejemplo de un árbol de sufijos con 2n-1 nodos, considere el árbol de la palabra . El tamaño del árbol de sufijos atómicos para la cadena t es O( ) .
El algoritmo mcc comienza con un árbol vacío y agrega sufijos comenzando desde el más largo. El algoritmo mcc no es un algoritmo en línea, es decir, se requiere toda la línea para su funcionamiento. El correcto funcionamiento requiere que la cadena esté terminada por un carácter especial distinto de los demás, de forma que ningún sufijo sea prefijo de otro sufijo. Cada sufijo en el árbol corresponderá a una hoja. Para el algoritmo, definimos - el sufijo actual (en el paso ), ( cabeza ) - el prefijo más grande del sufijo , que también es el prefijo de otro sufijo , donde . ( cola ) definir como .
La idea clave del algoritmo mcc es la relación entre y .
Lema. Si donde es una letra del alfabeto, es una cadena (puede estar vacía), entonces es un prefijo .
Prueba. deja _ Entonces existe , , tal que es a la vez un prefijo de y un prefijo de . Entonces es un prefijo y por lo tanto es un prefijo de la cabeza . El lema está probado.
Conocemos la ubicación de , y si tenemos un enlace de sufijo, podemos saltar rápidamente a la ubicación del prefijo principal sin tener que encontrar la ruta desde la raíz del árbol. Pero es posible que la ubicación no sea explícita (si la ubicación no fue explícita en el paso anterior) y es posible que el enlace del sufijo aún no esté configurado para el nodo . La solución de McCray encuentra un nodo en dos pasos: "volver a escanear" y "escanear". Atravesamos el árbol desde el nodo hasta que encontramos un enlace de sufijo, lo seguimos y luego volvemos a escanear la ruta a la ubicación (que es simple porque sabemos la longitud y la ubicación existe, por lo que no tenemos que leer el borde completo etiquetas moviéndose hacia abajo en el árbol, solo podemos verificar las letras iniciales y la longitud de las palabras).
La figura demuestra esta idea. En lugar de intentar encontrar una ruta desde la raíz hasta el nodo , el algoritmo salta a , sigue el sufijo a , vuelve a escanear la ruta a la ubicación (posiblemente implícita) y se queda para encontrar la ruta a , atravesando carácter por carácter.
El algoritmo consta de tres partes.
1. Primero, determina la estructura del encabezado anterior, encuentra el siguiente enlace de sufijo disponible y lo sigue.
2. A continuación, vuelve a escanear la parte de la cabeza anterior para la que se conoce la longitud (esta parte se denomina ).
3. Finalmente, el algoritmo establece el enlace de sufijo para , escanea el resto (llamado ) y agrega una nueva hoja para .
Se crea un nodo de rama en la segunda fase de la nueva exploración si no existe ninguna ubicación . En este caso, el escaneo no es necesario, porque si fuera más largo que entonces sería una rama derecha, pero por el lema también es una rama derecha, por lo que el nodo ya debe existir. El nodo se crea en la tercera fase si la ubicación aún no está explícita.
Algoritmo 1 (mcc, McCreight) Entrada: cadena t 1: T : = árbol vacío; 2: cabeza 0 := ; 3: n := longitud(t) ; 4: para i := 1 to n do 5: encuentre , , tal que una. cabeza i-1 = , b. si el antepasado del nodo principal i-1 no es la raíz ( raíz ), indíquelo , de lo contrario c. y ( | cabeza i-1 |=0) 6: si ( >0) entonces 7: siga el enlace del sufijo desde el nodo hasta ; 8: finaliza si 9: := Reescanear( ) ; 10: establezca el enlace de sufijo de a 11: ( , tail i ) := Scan ( , suf i - ); 12: agregue una hoja correspondiente a la cola i ; 13: final paraTenga en cuenta que si entonces y se reconoce con la misma rapidez, como siguiendo el enlace del sufijo de acuerdo con la línea 7 del algoritmo.
El procedimiento Volver a escanear busca una ubicación . Si la ubicación aún no es explícita, se agrega un nuevo nodo. Este caso se da cuando la cabecera ( ) se ve en su totalidad: si la cabecera es más larga (y el nodo ya está definido), debe ser un prefijo de más de dos sufijos y además es rama izquierda de . Una ubicación solo puede ser explícita si ese nodo ya es un nodo de rama, y si no había una rama izquierda entonces , debe haber sido más larga porque se encontró un prefijo más largo.
El procedimiento Scan busca la profundidad del árbol y devuelve la posición.
Procedimiento 1 Reescanear (n, ) Entrada : nodo n , línea 1: i :=1; 2: mientras yo | | haz 3: encuentra la arista e=n n' , w 1 = 1 ; 4: si yo +| w |>| |+1 luego 5: k :=| | -yo +1; 6: arista dividida e con nuevo nodo m y aristas n m y m n' ; 7: devuelve m ; 8: termina si 9: i := i +| w |; 10: n := n' ; 11: finaliza mientras 12: regresa n' ; Procedimiento 2 Escaneo (n, ) Entrada : nodo n , línea 1: i :=1; 2: mientras haya una arista e = n n' , w 1 = i do 3: k :=1; 4: mientras que w k = i y k | w | hacer 5: k := k +1; 6: yo := yo +1; 7: finaliza while 8: si k >| w | entonces 9: n := n' ; 10: else 11: dividir la arista e con el nuevo nodo m y las aristas n m y m n' ; 12: retorno ( m , i ,..., ); 13: finaliza si 14: finaliza mientras 15: regresa ( n , i ,..., );El algoritmo que inventó Esko Ukkonen para construir un árbol de sufijos en tiempo lineal es probablemente el más simple de estos algoritmos. La simplicidad proviene del hecho de que el algoritmo puede considerarse inicialmente como un método simple pero ineficiente que, con algunas implementaciones de "sentido común", alcanza el nivel de los mejores algoritmos en términos de tiempo de ejecución en las peores condiciones. Lo mismo se hace en PDF con figuras .
Explicación detallada del algoritmo y la implementación en C++ : cs.mipt.ru (en ruso) y marknelson.us (en inglés)
Para el algoritmo de Ukkonen necesitamos
1) Árboles de sufijos implícitos 2) Descripción general del algoritmo 3) Optimización de algoritmosEl algoritmo de Ukkonen construye una secuencia de árboles de sufijos implícitos, el último de los cuales se convierte en un árbol de sufijos de cadena real S .
Неявное суффиксное дерево строки S — это дерево, полученное из суффиксного дерева S$ удалением всех вхождений терминального символа $ из меток дуг дерева, удалением после этого дуг без меток и удалением затем вершин, имеющих меньше двух детей. Неявное суффиксное дерево префикса S[l..i] строки S аналогично получается из суффиксного дерева для S[l..i]$ удалением символов $, дуг и вершин, как описано выше.
El árbol de sufijos implícito para cualquier cadena S tendrá menos hojas que el árbol de sufijos para la cadena S$ si y solo si al menos uno de los sufijos de S es un prefijo de otro sufijo. El terminal $ se agregó al final de la S solo para evitar esta situación. Al definir un árbol de sufijos real, este es un punto muy importante. Sin embargo, si S termina con un carácter que no aparece en ningún otro lugar de S, entonces el árbol de sufijos implícito para S tendrá una hoja para cada sufijo y, por lo tanto, es un verdadero árbol de sufijos.
Aunque es posible que el árbol de sufijos implícitos no tenga hojas para todos los sufijos, codifica todos los sufijos S: cada uno se pronuncia mediante los caracteres de algún camino desde la raíz de este árbol de sufijos implícitos. Sin embargo, si este camino no termina con una hoja, no habrá ningún marcador que indique el final del camino. Por lo tanto, los propios árboles de sufijos implícitos son algo menos informativos que los reales. Solo los usaremos como una ayuda para el algoritmo de Ukkonen para obtener un verdadero árbol de sufijos para S.
El algoritmo de Ukkonen construye un árbol implícito de sufijos Ti para cada prefijo S[l..i] de la cadena S, comenzando en T 1 e incrementando i en uno hasta que se construye T m . El árbol de sufijos real para S proviene de T m , y todo el trabajo lleva O(m) tiempo. Explicaremos el algoritmo de Ukkonen presentando primero un método mediante el cual todos los árboles se construyen en un tiempo de O(m³), y luego optimizaremos la implementación de este método para que se logre la velocidad declarada.
Para convertir esta descripción general en un algoritmo, debemos especificar exactamente cómo realizar la continuación del sufijo. Sea S[j..i] = β el sufijo de S[1..i]. En la continuación j, cuando el algoritmo encuentra el final β en el árbol actual, continúa β para asegurarse de que el sufijo βS(i + 1) esté presente en el árbol. El algoritmo opera de acuerdo con una de las siguientes tres reglas.
Regla 1. En el árbol actual, el camino β termina en una hoja. Esto significa que el camino desde la raíz etiquetada como β va al final de algún arco de "hoja" (el arco incluido en la hoja). Al cambiar el árbol, agregue el símbolo S (i + 1) al final de la etiqueta de este arco de hojas.
Regla 2. Ningún camino desde el final de la cadena β comienza con S(i + 1), pero hay al menos un camino que comienza desde allí. En este caso, se debe crear un nuevo arco de hojas, comenzando al final de β, etiquetado con el símbolo S(i + 1). En este caso, si β termina dentro del arco, se debe crear un nuevo vértice. A la hoja al final del nuevo arco de hojas se le asigna el número j. Así, en la regla 2, son posibles dos casos.
Regla 3. Algún camino desde el final de la cadena β comienza con el símbolo S(i + 1). En este caso, la cadena βS(i + 1) ya está en el árbol actual, por lo que no es necesario hacer nada (en un árbol de sufijos implícitos, el final del sufijo no necesita marcarse explícitamente).
Deje que se dé el texto y un conjunto de patrones lleguen a la entrada. Después de construir un árbol de sufijos a partir del texto utilizando el algoritmo de Ukkonen, cada patrón se puede buscar de la siguiente manera:
|
Un árbol de sufijos se puede construir sobre un conjunto de cadenas con o sin concatenación de cadenas.
|
Este enfoque es problemático debido a la presencia de sufijos sintéticos, pero esto se resuelve reduciendo el segundo índice del par de sufijos en los arcos a los vértices de las hojas.
No habrá sufijos sintéticos en este algoritmo.
|
Debe tenerse en cuenta que las etiquetas comprimidas en diferentes arcos pueden hacer referencia a diferentes cadenas, por lo que se deben almacenar tres números en los arcos.
Los sufijos de dos cadenas pueden coincidir, pero al mismo tiempo, ningún sufijo será un prefijo de otro sufijo (debido a centinela). Luego, la hoja apunta a todas las cadenas y posiciones iniciales de ese sufijo.
Para resolver el problema de encontrar un conjunto de patrones, existe el algoritmo Aho-Korasik. Encuentra todas las ocurrencias para - la longitud total de los patrones, T - la longitud del texto, k - el número de ocurrencias.
Asintóticamente, encontrar todas las ocurrencias en un árbol de sufijos toma la misma cantidad de tiempo. Pero el hecho es que Aho-Korasik usa la memoria para el árbol de claves , el tiempo para construir y el tiempo para buscar . Pero el árbol de sufijos ocupa la memoria , el tiempo , la construcción y la búsqueda.
Es decir, si hay muchas muestras y más que el texto, entonces el árbol de sufijos es pequeño, pero lleva mucho tiempo buscar. De lo contrario, Aho-Korasik, cuando los patrones son cortos y el texto es más grande, ocupa menos memoria, pero el árbol de sufijos se busca más rápido.
Así, la elección entre uno u otro depende de la frontera del tiempo o de la memoria.
Árbol (estructura de datos) | |
---|---|
Árboles binarios | |
Árboles binarios autoequilibrados |
|
árboles B |
|
árboles de prefijo |
|
Partición binaria del espacio | |
Árboles no binarios |
|
Rompiendo el espacio |
|
Otros árboles |
|
Algoritmos |
Instrumentos de cuerda | |
---|---|
Medidas de similitud de cadenas | |
Búsqueda de subcadena | |
palíndromos | |
Alineación de secuencia | |
Estructuras de sufijos | |
Otro |