Algoritmo de coincidencia de cadenas-algoritmo KMP

algoritmo BF

Cuando se trata de coincidencia de cadenas, lo primero que viene a la mente es que la cadena del patrón coincide con la cadena principal carácter por carácter. Cuando se encuentra un carácter no coincidente, la cadena del patrón retrocede una posición y continúa desde la primera posición del patrón. cadena.Comience a comparar. Este algoritmo de coincidencia también se llama algoritmo BF , que es "algoritmo de coincidencia de fuerza bruta". La complejidad temporal es O (n*m), donde n y m representan las longitudes de la cadena principal y la cadena patrón.

Tanto el algoritmo BM como el algoritmo KMP están optimizados sobre la base del algoritmo BF, es decir, se espera omitir más caracteres durante el proceso de coincidencia.

Idea del algoritmo KMP

Dado que ya sabemos qué caracteres se han leído cuando falla una comparación de cadenas, ¿es posible evitar el paso de "saltar al siguiente carácter y volver a hacer coincidir"?

La idea básica del algoritmo KMP es utilizar la cadena de patrón que se ha atravesado para evitar el paso de "copia de seguridad" en el algoritmo de fuerza bruta cuando se encuentra un carácter que no coincide. En otras palabras, no desea disminuir el puntero de la cadena principal y dejar que avance para siempre.

Regala una castaña:

Haga coincidir la cadena del patrón - ABAB C en la cadena principal - ABAB A BCAA

 Al hacer coincidir, se descubre que el último carácter de la subcadena no coincide con el carácter correspondiente de la cadena principal, pero el sufijo de la cadena principal coincide con el prefijo de la subcadena, luego puede omitir dos caracteres y mover la subcadena hacia atrás dos lugares y continuar el partido.

Entonces, ¿cómo sabemos cuántos caracteres debemos omitir cada vez que encontramos un personaje que no coincide?

Aquí se utiliza la siguiente matriz definida en el algoritmo KMP . 

Regala una castaña:

Aquí no nos importa cómo se genera la siguiente matriz, primero veamos sus funciones y usos.

Cuando el algoritmo KMP encuentra una discrepancia de caracteres, buscará el siguiente valor correspondiente al último carácter coincidente.

En este ejemplo, el siguiente valor correspondiente al último carácter coincidente es 2, por lo que movemos la subcadena dos lugares hacia atrás para que omita dos caracteres en la cadena principal y pueda continuar coincidiendo.

 El valor del siguiente elemento de la matriz representa el número de caracteres que la subcadena puede "omitir coincidentes".

Dado que no es necesario un puntero de reversión y solo se necesita un recorrido de la cadena principal para completar la coincidencia, la eficiencia será naturalmente mucho mayor que la del algoritmo de fuerza bruta.

public int kmpSearch(char[] txt, char[] patt){ // txt代表主串,patt代表模式串
		int[] next =buider_nexts(patt); // 假设已经计算出了next数组
		int i = 0; // 主串中的指针
		int j = 0; // 子串中的指针
		while (true){
			if (i == txt.length) return -1;
			if (txt[i] == patt[j]) { // 字符匹配,指针后移一位继续匹配
				i++;
				j++;
			} else if (j > 0) { // 字符不匹配,则根据next数值跳过子串前几个字符的匹配
				j = next[j-1];
			}else { // 子串的第一个字符就不匹配,则直接后移一位
				++i;
			}
			if (j == patt.length) return i-j; // 如果j已经达到子串末尾,则匹配成功,返回匹配的起始位置
		}
	}

 Tenga en cuenta que el puntero de la cadena principal i nunca disminuye, que también es la esencia del algoritmo KMP.

Generación de la próxima matriz

Como se mencionó anteriormente, el valor en la siguiente matriz representa la cantidad de caracteres coincidentes que se pueden omitir en la subcadena cuando falla la coincidencia. Pero ¿por qué es esto posible?

Del ejemplo anterior, podemos encontrar que los dos últimos AB coincidentes son los mismos que los dos primeros AB omitidos.

En otras palabras, los primeros cuatro caracteres de la subcadena tienen un prefijo y un sufijo AB comunes y la longitud es 2.

La esencia de la siguiente matriz es encontrar la "longitud del mismo sufijo" en la subcadena, y debe ser el sufijo más largo. 

Regala una castaña:

 En esta subcadena, aunque el prefijo y el sufijo A son iguales, no son los más largos y ABA es el más largo del mismo prefijo y sufijo, por lo que el siguiente valor es 3.

