Arquitectura de datos y algoritmo: algoritmo KMP gráfico (resumen de investigación, cadena) [colección recomendada]

Prólogo : Aprendí el algoritmo KMP hace algún tiempo, y se siente un poco complicado, pero en cualquier caso lo entiendo, simplemente regístrelo, para poder recordarlo más tarde.

1. Introducción

Primero, veamos un ejemplo. Ahora hay dos cadenas A y B. ¿Tienes B en A y cuántas? Para facilitar la descripción, primero damos el valor de las dos cadenas
A = "abcaabababaa"
B = "abab"
Entonces, ¿cómo funciona el emparejamiento ordinario?
Por supuesto, es uno por uno. (El color azul indica que se ha emparejado y el color negro indica que el emparejamiento falló.) ¡
Inserte la descripción de la imagen aquí
Pero encontramos que tal emparejamiento es un desperdicio!
¿Por qué dices eso? Vemos el cuarto paso:
Inserte la descripción de la imagen aquí
en el cuarto paso, encontró cy a en el tercer lugar No coincide, y luego en el quinto paso, movemos la cadena B hacia atrás un bit, y luego comenzamos a emparejar desde el primero.
Inserte la descripción de la imagen aquí
Hay un gran desperdicio de información conocida, porque de acuerdo con los resultados de coincidencia anteriores, sabemos que los dos primeros dígitos de la cadena B son ab, por lo que no importa cómo se mueva, no pueden coincidir con b, por lo que debe omitir el par directamente La coincidencia de la segunda posición de la cuerda A es la misma para la tercera posición de la cuerda A.

Quizás este ejemplo no sea lo suficientemente clásico, démosle otro.

A=“abbaabbbabaa”
B=“abbaaba”

En este ejemplo, seguimos coincidiendo desde la primera posición hasta que falla la coincidencia:

abbaabbbabba
abbaaba
encontramos que la séptima posición no coincide,
por lo que si continuamos haciendo coincidir de la forma original, moveremos la cadena B hacia atrás un lugar y comenzaremos a hacer coincidir
abbaabbbabba desde el primer carácter nuevamente. Si
_abbaaba
aún no coincide, luego debemos seguir retrocediendo ligeramente.
¡En Vivo!
Ahora que hemos hecho coincidir los primeros 6 dígitos, también sabemos que estos 6 dígitos de la cadena A coinciden con los primeros 6 dígitos de la cadena B. ¿Podemos usar esta información para optimizar nuestra coincidencia?
En otras palabras, ¿podemos saltar directamente a:
abbaabbbabba
____abbaaba después de que falle la coincidencia anterior, de
modo que podamos guardar muchas coincidencias innecesarias?

El editor recomienda mi propio grupo de intercambio de tecnología de lenguaje linuxC / C ++: [ 1106675687 ] He recopilado algunos libros de aprendizaje y materiales de video que creo que es mejor compartir en los archivos del grupo, ¡y puede agregarlos si los necesita!
Inserte la descripción de la imagen aquí

Dos, algoritmo KMP

El algoritmo KMP resuelve los problemas anteriores. Antes de que hablemos de ello, presentemos primero dos conceptos:

前缀:指的是字符串的子串中从原串最前面开始的子串,如abcdef的前缀有:a,ab,abc,abcd,abcde
后缀:指的是字符串的子串中在原串结尾处结尾的子串,如abcdef的后缀有:f,ef,def,cdef,bcdef

El algoritmo KMP introduce una matriz F (en muchos artículos se llamará a continuación, pero el autor está más acostumbrado a usar F, que es más conveniente de expresar), F [i] representa la misma subcadena más larga compuesta por los caracteres antes de i La longitud del sufijo del prefijo.
¿Cómo entenderlo?
Por ejemplo, el mismo sufijo de prefijo de la cadena aababaaba tiene a y aaba, luego el más largo es aaba.

3. La incomprensibilidad del algoritmo KMP y la convención descrita en este artículo

Antes de continuar con nuestra descripción, el autor primero habla sobre por qué no se comprende bien el algoritmo KMP.
Aunque hay muchos blogs y tutoriales sobre el algoritmo KMP en Internet, el autor ha consultado mucha información y no hay muchas descripciones detalladas del proceso y los principios. Existen sutiles diferencias en la definición de artículos realmente bien escritos. (por supuesto, hay artículos muy bien escritos. También hay, no los enumeraré todos aquí), por ejemplo, algunas etiquetas comienzan desde 1, algunas siguientes significan la anterior y algunas son la actual. Si lo lees, inevitablemente se confundirá.
Entonces, para evitar que los lectores sientan la misma confusión que el autor en el estudio anterior, a continuación se presentan algunas explicaciones y convenciones.

