Algoritmo de Ukkonen

De Wikipedia, la enciclopedia libre
Saltar a: navegación, búsqueda

En el campo de la Ciencia de la Computación, el algoritmo de Ukkonen es un algoritmo on-line, con costo linear, para construir un árbol de sufijos de una cadena S. Este algoritmo fue propuesto por Esko Ukkonen en el año 1995.

Anteriormente existían dos algoritmos capaces de construir el árbol de sufijos de una cadena S en tiempo linear, estos son el algoritmo de Weiner (1973), el algoritmo de McCreight (1976). Pero el algoritmo de Ukkonen se destaca por ser más sencillo y por tener la característica de ser on-line.

Explicación del Algoritmo[editar]

Para explicar el algoritmo de Ukkonen partiremos de una implementación ingenua de este algoritmo que es O(n^3) a la cual se le hacen sucesivas mejoras hasta obtener el O(n).

Sea Ti el árbol de sufijos implícito de la cadena S[1..i], con i desde 1 hasta n, el algoritmo de Ukkonen aplicado a la cadena S$ de longitud n, construye sucesivamente T1, T2, ..., Tn. La ejecución del algoritmo de Ukkonen se divide en n fases, en la fase i+1 se construye Ti+1 modificando Ti, como el último carácter de S es $ se cumple que Tn es el árbol de sufijos de S.

Cada fase i+1 es a su vez dividida en i+1 extensiones. En la extensión j-ésima de la fase i+1 se busca el final del camino desde la raíz etiquetado con la subcadena S[j...i] y se "extiende" dicho camino con el carácter S[i+1].

Reglas de extensión[editar]

A continuación se especifica cómo se realizan las extensiones.

Sea S[j ..i]=a un sufijo de S[1..i]. En la extensión j, el algoritmo encuentra el final de a y lo extiende con el carácter c=S[i+1] de acuerdo a una de las siguientes reglas:

Regla 1: (Extensión en una hoja)

El camino de a termina en una hoja. El carácter c es añadido al final de la etiqueta de la arista incidente en dicha hoja.

Regla 2: (Extensión en una arista o nodo interno)

El camino de a no termina en una hoja, pero ningún camino desde el final de a inicia con el carácter c. En este caso una nueva arista etiquetada con c es creada desde el final de a, dicha arista incide en una nueva hoja etiquetada con j. Si a termina en el interior de una arista, debe crearse además un nuevo nodo que corte dicha arista en el final de a.

Regla 3: (El carácter c ya aparece, no hay que hacer nada) Algún camino desde el final de a inicia con el carácter c. No es necesario hacer nada.

Suffix links[editar]

Definición: Sea un v nodo interno con etiqueta xa donde x denota un carácter y a denota un string posiblemente vacío, si existe otro nodo s(v) con etiqueta a, entonces un puntero de v a s(v) es llamado un suffix link y se denota (v,s(v)).

Obsérvese que la definición no tiene sentido cuando v es la raíz, pues esta no está etiquetada con una cadena de la forma xa.

Lema: Si al final de una fase, la cadena xa pertenece al árbol, entonces la cadena a también pertenece.

Demostración: Si xa se insertó en la extensión j de alguna fase i, en la extensión j+1 de dicha fase se insertó a.

Teorema: Si un nuevo nodo interno v con etiqueta xa es añadido al árbol en la extensión j de la fase i+1, entonces se tendrá uno de los siguientes casos:

  • El camino etiquetado a termina en un nodo interno.
  • En la extensión j+1 un nodo interno al final del camino a será creado.

Demostración: Un nuevo nodo es creado en la extensión j de la fase i+1 solamente cuando la extensión ocurre en una arista (regla 2). Por tanto el camino etiquetado con xa continua con un carácter d distinto de c=S[i+1]. Al inicio de la extensión j+1, por el lema anterior, se cumple que hay un camino etiquetado con a en el árbol y este continua con el carácter d, (y posiblemente con otros caracteres). Si el camino etiquetado con a continua con otros caracteres además de d, entonces este ya termina en un nodo interno (primera situación del teorema). Por otra parte si el camino etiquetado con a continua solamente con el carácter d, entonces en la extensión j+1, se aplicará la regla 2 y se creará un nodo interno al final de dicho camino (segunda situación del teorema).

