Explicación detallada del algoritmo KMP de la estructura de datos

Tabla de contenido

1. ¿Qué es el algoritmo KMP?

2. El origen del algoritmo KMP

2.1 Problemas a resolver

2.2 El método que me vino a la mente al principio

2.3 Nació el algoritmo KMP

3. Explicación detallada del algoritmo KMP

4. Realización del algoritmo KMP

5. Mejora del algoritmo KMP


1. ¿Qué es el algoritmo KMP?

  • El algoritmo KMP es un algoritmo mejorado de coincidencia de cadenas, es decir, un algoritmo que puede encontrar rápidamente una subcadena de la cadena principal , propuesto por DEKnuth, JH Morris y VRRatt, por lo que la gente lo llama operación Knut-Morris-Pratt (algoritmo KMP para abreviar ).
  • El núcleo del algoritmo KMP es utilizar la información después de la falla de coincidencia para minimizar el número de coincidencias entre la cadena de patrones y la cadena principal para lograr el propósito de una coincidencia rápida. La implementación específica se realiza a través de una función next(), y la función misma contiene la información de coincidencia parcial de la cadena de patrones.

2. El origen del algoritmo KMP

2.1 Problemas a resolver

El problema que mencionamos en el capítulo "cadena" de la estructura de datos: el problema de encontrar la subcadena de la cadena principal,
por ejemplo: ahora tenemos la cadena principal: arr1[ ] =  "abababc" y la subcadena: arr2[ ] =  "abc".

2.2 El método que me vino a la mente al principio

Lo que pensamos al principio fue una solución de fuerza bruta. Hacemos coincidir la subcadena con la cadena principal una por una. Si el primer carácter es igual, continuaremos haciendo coincidir el segundo carácter hasta que la subcadena y la cadena principal coincidan con éxito. , y luego devolver la posición de la subcadena. , una vez que uno de los dos caracteres no coincida, la cadena principal volverá al siguiente carácter que inició el carácter coincidente, y la subcadena volverá al primer carácter.
De los ejemplos anteriores, podemos ver que la solución tradicional de fuerza bruta requiere múltiples retrocesos, y tanto la primera cadena como la segunda cadena deben retroceder. Obviamente, el retroceso debe ser incorrecto. ¿Hay alguna forma de que él retroceda? A un posición específica, no generará muchos pasos innecesarios como una solución violenta, por lo que ese es el enfoque de este artículo: nació el algoritmo KMP

2.3 Nació el algoritmo KMP

El algoritmo KMP es un algoritmo mejorado de coincidencia de cadenas, es decir, un algoritmo que puede encontrar rápidamente subcadenas a partir de la cadena principal.

3. Explicación detallada del algoritmo KMP

(1) Ahora veamos primero una imagen: la primera barra larga representa la cadena principal y la segunda barra larga representa la subcadena. Cuando se descubre que el elemento señalado por el puntero no coincide, el puntero vuelve al primero elemento Esta es la eficiencia del algoritmo La razón de la baja se llama algoritmo de coincidencia ingenua.

(2) Ahora echemos un vistazo a cómo se ve el algoritmo inteligente de coincidencia KMP: en la figura a continuación, cuando el elemento al que apunta el puntero no coincide, el modo de movimiento cambia, de modo que el prefijo se mueve al sufijo

(3) Después de comprender este paso, casi se domina la esencia del algoritmo KMP. De hecho, la cadena anterior a cada carácter tiene el sufijo igual más largo, y la longitud del sufijo igual más largo es la clave de nuestro cambio, por lo que usamos una siguiente matriz separada para almacenar el sufijo igual más largo de la longitud de la subcadena. Y el valor de la siguiente matriz solo está relacionado con la propia subcadena.
Entonces next[i]=j significa: la longitud de la cadena de caracteres antes del subíndice i es la más larga y la longitud del prefijo y el sufijo es j.

y ponerlos en una matriz

4. Realización del algoritmo KMP

Ahora analicemos la complejidad temporal del algoritmo KMP:
El algoritmo KMP tiene un proceso adicional de encontrar una matriz, que consume un poco más de espacio. Suponemos que la longitud de la cadena principal s es n, y la longitud de la subcadena t es m. Al encontrar la siguiente matriz, la complejidad de tiempo es O(m). Debido a que la cadena principal no retrocede en la coincidencia posterior, el número de comparaciones se puede registrar como n, por lo que la complejidad de tiempo total del algoritmo KMP es O(m +n), y la complejidad del espacio se registra como O(m). En comparación con la complejidad de tiempo de coincidencia de patrones simple O (m * n), la aceleración del algoritmo KMP es muy grande. Es muy significativo intercambiar un poco de consumo de espacio por una aceleración de tiempo muy alta. Esta idea también es muy importante. .

  • Apreciemos como la computadora obtiene el siguiente arreglo