1. En este artículo, todas las cadenas se numeran a partir de 0.
2. En este artículo, la matriz F (es decir, la siguiente en otros artículos), F [i] representa la longitud del sufijo de prefijo idéntico más largo de la cadena de 0 a yo.

Cuarto, el uso de la matriz F

Entonces, supongamos que tenemos todos los valores de F, ¿cómo usamos la matriz F para resolverlo?
Primero demos un ejemplo (el autor tardó mucho en construir este ejemplo más típico):
A = "abaabaabbabaaabaabbabaab"
B = "abaabbabaab"
Por supuesto, los lectores pueden simular manualmente que solo un lugar coincide con
abaabaabbabaaabaabbabaab y
luego también podemos calcular el valor de cada F según simulación manual

B="a b a a b b a b a a b "
F= 0 0 1 1 2 0 1 2 3 4 52017.7.25 Update 这里之前有一个错误,感谢@ 歌古道指正)(2017.7.2

Luego usamos i para representar la posición donde la cadena A actual debe coincidir (es decir, aún no coincide), y j representa la posición donde coincide la cadena B actual (tampoco coincide todavía). Para agregar, si i > 0, significa que i-1 ya está emparejado Sí (igual que j).
En primer lugar, todavía comenzamos a emparejar desde 0:
Inserte la descripción de la imagen aquí
en este momento, encontramos que la quinta posición de A y la quinta posición de B no coinciden (tenga en cuenta que la numeración comienza desde 0), en este momento i = 5, j = 5, luego miramos el valor F [j-1]:

F[5-1]=2;

Esto significa que nuestra próxima coincidencia solo debe comenzar desde la segunda posición de la cadena B (es decir, el tercer carácter), porque los dos primeros dígitos ya coinciden, consulte la imagen para obtener más detalles:
Inserte la descripción de la imagen aquí
y luego continuar haciendo coincidir:
Inserte la descripción de la imagen aquí
nosotros encuentra que A El 13 ° bit de la cadena no coincide con el 10 ° bit de la cadena B. En este momento, i = 13, j = 10, luego miramos el valor de F [j-1]:

F[10-1]=4

Esto muestra que los bits 0 y 3 de la cadena B coinciden con la actual (i-4) (i-1). No es necesario volver a hacer coincidir esta parte. Mueva la cadena B hacia atrás desde el cuarto bit de la B cadena. Comenzar a coincidir:
Inserte la descripción de la imagen aquí
en este momento, encontramos que el bit 13 de la cadena A y el 4to bit de la cadena B todavía no coinciden.
Inserte la descripción de la imagen aquí
En este momento, i = 13, j = 1, así que echemos un vistazo al valor de F [j-1]:

F[1-1]=0

Bueno, esto muestra que no hay el mismo prefijo y sufijo. Simplemente mueva la cadena B hacia atrás una posición hasta que encuentre que el bit 0 de la cadena B coincide con el bit i de la cadena A (en este ejemplo, i = 13 )
Inserte la descripción de la imagen aquí
Pero en este momento, la primera posición de la cuerda B aún no coincide con la posición 13 de la cuerda A. Inserte la descripción de la imagen aquí
Repita el proceso de coincidencia anterior y descubriremos que la coincidencia es exitosa.
Inserte la descripción de la imagen aquí

Es el proceso del algoritmo KMP.
Otro punto a enfatizar es que cuando movemos la cadena B hacia atrás, en realidad es i ++, y cuando no movemos B pero coincidimos, es i ++, j ++, que aparecerá en el código detrás, aquí hay una explicación.

Por último, ven a una versión completa (¡¡¡ha pasado mucho tiempo para hacer estas fotos !!!):
Inserte la descripción de la imagen aquí

Cinco, solución de matriz F

Ahora que se ha usado tanto espacio para explicar específicamente cómo usar la matriz F para resolver, ¿cómo calcular la matriz F? No puedo resolverlo violentamente.

Otra parte ingeniosa de KMP está aquí. Utiliza el método que usamos anteriormente para hacer coincidir A con B para calcular la matriz F. En pocas palabras, ¡es usar la cadena B para hacer coincidir la cadena B en sí!
Por supuesto, debido a que B string == B string, si presiona directamente la coincidencia anterior, no tiene sentido (por supuesto, puede emparejarse por completo), por lo que aquí debe cambiarse.

Como ya he hablado de una parte anterior, primero dé el código para calcular F:

for (int i=1;i<m;i++)
{
    
    
    int j=F[i-1];
    while ((B[j+1]!=B[i])&&(j>=0))
        j=F[j];
    if (B[j+1]==B[i])
        F[i]=j+1;
    else
        F[i]=-1;
}

Los primeros puntos que se pueden determinar son:

  • 1. F [0] = - 1 (Aunque debería ser 0 aquí, pero para la conveniencia de juzgar fuera de límites, al mismo tiempo, para la conveniencia de juzgar los bits 0 e iésimo, establézcalo en -1 aquí en el programa)
  • 2. Esta es una derivación lineal de adelante hacia atrás, por lo que al calcular F [i], se puede garantizar que F [0] ~ F [i-1] se ha calculado
  • 3. Si la subcadena que termina con un cierto bit no tiene el mismo prefijo y sufijo, la F de este bit se establece en -1 (la razón para establecerla en -1 aquí es la misma que la primera)

¡importante! : Además, por conveniencia en el programa, en la siguiente descripción, F [i] = 0 significa que la longitud de sufijo de prefijo idéntico más largo es 1, es decir, el sufijo de prefijo idéntico más largo real = F [i] +1. (El contenido importante debe ampliarse)
¿Por qué desea configurarlo de esta manera, porque en este momento F [i] representa no solo la longitud del prefijo y el sufijo, sino que también representa la posición del último carácter del prefijo en la subcadena B.

Por lo tanto, se debe cambiar el valor de F mencionado anteriormente (aquí, '_' se usa para ayudar a la alineación):

B="a _b a a b _b a b a a b "
F= -1 -1 0 0 1 -1 0 1 2 3 4

Entonces, también podemos deducir que la idea de resolver F es: vea si i puede ir seguido del sufijo prefijo idéntico más largo de F [i-1], si puede, entonces conéctelo directamente, si no, hablemos sobre esto a continuación.

por ejemplo:

Tomando B = "abaabbabaab" como ejemplo, vemos el segundo.

B="a baa b b a b a a b"
F=-1 -1

En este momento, el valor F de la b anterior de esta a es -1, por lo que en este momento a no se puede conectar a la parte posterior de b (el mismo sufijo de prefijo más largo de b es 0), en este momento, j = - 1, entonces juzgamos B [j + 1] y B [2], es decir, si B [0] y B [2] son ​​iguales. Lo mismo, entonces F [2] = j + 1 = 0 (que representa el final del prefijo del prefijo idéntico más largo de los primeros 0 ~ 2 caracteres es B [0], y la longitud es 0 + 1 = 1) .

Veamos el tercero:

B="a b a a b b a b a a b"
F=-1 -1 0

Al principio, j = F [3-1] = 0, encontramos que B [j + 1 = 1]! = B [i = 3], entonces j = F [j] = - 1, en este momento B [j + 1 = 0] == B [i = 3], entonces F [3] = j + 1 = 0.

Finalmente, como ejemplo, vea el cuarto

B="a b a a b b a b a a b"
F=-1 -1 0 0

Primero, F [4-1] = 0, vemos B [j + 1 = 1] == B [i], entonces F [i] = j + 1 = 1.

Se invita a los lectores a derivar este último lentamente. Para enfatizar nuevamente, el valor F que obtuvimos de esta manera es la posición de la matriz del carácter final del prefijo en el sufijo de prefijo idéntico más largo (numerado desde 0). Si se requiere la longitud del sufijo de prefijo idéntico más largo, la salida F [i] +1.

Seis, el código

Resuelve la matriz F:

for (int i=1;i<m;i++)
{
    
    
    int j=F[i-1];
    while ((B[j+1]!=B[i])&&(j>=0))
        j=F[j];
    if (B[j+1]==B[i])
        F[i]=j+1;
    else
        F[i]=-1;
}

Use la matriz F para encontrar una coincidencia, donde generamos la posición inicial cada vez que se encuentra una coincidencia:

while (i <n)
{ if (A [i]
B [j])
{ i ++; j ++; si (j


m)
{ printf ("% d \ n", i-m + 1); // Tenga en cuenta que la posición de salida aquí está etiquetada desde 1, si desea sacar la posición etiquetada desde 0, debería ser im. Este código fue escrito cuando estaba haciendo una pregunta. La posición de la cadena de salida de esa pregunta requiere que la etiqueta comience desde 1. Gracias @Draymonder por señalar esta omisión. Para obtener más contenido, consulte el área de comentarios

       j=F[j-1]+1;
        }
    }
    else
    {
    
    
        if (j==0)
            i++;
        else
            j=F[j-1]+1;
    }
}

La siguiente actualización de contenido en 2019.4.26

Pegue un método de escritura propio actual, pero aquí la cadena está etiquetada desde 1 y no es difícil de convertir si lo entiende arriba.

De rodillas para terminar el camino que elijas. Amigos, aunque el mundo se está volviendo cada vez más impetuoso, mientras podamos seguir trabajando duro por los sueños y las emociones puros en ese momento, sin importar lo que sean las demás personas, podemos mantener nuestros verdaderos colores y seguir adelante.

Supongo que te gusta

Origin blog.csdn.net/m0_50662680/article/details/113181361
Recomendado
Clasificación