Corolario: Al final de cada fase todos los nodos internos exceptuando la raíz tendrán un suffix link hacia otro nodo interno.

Usando los Suffix links para construir Ti+1[editar]

Recordemos que en la extensión j de la fase i+1 el algoritmo localiza el sufijo S[j..i] de S[1..i] con 1 \le j \le i+1, y luego realiza la extensión adecuada en dicho punto. Los suffix links se utilizarán para acortar el camino a la hora de localizar S[j..i] en el árbol. Las primera extensión se puede realizar macheando el string S[1..i] en el árbol, sin la ayuda de suffix links. Para realizar la extensión j+1, debemos analizar los posibles casos donde ocurrió la extensión j.

  • Si j ocurrió en un nodo interno v (distinto de la raíz), este estará etiquetado con el string xa=S[j..i] y tendrá un suffix link hacia un nodo s(v) etiquetado con el string a=S[j+1..i] (por ser un nodo interno distinto de la raíz, creado en una extensión anterior a la extensión j de la fase i). Por tanto la extensión j+1 se realizará en el nodo s(v).
  • Si j ocurrió en una hoja u, sea v el padre de u, y sea L la etiqueta de la arista (u,v). Si u es la raíz, la extensión j+1 se debe realizar macheando la cadena S[j+1..i] desde la raíz. Por otra parte si u no es la raíz, entonces tiene un suffix link hacia el nodo s(u), para realizar la extensión j+1, basta machear la cadena L a partir de s(v).

-Si j ocurrió en una arista, sea v el nodo creado para partir dicha arista, sea u el padre v y sea L la etiqueta de la arista (u,v). La extensión j+1 se realiza de forma análoga al caso anterior.

Obsérvese que la extensión j no puede ocurrir en la raíz pues esta no es la última extensión de la fase.

Skip/count trick[editar]

Cuando se desea buscar la localización del final de una cadena S en el árbol y se tiene la certeza de que esta se encuentra (como es el caso de las búsquedas que se realizan en las extensiones), no es necesario machear la cadena S carácter a carácter, en estos casos es posible utilizar el skip/count trick que permite realizar la búsqueda en un orden linear respecto al número de nodos del camino.

Sea cur el nodo en el que se encuentra el algoritmo, intentando localizar la cadena S, el skip/count trick ejecuta sucesivamente el siguiente procedimiento hasta obtener la posición buscada:

Si S es la cadena vacía, cur es la posición buscada.
Sino (cur,v) es la arista etiqueta L comparte el primer carácter con S (tiene que existir y ser única)
    Si |L|<|S|, la posición buscada es el carácter |S|-ésimo de la arista (cur,v).
    Sino hacer cur=v y eliminar los primeros |L| caracteres de S

Lema: Sea (v,s(v)) un suffix link atravesado durante el algoritmo de Ukkonen. En ese momento profundidad(v) está acotada por 1+profundidad(s(v)).

