Algoritmo de coincidencia de patrones de cadena: BF y KMP

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.
Inserte la descripción de la imagen aquí
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.Inserte la descripción de la imagen aquí

Como B no coincide con A, el término de búsqueda retrocede.
3. Inserte la descripción de la imagen aquí
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.
Inserte la descripción de la imagen aquí
Luego, compare la cadena con el siguiente carácter del término de búsqueda y seguirá siendo el mismo.
5.
Inserte la descripción de la imagen aquí
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.
Inserte la descripción de la imagen aquí
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.
Inserte la descripción de la imagen aquí
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.
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí
"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 Inserte la descripción de la imagen aquí
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. Inserte la descripción de la imagen aquí
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. Inserte la descripción de la imagen aquí
Debido a que el espacio no coincide con A, continúe retrocediendo un dígito.
11.
Inserte la descripción de la imagen aquí

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.
Inserte la descripción de la imagen aquí
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;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_51216553/article/details/110140770
Recomendado
Clasificación