Notas de algoritmo (1) - Algoritmo KMP

contenido

Algoritmo de coincidencia de fuerza bruta (BF)

concepto basico

Análisis de algoritmos BF

Código 

una pequeña prueba

Complejidad temporal del algoritmo BF

Algoritmo KMP

concepto basico

Análisis de algoritmos KMP

Saca la siguiente matriz

Código

Explicación del código clave

una pequeña prueba

Complejidad temporal del algoritmo KMP


Algoritmo de coincidencia de fuerza bruta (BF)

concepto basico

El algoritmo BF, el algoritmo de fuerza bruta , es un algoritmo común de coincidencia de patrones. La idea del algoritmo BF es hacer coincidir el primer carácter de la cadena de destino S con el primer carácter de la cadena de patrón. Si son iguales, continúe para comparar S. El segundo carácter de S y el segundo carácter de T, si no son iguales, compare el segundo carácter de S y el primer carácter de T, y compárelos a su vez hasta obtener el resultado final de coincidencia. El algoritmo BF es un algoritmo de fuerza bruta.

Análisis de algoritmos BF

Solo mirar la definición es oscuro y difícil de entender. A continuación, daré un ejemplo para aprender con usted:

Supongamos que damos la cadena "ababcabcdabcde" como la cadena principal, y luego damos la subcadena "abcd", ahora necesitamos averiguar si la subcadena aparece en la cadena principal, devuelve el primer subíndice coincidente en la cadena principal, falla devuelve: 1.

  

Para este problema, podemos pensarlo fácilmente: hacer coincidir de izquierda a derecha, si los caracteres son iguales, todos se desplazan hacia atrás en uno; comience con 0 subíndice, la próxima vez comience con 1 subíndice)

Podemos inicializar así:

De acuerdo con nuestras ideas, entonces necesitamos comparar si los números señalados por el puntero i y el puntero j son consistentes. Si son consistentes, se moverán hacia atrás. Si son inconsistentes, como se muestra a continuación:

Si b y d no son iguales, entonces el puntero i se devuelve a la siguiente posición del puntero justo ahora (el puntero acaba de comenzar desde el subíndice 0), y el puntero j se devuelve al subíndice 0 y comienza de nuevo.

Código 

Con base en el análisis anterior, comencemos a escribir el código:

Código C:

#include<stdio.h>
#include<string.h>
#include<assert.h>
int BF(char* str1, char* str2)
{
	assert(str1 != NULL && str2 != NULL);
	int len1 = strlen(str1);//主串的长度
	int len2 = strlen(str2);//子串的长度
	int i = 0;//主串的起始位置
	int j = 0;//子串的起始位置
	while (i < len1 && j < len2)
	{
		if (str1[i] == str2[j])
		{
			i++;//相等i和j都向后移动一位
			j++;
		}
		else {//不相等
			i = i - j + 1;//i回退
			j = 0;//j回到0位置
		}
	}
	if (j >= len2) {//子串遍历玩了说明已经找到与其匹配的子串
		return i - j;
	}
	else {
		return -1;
	}

}
int main()
{
	printf("%d\n", BF("ababcabcdabcde", "abcd"));//测试,为了验证代码是否正确尽量多举几个例子
	printf("%d\n", BF("ababcabcdabcde", "abcde"));
	return 0;
}

codigo java:

public class Test {
public static int BF(String str,String sub) {
if(str == null || sub == null) return -1;
int strLen = str.length();
int subLen = sub.length();
int i = 0;
int j = 0;
while (i < strLen && j < subLen) {
if(str.charAt(i) == sub.charAt(j)) {
i++;
j++;
}else {
i = i-j+1;
j = 0;
}
} i
f(j >= subLen) {
return i-j;
} r
eturn -1;
} 
public static void main(String[] args) {
System.out.println(BF("ababcabcdabcde","abcd"));
System.out.println(BF("ababcabcdabcde","abcde"));
}
}

una pequeña prueba

A través del estudio anterior, tengo una comprensión preliminar del algoritmo BF Para tener una comprensión y una aplicación más profundas, completaré las siguientes preguntas de prueba con usted:

Preguntas de prueba aquí >> Implementar strStr()

Los socios interesados ​​pueden probarlo y lo discutiremos juntos en el próximo capítulo;

Complejidad temporal del algoritmo BF

El mejor de los casos es que la complejidad temporal del emparejamiento sea O(1) desde la primera vez;

El peor de los casos es que cada vez que se compara el último solo se encuentra que es diferente de la cadena principal, como "aaaaab", subcadena "aab"

 

 

 

 

Mirando la imagen de arriba, excepto la última vez, el resto se empareja hasta el final cada vez, solo para descubrir, ah, somos diferentes.