Demostración: Cuando el link v,s(v)) (es atravesado, todo nodo interno u ancestro de v, con etiqueta xa (donde x es un carácter y a una cadena posiblemente vacía) tiene un suffix link hacia un nodo s(u), etiquetado con a, ancestro de s(v). Esta correspondencia es además unívoca. El único ancestro que puede tener v sin su correspondiente ancestro para s(v) es la raíz.

Teorema: Usando el skip/count trick, toda fase del algoritmo de Ukkonen toma un tiempo O(n) donde |S|=n.

Demostración: hay i \le n extensiones en la fase i, en una extensión el algoritmo sube en el árbol a lo sumo un nodo, atraviesa a lo sumo un suffix link, baja en el árbol cierto número de nodos, aplica la extensión y añada a lo sumo un suffix link. Todas las operaciones distintas del descenso en el árbol toman tiempo constante. Solo necesitamos analizar la suma de los tiempos de realizar los descensos de todas las extensiones en la fase.

Para ello nos centraremos en la profundidad del nodo v donde el algoritmo termina la fase. Al inicio de la fase se parte de la raíz, que tiene profundidad 0, como i \le n, en la fase se realizan a lo sumo 2*n descensos de profundidad (cuando se sube al padre o se atraviesa un suffix link), pero la profundidad del v está acotada por n, por tanto el número de aumentos de profundidad está acotado por 3*n ya que 0-2n+3n=n, y como cada descenso en el árbol aumenta la profundidad, el número de descensos en toda la fase está acotado por 3*n. Por tanto el tiempo que toma realizar una fase es O(n).

Corolario: El algoritmo de Ukkonen puede ser implementado usando suffix links en tiempo O(n^2).

Extensiones implícitas[editar]

Observación: Una vez que la regla 3 aplica en la extensión j de la fase i, la regla 3 aplicará en el resto de las extensiones de la fase i.

Esto es debido a que cuando la regla 3 aplica, es porque la cadena S[j..i] pertenece al árbol antes de realizar la extensión j y por tanto las cadenas S[j + 1..i],...,S[i..i] también pertenecen al árbol, por tanto en las extensiones j+1,j+2,...,i, la regla 3 también aplicará.

Observación: La extensión j+1 crea un suffix link solo cuando en la extensión j aplica la regla 2.

Stop Trick[editar]

Es posible terminar la fase i la primera vez que la regla 3 aplique. Si esto ocurre en la extensión j, se dice que las extensiones j+1,...,i se realizaron de forma implícita.

Observación: Sea j' la primera extensión de la fase i en la que la regla 3 aplica, se cumple que las primeras j'-1 extensiones de la fase i solo pudieron aplicar las reglas 1 o 2. Ambas reglas determinan una hoja etiquetada con el sufijo de S[1..i] que se extendió. En la fase i+1 las primeras j'-1 extensiones ocurrirán en las hojas determinadas por las primeras j'-1 extensiones de la fase i. Por tanto en las primeras j'-1 extensiones de la fase i+1 aplicará la regla 1.

Observación: Cuando la regla 1 aplica en la fase i, en una hoja v, se sustituye la etiqueta [p,i] de la arista incidente en la hoja v por [p,i+1]. Esto da lugar a la siguiente mejora.

Pointer Trick[editar]

Cada vez que una hoja es creada en la fase i, se etiqueta la arista incidente a dicha hoja con el par [p,i], el pointer trick propone etiquetar dicha arista con el par [p,e], donde e es un puntero al número que identifica la fase actual. De esta forma no es necesario hacer nada cuando aplica la regla 1. Como se tiene que las primeras en las primeras j'-1 extensiones de la fase i+1 aplicará la regla 1, es posible comenzar la fase i+1 a partir de la extensión j', dejando que las primeras j'-1 extensiones, se realicen de forma implícita.

Teorema: El algoritmo de Ukkonen construye el árbol de sufijos de S, con los suffix links con costo espacial y temporal O(n).

Demostración: El tiempo para realizar todas las extensiones implícitas a lo largo del algoritmo es constante.

Consideremos el índice j correspondiente a la extensión explicita actual, en toda la ejecución del algoritmo de Ukkonen j nunca decrece, aunque sí se mantiene invariante cuando pasa de una fase a la siguiente. Como solo hay |S|=n fases, el número de extensiones explícitas está acotado por n. El algoritmo por tanto ejecuta a lo sumo 2*n extensiones explícitas.

Como se explicó anteriormente el costo temporal de una extensión explícita depende del costo temporal de realizar el descenso en el árbol en dicha extensión. Para acotar el número de nodos recorridos en los descensos en el árbol durante el algoritmo de Ukkonen, es importante observar, que el nodo actual no varía cuando el algoritmo cambia de fase, la acotación se efectúa de forma análoga a como se hizo en la demostración del teorema anterior.

Referencias[editar]

  • E. Ukkonen. (1995). On-line construction of suffix trees. Algorithmica 14(3):249-260. PDF | PDF with figures

Enlaces Extermos[editar]