La más larga subsecuencia común ( LCS ) problema es el problema de encontrar la más larga subsecuencia común a todas las secuencias en un conjunto de secuencias (a menudo apenas dos secuencias). Difiere del problema de subcadena más común : a diferencia de las subcadenas, no se requieren subsecuencias para ocupar posiciones consecutivas dentro de las secuencias originales. El problema de subsecuencia común más largo es un problema clásico de ciencias de la computación , la base de programas de comparación de datos como la utilidad diff , y tiene aplicaciones en lingüística computacional y bioinformática . También es ampliamente utilizado porsistemas de control de revisión como Git para conciliar múltiples cambios realizados en una colección de archivos controlada por revisión.
Complejidad [ editar ]
Para el caso general de un número arbitrario de secuencias de entrada, el problema es NP-hard . [1] Cuando el número de secuencias es constante, el problema se puede resolver en tiempo polinómico mediante programación dinámica (ver Solución a continuación). Asume que tienes secuencias de longitudes . Una búsqueda ingenua probaría cada uno de lossubsecuencias de la primera secuencia para determinar si también son subsecuencias de las secuencias restantes; cada subsecuencia se puede probar en tiempo lineal en las longitudes de las secuencias restantes, por lo que el tiempo para este algoritmo sería
Para el caso de dos secuencias de n y m elementos, el tiempo de funcionamiento del enfoque de programación dinámica es O ( n × m ). [2] Para un número arbitrario de secuencias de entrada, el enfoque de programación dinámica da una solución en
Existen métodos con menor complejidad, [3] que a menudo dependen de la longitud del LCS, el tamaño del alfabeto o ambos.
Tenga en cuenta que el LCS no es necesariamente único; por ejemplo, el LCS de "ABC" y "ACB" es "AB" y "AC". De hecho, el problema de LCS a menudo se define como encontrar todas las subsecuencias comunes de una longitud máxima. Este problema tiene inherentemente una mayor complejidad, ya que el número de tales subsecuencias es exponencial en el peor de los casos, [4] incluso para solo dos cadenas de entrada.
Solución para dos secuencias [ editar ]
El problema de LCS tiene una subestructura óptima : el problema se puede dividir en "subproblemas" más pequeños y simples, que se pueden dividir en subproblemas aún más simples, y así sucesivamente, hasta que, finalmente, la solución se vuelve trivial. El problema de LCS también tiene subproblemas superpuestos : la solución a subproblemas de alto nivel a menudo reutiliza subproblemas de nivel inferior. Los problemas con estas dos propiedades (subestructura óptima y subproblemas superpuestos) pueden abordarse mediante una técnica de resolución de problemas llamada programación dinámica , en la que las soluciones de subproblemas se memorizan en lugar de calcularse una y otra vez. El procedimiento requiere memorización: guardar las soluciones a un nivel de subproblema en una tabla (análogo a escribirlas en una nota, de ahí el nombre) para que las soluciones estén disponibles para el siguiente nivel de subproblemas. Este método se ilustra aquí.
Prefijos [ editar ]
Los subproblemas se vuelven más simples a medida que las secuencias se acortan. Las secuencias más cortas se describen convenientemente usando el término prefijo . Un prefijo de una secuencia es la secuencia con el final cortado. Sea S la secuencia (AGCA). Entonces, la secuencia (AG) es uno de los prefijos de S . Los prefijos se denotan con el nombre de la secuencia, seguido de un subíndice para indicar cuántos caracteres contiene el prefijo. [5] El prefijo (AG) se denota S 2 , ya que contiene los 2 primeros elementos de S . Los posibles prefijos de S son
- S 1 = (A)
- S 2 = (AG)
- S 3 = (AGC)
- S 4 = (AGCA).
La solución al problema LCS para dos secuencias arbitrarias, X y Y , asciende a la construcción de alguna función, LCS ( X , Y ), que da la más larga subsecuencias común para X y Y . Esa función se basa en las siguientes dos propiedades.
Primera propiedad [ editar ]
Supongamos que dos secuencias terminan en el mismo elemento. Para encontrar su LCS, acorte cada secuencia eliminando el último elemento, encuentre el LCS de las secuencias acortadas, y a ese LCS agregue el elemento eliminado.
- Por ejemplo, aquí hay dos secuencias que tienen el mismo último elemento: (BANANA) y (ATANA).
- Eliminar el mismo último elemento. Repita el procedimiento hasta que no encuentre un último elemento común. La secuencia eliminada será (ANA).
- Las secuencias ahora bajo consideración: (BAN) y (AT)
- El LCS de estas dos últimas secuencias es, por inspección, (A).
- Agregue el elemento eliminado, (ANA), dando (AANA), que, por inspección, es el LCS de las secuencias originales.
En general, para cualquier secuencia de X y de Y de longitud n y m , si denotamos sus elementos x 1 a x n y y 1 a y m y sus prefijos X 1 a X n-1 y Y 1 a Y m-1 , entonces podemos decir esto:
- Si: x n = y m
- entonces: LCS ( X n , Y m ) = LCS ( X n-1 , Y m-1 ) ^ x n
donde caret ^ indica que el siguiente elemento, x n , se agrega a la secuencia. Tenga en cuenta que el LCS para X n e Y m implica determinar el LCS de las secuencias más cortas, X n-1 e Y m-1 .
Segunda propiedad [ editar ]
Suponga que las dos secuencias X e Y no terminan en el mismo símbolo. Entonces el LCS de X e Y es la más larga de las dos secuencias LCS (X n , Y m-1 ) y LCS (X n-1 , Y m ).
Para comprender esta propiedad, considere las dos secuencias siguientes:
secuencia X: ABCDEFG (n elementos)
secuencia Y: BCDGK (m elementos)
secuencia Y: BCDGK (m elementos)
El LCS de estas dos secuencias termina con una G (el último elemento de la secuencia X) o no.
Caso 1: la LCS termina con una G
Entonces no puede terminar con una K. Por lo tanto, no está de más eliminar la K de la secuencia Y: si K estuviera en la LCS, sería su último carácter; Como consecuencia, K no está en la LCS. Entonces podemos escribir: LCS (X n , Y m ) = LCS (X n , Y m-1 ).
Entonces no puede terminar con una K. Por lo tanto, no está de más eliminar la K de la secuencia Y: si K estuviera en la LCS, sería su último carácter; Como consecuencia, K no está en la LCS. Entonces podemos escribir: LCS (X n , Y m ) = LCS (X n , Y m-1 ).
Caso 2: el LCS no termina con una G
Entonces no está de más eliminar la G de la secuencia X (por el mismo motivo que el anterior). Y luego podemos escribir: LCS (X n , Y m ) = LCS (X n-1 , Y m ).
Entonces no está de más eliminar la G de la secuencia X (por el mismo motivo que el anterior). Y luego podemos escribir: LCS (X n , Y m ) = LCS (X n-1 , Y m ).
En cualquier caso, el LCS que estamos buscando es uno de LCS (X n , Y m-1 ) o LCS (X n-1 , Y m ). Esos dos últimos LCS son subsecuencias comunes a X e Y. LCS (X, Y) es el más largo. Por lo tanto, su valor es la secuencia más larga de LCS (X n , Y m-1 ) y LCS (X n-1 , Y m ).
Función LCS definida [ editar ]
Deje que dos secuencias se definan de la siguiente manera: y . Los prefijos de son ; los prefijos de son . Dejar representa el conjunto de subsecuencias comunes más largas de prefijos y . Este conjunto de secuencias viene dado por lo siguiente.
Para encontrar las subsecuencias más largas comunes a y , compara los elementos y . Si son iguales, entonces la secuencia se extiende por ese elemento, . Si no son iguales, entonces la más larga de las dos secuencias,y , Es retenido. (Si ambos tienen la misma longitud, pero no son idénticos, ambos se conservan). Observe que los subíndices se reducen en 1 en estas fórmulas. Eso puede resultar en un subíndice de 0. Dado que los elementos de secuencia están definidos para comenzar en 1, fue necesario agregar el requisito de que el LCS está vacío cuando un subíndice es cero.
Ejemplo trabajado [ editar ]
Se encontrará la subsecuencia más larga común a R = (GAC) y C = (AGCAT). Debido a que la función LCS usa un elemento "cero", es conveniente definir prefijos cero que estén vacíos para estas secuencias: R 0 = Ø; y C 0 = Ø. Todos los prefijos se colocan en una mesa con C en la primera fila (lo que es una c OLUMNA cabecera) y R en la primera columna (lo que es una r ow cabecera).
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | Ø | Ø | Ø | Ø | Ø | Ø |
sol | Ø | |||||
UNA | Ø | |||||
do | Ø |
Esta tabla se utiliza para almacenar la secuencia LCS para cada paso del cálculo. La segunda columna y la segunda fila se han rellenado con Ø, porque cuando se compara una secuencia vacía con una secuencia no vacía, la subsecuencia común más larga siempre es una secuencia vacía.
LCS ( R 1 , C 1 ) se determina comparando los primeros elementos en cada secuencia. G y A no son lo mismo, por lo que este LCS obtiene (utilizando la "segunda propiedad") la más larga de las dos secuencias, LCS ( R 1 , C 0 ) y LCS ( R 0 , C 1 ). Según la tabla, ambos están vacíos, por lo que LCS ( R 1 , C 1 ) también está vacío, como se muestra en la tabla a continuación. Las flechas indican que la secuencia proviene tanto de la celda anterior, LCS ( R0 , C 1 ) y la celda de la izquierda, LCS ( R 1 , C 0 ).
LCS ( R 1 , C 2 ) se determina comparando G y G. Coinciden, por lo que G se agrega a la secuencia superior izquierda, LCS ( R 0 , C 1 ), que es (Ø), dando (ØG), que es (G).
Para LCS ( R 1 , C 3 ), G y C no coinciden. La secuencia anterior está vacía; el de la izquierda contiene un elemento, G. Seleccionando el más largo de estos, LCS ( R 1 , C 3 ) es (G). La flecha apunta a la izquierda, ya que es la más larga de las dos secuencias.
LCS ( R 1 , C 4 ), asimismo, es (G).
LCS ( R 1 , C 5 ), igualmente, es (G).
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | Ø | Ø | Ø | Ø | Ø | Ø |
sol | Ø | Ø | (SOL) | (SOL) | (SOL) | (SOL) |
UNA | Ø | |||||
do | Ø |
Para LCS ( R 2 , C 1 ), A se compara con A. Los dos elementos coinciden, por lo que A se agrega a Ø, dando (A).
Para LCS ( R 2 , C 2 ), A y G no coinciden, por lo que el más largo de LCS ( R 1 , C 2 ), que es (G), y LCS ( R 2 , C 1 ), que es (A ), se utiliza. En este caso, cada uno contiene un elemento, por lo que este LCS tiene dos subsecuencias: (A) y (G).
Para LCS ( R 2 , C 3 ), A no coincide con C. LCS ( R 2 , C 2 ) contiene las secuencias (A) y (G); LCS ( R 1 , C 3 ) es (G), que ya está contenido en LCS ( R 2 , C 2 ). El resultado es que LCS ( R 2 , C 3 ) también contiene las dos subsecuencias, (A) y (G).
Para LCS ( R 2 , C 4 ), A coincide con A, que se agrega a la celda superior izquierda, dando (GA).
Para LCS ( R 2 , C 5 ), A no coincide con T. Comparando las dos secuencias, (GA) y (G), la más larga es (GA), por lo que LCS ( R 2 , C 5 ) es (GA).
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | Ø | Ø | Ø | Ø | Ø | Ø |
sol | Ø | Ø | (SOL) | (SOL) | (SOL) | (SOL) |
UNA | Ø | (UNA) | (A) y (G) | (A) y (G) | (GEORGIA) | (GEORGIA) |
do | Ø |
Para LCS ( R 3 , C 1 ), C y A no coinciden, por lo que LCS ( R 3 , C 1 ) obtiene la más larga de las dos secuencias, (A).
Para LCS ( R 3 , C 2 ), C y G no coinciden. Tanto LCS ( R 3 , C 1 ) como LCS ( R 2 , C 2 ) tienen un elemento. El resultado es que LCS ( R 3 , C 2 ) contiene las dos subsecuencias, (A) y (G).
Para LCS ( R 3 , C 3 ), C y C coinciden, por lo que C se agrega a LCS ( R 2 , C 2 ), que contiene las dos subsecuencias, (A) y (G), dando (AC) y (GC )
Para LCS ( R 3 , C 4 ), C y A no coinciden. La combinación de LCS ( R 3 , C 3 ), que contiene (AC) y (GC), y LCS ( R 2 , C 4 ), que contiene (GA), da un total de tres secuencias: (AC), (GC) y (GA).
Finalmente, para LCS ( R 3 , C 5 ), C y T no coinciden. El resultado es que LCS ( R 3 , C 5 ) también contiene las tres secuencias, (AC), (GC) y (GA).
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | Ø | Ø | Ø | Ø | Ø | Ø |
sol | Ø | Ø | (SOL) | (SOL) | (SOL) | (SOL) |
UNA | Ø | (UNA) | (A) y (G) | (A) y (G) | (GEORGIA) | (GEORGIA) |
do | Ø | (UNA) | (A) y (G) | (AC) y (GC) | (AC) y (GC) y (GA) | (AC) y (GC) y (GA) |
El resultado final es que la última celda contiene todas las subsecuencias más largas comunes a (AGCAT) y (GAC); estos son (AC), (GC) y (GA). La tabla también muestra las subsecuencias comunes más largas para cada posible par de prefijos. Por ejemplo, para (AGC) y (GA), la subsecuencia común más larga es (A) y (G).
Enfoque de rastreo [ editar ]
Calcular el LCS de una fila de la tabla LCS requiere solo las soluciones para la fila actual y la fila anterior. Aún así, para secuencias largas, estas secuencias pueden ser numerosas y largas, lo que requiere mucho espacio de almacenamiento. El espacio de almacenamiento se puede guardar guardando no las subsecuencias reales, sino la longitud de la subsecuencia y la dirección de las flechas, como en la tabla a continuación.
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 |
sol | 0 0 | 0 0 | 1 | 1 | 1 | 1 |
UNA | 0 0 | 1 | 1 | 1 | 2 | 2 |
do | 0 0 | 1 | 1 | 2 | 2 | 2 |
Las subsecuencias reales se deducen en un procedimiento de "rastreo" que sigue las flechas hacia atrás, comenzando desde la última celda de la tabla. Cuando la longitud disminuye, las secuencias deben haber tenido un elemento común. Son posibles varios caminos cuando se muestran dos flechas en una celda. A continuación se muestra la tabla para dicho análisis, con números coloreados en celdas donde la longitud está a punto de disminuir. Los números en negrita trazan la secuencia, (GA). [6]
Ø | UNA | sol | do | UNA | T | |
---|---|---|---|---|---|---|
Ø | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 |
sol | 0 0 | 0 0 | 1 | 1 | 1 | 1 |
UNA | 0 0 | 1 | 1 | 1 | 2 | 2 |
do | 0 0 | 1 | 1 | 2 | 2 | 2 |
Relación con otros problemas [ editar ]
Para dos cuerdas y , la longitud de la supersecuencia común más corta está relacionada con la longitud de la LCS en [3]
La distancia de edición cuando solo se permite la inserción y eliminación (sin sustitución), o cuando el costo de la sustitución es el doble del costo de una inserción o eliminación, es:
Código para la solución de programación dinámica [ editar ]
Esta sección no cita ninguna fuente . ( marzo de 2013 ) ( Aprenda cómo y cuándo eliminar este mensaje de plantilla )
|
Calcular la longitud de la LCS [ editar ]
La siguiente función toma como secuencias de entrada
X[1..m]
y Y[1..n]
, calcula el LCS entre X[1..i]
y Y[1..j]
para todos 1 ≤ i ≤ m
y 1 ≤ j ≤ n
, y lo almacena C[i,j]
. C[m,n]
contendrá la longitud del LCS de X
y Y
.Leer un LCS [ editar ]
La siguiente función retrocede las opciones tomadas al calcular la
C
tabla. Si los últimos caracteres en los prefijos son iguales, deben estar en un LCS. Si no, verifique qué le dio el mayor LCS de mantenimiento y , y hacer la misma elección. Simplemente elija uno si fueran igualmente largos. Llame a la función con i=m
y j=n
.Leer todos los LCS [ editar ]
Si elegir y daría un resultado igualmente largo, lea ambas subsecuencias resultantes. Esto se devuelve como un conjunto por esta función. Tenga en cuenta que esta función no es polinómica, ya que podría ramificarse en casi todos los pasos si las cadenas son similares.
Imprime el diff [ editar ]
Esta función retrocederá a través de la matriz C e imprimirá la diferencia entre las dos secuencias. Tenga en cuenta que obtendrá una respuesta diferente si intercambia
≥
y <
, con >
y ≤
debajo.Ejemplo [ editar ]
Dejar ser "
XMJYAUZ
" yser " MZJAWXU
". La subsecuencia común más larga entre y es " MJAU
". La tabla que se C
muestra a continuación, que es generada por la función LCSLength
, muestra las longitudes de las subsecuencias comunes más largas entre los prefijos de y . losth fila y La columna th muestra la longitud del LCS entre y .0 0 | 1 | 2 | 3 | 4 4 | 5 5 | 6 6 | 7 7 | ||
---|---|---|---|---|---|---|---|---|---|
Ø | METRO | Z | J | UNA | W | X | U | ||
0 0 | Ø | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 |
1 | X | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 1 | 1 |
2 | METRO | 0 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
3 | J | 0 0 | 1 | 1 | 2 | 2 | 2 | 2 | 2 |
4 4 | Y | 0 0 | 1 | 1 | 2 | 2 | 2 | 2 | 2 |
5 5 | UNA | 0 0 | 1 | 1 | 2 | 3 | 3 | 3 | 3 |
6 6 | U | 0 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4 4 |
7 7 | Z | 0 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 4 |
Los números resaltados muestran la ruta
backtrack
que seguiría la función desde la esquina inferior derecha hasta la esquina superior izquierda, al leer un LCS. Si los símbolos actuales en y son iguales, son parte de la LCS, y vamos hacia arriba y hacia la izquierda (en negrita ). Si no, subimos o nos vamos, dependiendo de qué celda tenga un número más alto. Esto corresponde a tomar el LCS entre y o y .Optimización de código [ editar ]
Se pueden hacer varias optimizaciones en el algoritmo anterior para acelerarlo en casos del mundo real.
Reducir el conjunto de problemas [ editar ]
La matriz C en el algoritmo ingenuo crece cuadráticamente con las longitudes de las secuencias. Para dos secuencias de 100 ítems, se necesitaría una matriz de 10,000 ítems y se necesitarían 10,000 comparaciones. En la mayoría de los casos del mundo real, especialmente los parches y las diferencias de código fuente, el comienzo y el final de los archivos rara vez cambian, y casi con certeza no ambos al mismo tiempo. Si solo unos pocos elementos han cambiado en el medio de la secuencia, el principio y el final se pueden eliminar. Esto reduce no solo los requisitos de memoria para la matriz, sino también el número de comparaciones que deben realizarse.
En el mejor de los casos, una secuencia sin cambios, esta optimización eliminaría por completo la necesidad de la matriz C. En el peor de los casos, un cambio en el primer y último ítem de la secuencia, solo se realizan dos comparaciones adicionales.
Reduzca el tiempo de comparación [ editar ]
La mayor parte del tiempo empleado por el ingenuo algoritmo se dedica a realizar comparaciones entre los elementos de las secuencias. Para secuencias de texto como el código fuente, desea ver líneas como elementos de secuencia en lugar de caracteres individuales. Esto puede significar comparaciones de cadenas relativamente largas para cada paso en el algoritmo. Se pueden hacer dos optimizaciones que pueden ayudar a reducir el tiempo que consumen estas comparaciones.
Reducir cadenas a hashes [ editar ]
Se puede usar una función hash o suma de comprobación para reducir el tamaño de las cadenas en las secuencias. Es decir, para el código fuente donde la línea promedio tiene 60 o más caracteres de largo, el hash o la suma de verificación para esa línea puede tener solo 8 a 40 caracteres de largo. Además, la naturaleza aleatoria de los hashes y las sumas de verificación garantizaría que las comparaciones se cortocircuiten más rápido, ya que las líneas de código fuente rara vez se cambiarán al principio.
Hay tres inconvenientes principales para esta optimización. Primero, se debe gastar una cantidad de tiempo de antemano para calcular previamente los hash para las dos secuencias. En segundo lugar, se debe asignar memoria adicional para las nuevas secuencias hash. Sin embargo, en comparación con el algoritmo ingenuo utilizado aquí, ambos inconvenientes son relativamente mínimos.
El tercer inconveniente es el de las colisiones . Dado que no se garantiza que la suma de comprobación o el hash sean únicos, existe una pequeña posibilidad de que dos elementos diferentes se puedan reducir al mismo hash. Esto es poco probable en el código fuente, pero es posible. Por lo tanto, un hash criptográfico sería mucho más adecuado para esta optimización, ya que su entropía será significativamente mayor que la de una suma de comprobación simple. Sin embargo, los beneficios pueden no valer la configuración y los requisitos computacionales de un hash criptográfico para pequeñas secuencias de longitud.
Reduzca el espacio requerido [ editar ]
Si solo se requiere la longitud del LCS, la matriz se puede reducir a un matriz con facilidad, o para un vector (más inteligente) como el enfoque de programación dinámica solo necesita las columnas actuales y anteriores de la matriz. El algoritmo de Hirschberg permite la construcción de la secuencia óptima en sí misma en el mismo tiempo cuadrático y límites de espacio lineal. [7]
Otros algoritmos optimizados [ editar ]
Existen varios algoritmos que son el peor de los casos más rápido que el enfoque de programación dinámica presentado. [8] Para problemas con un tamaño de alfabeto acotado, el Método de los cuatro rusos se puede utilizar para reducir el tiempo de ejecución del algoritmo de programación dinámica mediante un factor logarítmico. [9] Hay un algoritmo que funciona en tiempo (para ), dónde es el número de coincidencias entre las dos secuencias. [10]
Comportamiento en cadenas aleatorias [ editar ]
Comenzando con Chvátal y Sankoff (1975) , [11] varios investigadores han investigado el comportamiento de la longitud de subsecuencia común más larga cuando las dos cadenas dadas se dibujan al azar del mismo alfabeto. Cuando el tamaño del alfabeto es constante, la longitud esperada del LCS es proporcional a la longitud de las dos cadenas, y las constantes de proporcionalidad (dependiendo del tamaño del alfabeto) se conocen como las constantes de Chvátal-Sankoff . Se desconocen sus valores exactos, pero se han demostrado los límites superior e inferior de sus valores [12], y se sabe que crecen de forma inversamente proporcional a la raíz cuadrada del tamaño del alfabeto. [13]Se ha demostrado que los modelos matemáticos simplificados del problema de subsecuencia común más largo están controlados por la distribución de Tracy-Widom .
No hay comentarios:
Publicar un comentario