Pero tenga en cuenta que el sufijo que estamos buscando no puede ser la subcadena en sí. Si se omite el número de caracteres de la longitud de la subcadena en sí, no tendrá sentido.

Cálculo de la siguiente matriz

Tome la subcadena ABABC como ejemplo para calcular la siguiente matriz.

  • Para el primer carácter, el mismo sufijo y sufijo obviamente no existen, y el siguiente es 0
  • Para los dos primeros caracteres, no existe el mismo sufijo y sufijo, el siguiente es 0
  • Los primeros tres caracteres tienen el mismo prefijo y sufijo A, y el siguiente es 1.
  • Los primeros cuatro caracteres tienen el mismo prefijo y sufijo AB, y el siguiente es 2.
  • Para los primeros cinco caracteres que no tienen el mismo sufijo y sufijo, el siguiente es 0

¿Pero cómo implementar el algoritmo?

Podemos usar un bucle for para resolver el problema violentamente, pero la eficiencia es demasiado baja. De hecho, el método recursivo se puede utilizar para resolver rápidamente la siguiente matriz. Su inteligencia es que utilizará continuamente información conocida para evitar operaciones repetidas.

Regala una castaña:

Suponiendo que se conocen el sufijo común actual y la longitud es 2, existen dos situaciones para continuar la coincidencia descendente.

1. Si el siguiente carácter sigue siendo el mismo, forma un sufijo común más largo y la longitud es la longitud anterior + 1.

2. Si el siguiente carácter no es el mismo, debe buscar si hay un sufijo idéntico más corto.

Entonces, ¿cómo encontrar sufijos idénticos más cortos?

En el cálculo anterior, ya sabemos que hay un mismo sufijo con una longitud de 3 delante del carácter B no coincidente. Podemos encontrar directamente el sufijo común a la izquierda, y la longitud del sufijo más largo a la izquierda puede ser obtenido buscando en la tabla. Si es 1, entonces volvemos al paso original y verificamos si los siguientes caracteres son iguales. Si son iguales, podemos construir un sufijo más largo con una longitud de +1.

Compare la animación para comprender:

 

Código de próxima generación de matriz del algoritmo KMP: 

private static int[] buider_nexts(char[] patt) {
	int[] next = new int[patt.length]; // next数组,且第一个元素为0
    next[0] = 0;
	int prefix_len = 0; // 当前公共前后缀的长度
	int i = 1;
	while (i < patt.length){
		if (patt[prefix_len] == patt[i]){ // 字符匹配,则将prefix_len+1,存入对应next数组中
			prefix_len++;
			next[i] = prefix_len;
		}else{
			if (prefix_len == 0){	// 如果不存在相同前后缀则直接把next设为0
				next[i] = 0;
				i++;
			}else{
				prefix_len = next[prefix_len-1];	// 字符不匹配则直接查表看看存不存在更短的共同前后缀
			}
		}
	}
	return next;
}

 Código completo:

public int kmpSearch(char[] txt, char[] patt){
		int[] next =buider_nexts(patt); // 假设已经计算出了next数组
		int i = 0; // 主串中的指针
		int j = 0; // 子串中的指针
		while (true){
			if (i == txt.length) return -1;
			if (txt[i] == patt[j]) { // 字符匹配,指针后移一位继续匹配
				i++;
				j++;
			} else if (j > 0) { // 字符不匹配,则根据next数值跳过子串前几个字符的匹配
				j = next[j-1];
			}else { // 子串的第一个字符就不匹配,则直接后移一位
				++i;
			}
			if (j == patt.length) return i-j; // 如果j已经达到子串末尾,则匹配成功,返回匹配的起始位置
		}
	}

	private int[] buider_nexts(char[] patt) {
		int[] next = new int[patt.length]; // next数组,且第一个元素为0
		next[0] = 0;
		int prefix_len = 0; // 当前公共前后缀的长度
		int i = 1;
		while (i < patt.length){
			if (patt[prefix_len] == patt[i]){ // 字符匹配,则将prefix_len+1,存入对应next数组中
				prefix_len++;
				next[i] = prefix_len;
                i++;
			}else{
				if (prefix_len == 0){	// 如果不存在相同前后缀则直接把next设为0
					next[i] = 0;
					i++;
				}else{
					prefix_len = next[prefix_len-1];	// 字符不匹配则直接查表看看存不存在更短的共同前后缀
				}
			}
		}
		return next;
	}

Supongo que te gusta

Origin blog.csdn.net/weixin_53922163/article/details/132755827
Recomendado
Clasificación