En este caso, en la figura anterior, la secuencia del patrón está en las primeras 3 veces, y cada vez coincide 3 veces, y no coincide, hasta la 4ª vez, todas coinciden, no es necesario seguir moviéndose, por lo que el número de coincidencias es (6 - 3 + 1) * 3 = 12 veces.

Puede verse que, para la longitud de la cadena principal de n y la longitud de la cadena patrón de m, la complejidad temporal del peor de los casos es O((n - m + 1) * m) = O(n * m ).
Creo que los amigos que piensan encontrarán que si es para buscar, no hay necesidad de mover i a la posición de 1 en absoluto, porque todos los caracteres anteriores coinciden, luego mueva i a la posición de 1 y mueva j a la posición de 0, la posición está escalonada, y obviamente no coincidirá, entonces podemos descartar los pasos innecesarios anteriores, reducir el retroceso del puntero para simplificar el algoritmo, hay una idea, la posición i no se mueve, solo necesita moverse la posición j , que nos lleva hoy El algoritmo kmp protagonista.

Algoritmo KMP

concepto basico

El algoritmo KMP es un algoritmo mejorado de coincidencia de cadenas propuesto por DEKnuth, JH Morris y VRRatt, por lo que la gente lo llama operación Knuth-Morris-Platt (algoritmo KMP para abreviar). El núcleo del algoritmo KMP es utilizar la información después de la falla de coincidencia para minimizar los tiempos de coincidencia entre la cadena de patrones y la cadena principal para lograr el propósito de una coincidencia rápida . La implementación específica es a través de una función next() y la función en sí contiene la información de coincidencia local de la cadena de patrón. La complejidad temporal del algoritmo KMP es O(m+n).

Diferencia: la única diferencia entre K MP y BF es que i de mi cadena principal no retrocederá y j no se moverá a la posición 0.

Análisis de algoritmos KMP

Supongamos que damos la cadena "ababcabcdabcde" como la cadena principal, y luego damos la subcadena "abcd", ahora necesitamos averiguar si la subcadena aparece en la cadena principal, devuelve el primer subíndice coincidente en la cadena principal, falla devuelve: 1.

1. Primero, dé un ejemplo, por qué la cadena principal no se revierte

2.j Ubicación alternativa

Entonces, ¿cómo regresa j a la cápsula en la posición del subíndice 2? A continuación, llevamos a la siguiente matriz.

Saca la siguiente matriz

La esencia de KMP es la siguiente matriz: es decir, está representada por next[j] = k ;, diferentes j corresponden a un valor de K, y esta K es la posición de la j que desea mover en el futuro . Y el valor de K se calcula así:

  •  Regla: busque dos subcadenas propias iguales (excluyéndose a sí misma) que coincidan con la parte exitosa, una que comience con el carácter de subíndice 0 y la otra que termine con el carácter de subíndice j-1.
  • No importa qué datos next[0] = -1, next[1] = 0, aquí comenzamos con subíndices, y el número de veces mencionado comienza desde 1;

Ejercicios para encontrar la siguiente matriz:  

Ejercicio 1: por ejemplo, para "ababcabcdabcde", ¿busca su siguiente matriz?

-1 0 0 1 2 0 1 2 0 0 1 2 0 0

Ejercicio 2: ¿Encuentre la siguiente matriz de "abcabcabcabcdabcde"? "
-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0

Aquí viene lo esencial:
aquí nadie debería tener problemas para encontrar la siguiente matriz. La siguiente pregunta es, si sabemos que next[i] = k; ¿cómo encontrar next[i+1] =
? pase next El valor de [i], a través de una serie de conversiones para obtener el valor de next[i+1], luego podemos implementar esta parte.
Así que ¿cómo se hace?

Primero suponga: se establece next[i] = k, luego se establece esta fórmula: P0...Pk-1 = Px...Pi-1, se obtiene: P0...Pk-1 = Pi-k. .Pi -1; Análisis como se muestra a continuación:


Entonces asumimos que si Pk = Pi, podemos obtener P0...Pk = Pi-k..Pi, entonces esto es lo siguiente[i+1] = k+1;



Entonces: ¿qué pasa con Pk != Pi ?


 

Código

Código C:

