Reimpreso de http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
1.BF Violent Solution
La idea básica :
compare el primer carácter de la cadena de destino s con la primera cadena de caracteres de la cadena de patrón ss, si son iguales, compare las cadenas siguientes; de lo
contrario , compare el siguiente carácter de la cadena s con la cadena de patrón nuevamente.
Por analogía, hasta que cada carácter en ss sea igual a una subcadena continua en s, la coincidencia es exitosa. En este momento, la posición del primer carácter de ss en s es la posición de ss en s, de lo contrario coincide fracasado.
(De hecho, puede usar la función de búsqueda en este lugar)
2.KMP
Por ejemplo, hay una cadena "BBC ABCDAB ABCDABCDABDE", quiero saber si contiene otra cadena "ABCDABD".
1.
Primero, compare el primer carácter de la cadena "BBC ABCDAB ABCDABCDABDE" con el primer carácter del término de búsqueda "ABCDABD". Como B no coincide con A, el término de búsqueda se desplaza un dígito hacia atrás.
2.
Como B no coincide con A, el término de búsqueda retrocede.
3.
Eso es todo, hasta que la cadena tenga un carácter que sea el mismo que el primer carácter del término de búsqueda.
4.
Luego, compare la cadena con el siguiente carácter del término de búsqueda y seguirá siendo el mismo.
5.
Hasta que haya un carácter en la cadena que sea diferente del carácter correspondiente al término de búsqueda, la solución violenta en este momento es mover la cadena un lugar hacia atrás y compararla nuevamente, como sigue
6.
Esto es muy ineficiente, porque tienes que La posición de búsqueda se mueve a la posición que se ha comparado y la comparación se vuelve a realizar.
7.
Un hecho básico es que cuando el espacio no coincide con D, realmente sabe que los primeros seis caracteres son "ABCDAB". La idea del algoritmo KMP es tratar de utilizar esta información conocida, no mover la "posición de búsqueda" de regreso a una posición que ya ha sido comparada, y continuar moviéndola hacia atrás, lo que mejora la eficiencia.
8. ¿Cómo sabes cuánto mover?
En este momento, debe contar como una "tabla de coincidencias parciales".
Primero, debe comprender dos palabras: prefijo y sufijo. "Prefijo" se refiere a todas las combinaciones de cabeza de una cadena excepto el último carácter; "sufijo" se refiere a la primera combinación. A excepción de los caracteres, se combinan todas las colas de una cadena.
"Valor de coincidencia parcial" es la longitud del elemento común más largo de "prefijo" y "sufijo" . Tome "ABCDABD" como ejemplo,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
-El prefijo de "AB" es [A], el sufijo es [B] y la longitud de los elementos comunes es 0;
- El prefijo de "ABC" es [A, AB], el sufijo es [BC, C] y la longitud de los elementos totales es 0;
- El prefijo de "ABCD" es [A, AB, ABC], el sufijo es [BCD, CD, D] y la longitud de los elementos comunes es 0;
- El prefijo de "ABCDA" es [A, AB, ABC, ABCD], el sufijo es [BCDA, CDA, DA, A], el elemento total es "A" y la longitud es 1;
- El prefijo de "ABCDAB" es [A, AB, ABC, ABCD, ABCDA], el sufijo es [BCDAB, CDAB, DAB, AB, B], el elemento total es "AB" y la longitud es 2;
- El prefijo de "ABCDABD" es [A, AB, ABC, ABCD, ABCDA, ABCDAB], el sufijo es [BCDABD, CDABD, DABD, ABD, BD, D] y la longitud de los elementos comunes es 0.
La esencia de la "coincidencia parcial" es que, a veces, se repetirá el principio y el final de la cadena. Por ejemplo, si hay dos "AB" en "ABCDAB", entonces su "valor de coincidencia parcial" es 2 (la longitud de "AB"). Cuando la palabra de búsqueda se mueve, el primer "AB" retrocede 4 dígitos (longitud de la cadena-valor de coincidencia parcial), y puede llegar a la segunda posición "AB".
8. Cuando el
espacio conocido no coincide con D, los primeros seis caracteres "ABCDAB" coinciden. Mirando la tabla, podemos ver que el "valor de coincidencia parcial" correspondiente al último carácter coincidente B es 2, así que calcule el número de bits movidos hacia atrás de acuerdo con la siguiente fórmula:
Número de turnos = número de caracteres coincidentes-valor de coincidencia parcial correspondiente
Como 6-2 es igual a 4, mueva el término de búsqueda 4 lugares hacia atrás.
9.
Dado que el espacio no coincide con la C, el término de búsqueda debe continuar retrocediendo. En este momento, el número de caracteres coincidentes es 2 ("AB") y el "valor de coincidencia parcial" correspondiente es 0. Por lo tanto, el número de turnos = 2-0 y el resultado es 2, por lo que el término de búsqueda se desplaza hacia atrás 2 dígitos.
10.
Debido a que el espacio no coincide con A, continúe retrocediendo un dígito.
11.
Compare poco a poco, hasta que C y D no coincidan. Entonces, mueva el número de dígitos = 6-2, continúe moviendo el término de búsqueda hacia atrás 4 lugares.
12.
Compare bit a bit hasta que se encuentre el último bit del término de búsqueda y se encuentre una coincidencia completa, de modo que la búsqueda esté completa. Si desea continuar la búsqueda (es decir, encontrar todas las coincidencias), mueva el número de dígitos = 7-0 y luego mueva el término de búsqueda hacia atrás 7 lugares, para que no lo repita aquí.
La siguiente es la implementación del algoritmo
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//部分匹配表
void cal_next(string &str, vector<int> &next)
{
const int len = str.size();
next[0] = -1;
int k = -1;
int j = 0;
while (j < len - 1)
{
if (k == -1 || str[j] == str[k])
{
++k;
++j;
next[j] = k;//表示第j个字符有k个匹配(“最大长度值” 整体向右移动一位,然后初始值赋为-1)
}
else
k = next[k];//往前回溯
}
}
vector<int> KMP(string &str1, string &str2, vector<int> &next)
{
vector<int> vec;
cal_next(str2, next);
int i = 0;//i是str1的下标
int j = 0;//j是str2的下标
int str1_size = str1.size();
int str2_size = str2.size();
while (i < str1_size && j < str2_size)
{
//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),
//都令i++,j++. 注意:这里判断顺序不能调换!
if (j == -1 || str1[i] == str2[j])
{
++i;
++j;
}
else
j = next[j];//当前字符匹配失败,直接从str[j]开始比较,i的位置不变
if (j == str2_size)//匹配成功
{
vec.push_back(i - j);//记录下完全匹配最开始的位置
j = -1;//重置
}
}
return vec;
}
int main(int argc, char const *argv[])
{
vector<int> vec(20, 0);
vector<int> vec_test;
string str1;
cin>>str1;
string str2 ;
cin>>str2;
vec_test = KMP(str1, str2, vec);
vector<int>::iterator it;
for(it = vec_test.begin(); it != vec_test.end(); it++)
{
cout<<*it + 1<<endl;
}
// for (const auto v : vec_test)
// cout << v << endl;
return 0;
}