typedef struct
{	
	char data[MaxSize];
	int length;			//串长
} SqString;
//SqString 是串的数据结构
//typedef重命名结构体变量,可以用SqString t定义一个结构体。
void GetNext(SqString t,int next[])		//由模式串t求出next值
{
	int j,k;
	j=0;k=-1;
	next[0]=-1;//第一个字符前无字符串,给值-1
	while (j<t.length-1) 
	//因为next数组中j最大为t.length-1,而每一步next数组赋值都是在j++之后
	//所以最后一次经过while循环时j为t.length-2
	{	
		if (k==-1 || t.data[j]==t.data[k]) 	//k为-1或比较的字符相等时
		{	
			j++;k++;
			next[j]=k;
			//对应字符匹配情况下,s与t指向同步后移
			//通过字符串"aaaaab"求next数组过程想一下这一步的意义
			//printf("(1) j=%d,k=%d,next[%d]=%d\n",j,k,j,k);
       	}
       	else
		{
			k=next[k];
			**//我们现在知道next[k]的值代表的是下标为k的字符前面的字符串最长相等前后缀的长度
			//也表示该处字符不匹配时应该回溯到的字符的下标
			//这个值给k后又进行while循环判断,此时t.data[k]即指最长相等前缀后一个字符**
			//为什么要回退此处进行比较,我们往下接着看。其实原理和上面介绍的KMP原理差不多
			//printf("(2) k=%d\n",k);
		}
	}
}
  • Explicación del código del algoritmo KMP
int KMPIndex(SqString s,SqString t)  //KMP算法
{

	int next[MaxSize],i=0,j=0;
	GetNext(t,next);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{
			i++;j++;  			//i,j各增1
		}
		else j=next[j]; 		//i不变,j后退,现在知道为什么这样让子串回退了吧
    }
    if (j>=t.length)
		return(i-t.length);  	//返回匹配模式串的首字符下标
    else  
		return(-1);        		//返回不匹配标志
}

5. Mejora del algoritmo KMP

¿Por qué es necesario mejorar el algoritmo KMP a pesar de que es tan poderoso?
Echemos un vistazo a un ejemplo:
la cadena principal s="aaaaabaaaaac"
subcadena t="aaaaac"
En este ejemplo, cuando 'b' y 'c' no coinciden, 'b' debe compararse con la 'a' anterior 'c' , que obviamente no coincide. 'a' antes de 'c' sigue siendo 'a' después de retroceder.
Sabemos que no hay necesidad de comparar 'b' con 'a', porque el carácter retrocedido es el mismo que el carácter original, y el carácter original no coincide, y el carácter retrocedido es naturalmente imposible de igualar. Sin embargo, el algoritmo KMP todavía compara 'b' con la 'a' retrocedida. Aquí es donde podemos mejorar. Nuestra siguiente matriz mejorada se llama: matriz nextval. La mejora del algoritmo KMP se puede describir brevemente de la siguiente manera: si el carácter del bit a es igual al carácter del bit b al que apunta su siguiente valor, entonces el valor siguiente del bit a apunta al valor siguiente del valor b. -bit, si no son iguales, el siguiente valor del a-bit es El siguiente valor de su propio a bit. Esta debería ser la explicación más obvia. Por ejemplo, las matrices next y nextval de la cadena "ababaaab" son respectivamente:
subíndice | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
subcadena | a | b | a | b | a | a | a |b|
siguiente|-1|0|0|1|2|3|1|1|
nextval|-1|0|-1|0|-1|3|1|0|

Analicemos el código:

void GetNextval(SqString t,int nextval[])  
//由模式串t求出nextval值
{
	int j=0,k=-1;
	nextval[0]=-1;
   	while (j<t.length) 
	{
       	if (k==-1 || t.data[j]==t.data[k]) 
		{	
			j++;k++;
			if (t.data[j]!=t.data[k]) 
//这里的t.data[k]是t.data[j]处字符不匹配而会回溯到的字符
//为什么?因为没有这处if判断的话,此处代码是next[j]=k;
//next[j]不就是t.data[j]不匹配时应该回溯到的字符位置嘛
				nextval[j]=k;
           	else  
				nextval[j]=nextval[k];
//这一个代码含义是不是呼之欲出了?
//此时nextval[j]的值就是就是t.data[j]不匹配时应该回溯到的字符的nextval值
//用较为粗鄙语言表诉:即字符不匹配时回溯两层后对应的字符下标
       	}
       	else  k=nextval[k];    	
	}

}
int KMPIndex1(SqString s,SqString t)    
//修正的KMP算法
//只是next换成了nextval
{
	int nextval[MaxSize],i=0,j=0;
	GetNextval(t,nextval);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{	
			i++;j++;	
		}
		else j=nextval[j];
	}
	if (j>=t.length)  
		return(i-t.length);
	else
		return(-1);
}

Supongo que te gusta

Origin blog.csdn.net/weixin_43313333/article/details/131362556
Recomendado
Clasificación