#include<stdio.h>
#include<string.h>
#include<assert.h>
void GetNext(int* next, char* sub, int len2)
{
	next[0] = -1;//规定第一个为-1,第二个为0,则直接这样定义就好了;
	next[1] = 0;
	int k =0;//前一项的k
	int j = 2;//下一项
	while (j < len2)
	{
		if (k==-1||sub[j-1] == sub[k])
		{
			next[j] = k + 1;
			j++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}
int KMP(char* str, char* sub, int pos)
{
	assert(str != NULL && sub != NULL);
	int len1 = strlen(str);
	int len2 = strlen(sub);
	assert(pos >= 0 && pos < len1);
	int i = pos;//i从指定下标开始遍历
	int j = 0;
	int* next = (int*)malloc(sizeof(int) * len2);//动态开辟next和子串一样长
	assert(next != NULL);
	GetNext(next, sub, len2);
	while (i < len1 && j < len2)
	{
		if (j == -1||str[i] == sub[j])//j==-1是防止next[k]回退到-1的情况
		{
			i++;
			j++;
		}
		else {
			j = next[j];//如果不相等,则用next数组找到j的下个位置
		}
	}
	if (j >= len2)
	{
		return i - j;
	}
	else {
		return -1;
	}
}
int main()
{
	char* str = "ababcabcdabcde";
	char* sub = "abcd";
	printf("%d\n", KMP(str, sub, 0));
	return 0;
}

codigo java:

public static void getNext(int[] next, String sub){
next[0] = -1;
next[1] = 0;
int i = 2;//下一项
int k = 0;//前一项的K
while(i < sub.length()){//next数组还没有遍历完
if((k == -1) || sub.charAt(k) == sub.charAt(i-1)) {
next[i] = k+1;
i++;
k++;
}else{
k = next[k];
}
}
} 
public static int KMP(String s,String sub,int pos) {
int i = pos;
int j = 0;
int lens = s.length();
int lensub = sub.length();
int[] next= new int[sub.length()];
getNext(next,sub);
while(i < lens && j < lensub){
if((j == -1) || (s.charAt(i) == sub.charAt(j))){
i++;
j++;
}else{
j = next[j];
}
} 
if(j >= lensub) {
return i-j;
}else {
return -1;
}
} 
public static void main(String[] args) {
System.out.println(KMP("ababcabcdabcde","abcd",0));
System.out.println(KMP("ababcabcdabcde","abcde",0));
System.out.println(KMP("ababcabcdabcde","abcdef",0));
}

Explicación del código clave

demás{

   j=siguiente[j]

}

if (j == -1||str[i] == sub[j])
        {             i++;             j++;         }


 Pregunta : ¿Por qué todavía hay un j==-1?

Como se muestra en la siguiente figura: cuando el primer carácter no coincide, i, j son ambos 0 en este momento , j=siguiente[j] >> j=siguiente[0] >> j=-1;  en este momento j es -1, si no agrega j==-1, entonces el programa finalizará y no devolverá ninguna coincidencia, pero si observa detenidamente la figura a continuación, P[5]~P[8] coincide con la subcadena, por lo que el La respuesta es obviamente incorrecta. Entonces, debemos agregar el caso de j == -1 y dejar que atraviese desde el principio;

 

   siguiente[0] = -1;
    siguiente[1] = 0;
    int k =0;//k
    int j = 2 del elemento anterior;//siguiente elemento

 Según nuestras normas, el primer y segundo número de la siguiente matriz son -1 y 0, por lo que no hay problema. k=0 es el valor del elemento anterior k, y j=2 es el elemento siguiente.

if (k==-1||sub[j-1] == sub[k])
        {             siguiente[j] = k + 1;             j++;             k++;         } 



De acuerdo con el contenido anterior, podemos saber que p[j]==p[k], next[i]=k; entonces podemos deducir next[i+1]=k+1; como se muestra en la siguiente figura, pero aquí i es j- 1, todos deberían prestar atención a esto, p[j]==p[k]>>sub[j-1]==sub[k];next[i+1]=k+1 >>siguiente[j]= k+1;

 

más
        {             k = siguiente [k];         } 

Este punto de conocimiento se ha mencionado anteriormente, cuando p[j]!=p[k], k retrocede, siempre encuentra p[j]==p[k] y luego usa este next[i+1]=k+1 ;

una pequeña prueba

tema aquí >> subcadenas repetidas 

 Los socios interesados ​​pueden probarlo y lo discutiremos juntos en el próximo capítulo;

Complejidad temporal del algoritmo KMP

Supongamos que para encontrar la posición inicial de la cadena N en la cadena M, las longitudes son m y n respectivamente, utilizando el algoritmo KMP, generalmente se considera que la complejidad temporal es O(m+n), es decir, la complejidad temporal de calcular la siguiente matriz es O (n) y O (m) cuando se empareja.

 Lo anterior es la explicación del algoritmo KMP. Si hay alguna deficiencia o una mejor comprensión del código, deje un mensaje en el área de comentarios para discutir y progresar juntos.

Supongo que te gusta

Origin blog.csdn.net/m0_58367586/article/details/123073696
Recomendado
Clasificación