Notas do algoritmo (1) - Algoritmo KMP

contente

Algoritmo de correspondência de força bruta (BF)

conceito básico

Analisando Algoritmos BF

Código 

Um pequeno teste

Complexidade de tempo do algoritmo BF

Algoritmo KMP

conceito básico

Analisando algoritmos KMP

Traga a próxima matriz

Código

Explicação do código-chave

Um pequeno teste

Complexidade de tempo do algoritmo KMP


Algoritmo de correspondência de força bruta (BF)

conceito básico

O algoritmo BF, o algoritmo Brute Force , é um algoritmo comum de correspondência de padrões. A ideia do algoritmo BF é combinar o primeiro caractere da string de destino S com o primeiro caractere da string padrão. Se forem iguais, continue para comparar S. O segundo caractere de S e o segundo caractere de T. Se não forem iguais, compare o segundo caractere de S e o primeiro caractere de T e compare-os sucessivamente até obter o resultado final correspondente. O algoritmo BF é um algoritmo de força bruta.

Analisando Algoritmos BF

Apenas olhar para a definição é obscuro e difícil de entender. A seguir, darei um exemplo para aprender com você:

Suponha que forneçamos a string "ababcabcdabcde" como a string principal e, em seguida, forneçamos a substring "abcd", agora precisamos descobrir se a substring aparece na string principal, retornar o primeiro subscrito correspondente na string principal, fail retorna - 1.

  

Para este problema, podemos facilmente pensar nisso: combinando da esquerda para a direita, se os caracteres forem iguais, todos serão deslocados para trás em um; Comece com 0 subscrito, da próxima vez comece com 1 subscrito)

Podemos inicializar assim:

De acordo com nossas ideias, então precisamos comparar se os números apontados pelo ponteiro i e pelo ponteiro j são consistentes. Se forem consistentes, eles retrocederão. Se forem inconsistentes, conforme mostrado abaixo:

Se b e d não forem iguais, então o ponteiro i é retornado para a próxima posição do ponteiro agora (o ponteiro começou do subscrito 0), e o ponteiro j é retornado para o subscrito 0 e começa novamente.

Código 

Com base na análise acima, vamos começar a escrever o 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;
}

código 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"));
}
}

Um pequeno teste

Através do estudo acima, tenho uma compreensão preliminar do algoritmo BF. Para ter um entendimento e aplicação mais profundos, responderei às seguintes perguntas do teste com você:

Perguntas de teste aqui >> Implemente strStr()

Os parceiros interessados ​​podem experimentá-lo, e discutiremos juntos no próximo capítulo;

Complexidade de tempo do algoritmo BF

O melhor caso é que a complexidade de tempo de correspondência é O(1) desde a primeira vez;

O pior caso é que cada vez que o último é correspondido apenas para descobrir que é diferente da string principal, como "aaaaab", substring "aab"

 

 

 

 

Olhando para a foto acima, exceto pela última vez, o resto é combinado até o final todas as vezes, apenas para descobrir, ah, somos diferentes.

Neste caso, na figura acima, a string do padrão está nas 3 primeiras vezes, e cada vez corresponde 3 vezes, e não corresponde, até a 4ª vez, todas as correspondências, não há necessidade de continuar a se mover, então o número de partidas é (6 - 3 + 1) * 3 = 12 vezes.

Pode-se ver que, para o comprimento da cadeia principal de n e o comprimento da cadeia padrão de m, a complexidade de tempo do pior caso é O((n - m + 1) * m) = O(n * m ).
Acredito que amigos pensantes descobrirão que, se for para pesquisar, não há necessidade de mover i para a posição 1, porque os caracteres anteriores são todos correspondentes, então mova i para a posição 1 e mova j para a posição de 0 , a posição é escalonada, e obviamente não vai corresponder, então podemos descartar as etapas desnecessárias acima, reduzir o retrocesso do ponteiro para simplificar o algoritmo, há uma ideia, i posição não se move, só precisa mover a posição j , que nos leva hoje O algoritmo KMP protagonista.

Algoritmo KMP

conceito básico

O algoritmo KMP é um algoritmo aprimorado de correspondência de strings proposto por DEKnuth, JH Morris e VRratt, então as pessoas o chamam de operação Knuth-Morris-Platt (algoritmo KMP para abreviar). O núcleo do algoritmo KMP é usar as informações após a falha de correspondência para minimizar os tempos de correspondência entre a string padrão e a string principal para atingir o objetivo de correspondência rápida . A implementação específica é por meio de uma função next() , e a própria função contém as informações de correspondência local da string padrão. A complexidade de tempo do algoritmo KMP é O(m+n).

Diferença: A única diferença entre K MP e BF é que i da minha string principal não voltará e j não se moverá para a posição 0.

Analisando algoritmos KMP

Suponha que forneçamos a string "ababcabcdabcde" como a string principal e, em seguida, forneçamos a substring "abcd", agora precisamos descobrir se a substring aparece na string principal, retornar o primeiro subscrito correspondente na string principal, fail retorna - 1.

1. Primeiro, dê um exemplo, por que a string principal não é revertida

2.j Localização de reserva

Então, como j retorna à cápsula na posição do subscrito 2? Abaixo levamos ao próximo array

Traga a próxima matriz

A essência do KMP é o próximo array: ou seja, ele é representado por next[j] = k ;, diferente j corresponde a um valor K, e este K é a posição do j que você deseja mover no futuro . E o valor de K é calculado assim:

  •  Regra: Encontre duas substrings próprias iguais (excluindo-se) que correspondam à parte bem-sucedida, uma começando com o caractere subscrito 0 e a outra terminando com o caractere subscrito j-1.
  • Não importa quais dados next[0] = -1; next[1] = 0; aqui, começamos com subscritos e o número de vezes mencionado começa em 1;

Exercícios para encontrar o próximo array:  

Exercício 1: Por exemplo, para "ababcabcdabcde", encontre seu próximo array?

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

Exercício 2: Encontre a próxima matriz de "abcabcabcabcdabcde"? "
-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0

Aqui vem a coisa principal:
aqui todos não devem ter problemas em como encontrar o próximo array. A próxima pergunta é, se sabemos que next[i] = k; como encontrar next[i+1] =
? pass next O valor de [i], através de uma série de conversões para obter o valor de next[i+1], então podemos implementar essa parte.
Então, como fazê-lo?

Primeiro suponha: next[i] = k é estabelecido, então esta fórmula é estabelecida: P0...Pk-1 = Px...Pi-1, obtenha: P0...Pk-1 = Pi-k. .Pi -1; Análise conforme mostrado abaixo:


Então assumimos que se Pk = Pi; podemos obter P0...Pk = Pi-k..Pi; então este é o próximo[i+1] = k+1;



Então: e quanto a 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;
}

código 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));
}

Explicação do código-chave

outro{

   j=próximo[j]

}

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


 Pergunta : Por que ainda existe um j==-1?

Como mostrado na figura abaixo: quando o primeiro caractere não corresponde, i, j são ambos 0 neste momento , j=next[j] >> j=next[0] >> j=-1;  neste momento j é -1 , Se você não adicionar j==-1, o programa terminará e não retornará nenhuma correspondência, mas se você observar atentamente a figura abaixo, P[5]~P[8] corresponderá à substring, então o a resposta está obviamente errada, então devemos adicionar o caso de j==-1 e deixá-lo percorrer desde o início;

 

   next[0] = -1;
    next[1] = 0;
    int k =0;//k
    int j = 2 do item anterior;//próximo item

 De acordo com nossos regulamentos, o primeiro e o segundo números da próxima matriz são -1 e 0, portanto, não há problema. k=0 é o valor do item anterior k, e j=2 é o próximo item.

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



De acordo com o conteúdo acima, podemos saber que p[j]==p[k], next[i]=k; então podemos deduzir next[i+1]=k+1; como mostra a figura abaixo, mas aqui i é j- 1, todos deveriam prestar atenção nisso, p[j]==p[k]>>sub[j-1]==sub[k];next[i+1]=k+1 >>próximo[j]= k+1;

 

senão
        {             k = próximo[k];         } 

Este ponto de conhecimento foi mencionado acima, quando p[j]!=p[k], k reverte, sempre encontra p[j]==p[k] e então usa este next[i+1]=k+1 ;

Um pequeno teste

tópico aqui >> substrings repetidas 

 Os parceiros interessados ​​podem experimentá-lo, e discutiremos juntos no próximo capítulo;

Complexidade de tempo do algoritmo KMP

Suponha que para encontrar a posição inicial da string N na string M, os comprimentos são m e n respectivamente, usando o algoritmo KMP, geralmente considera-se que a complexidade de tempo é O(m+n), ou seja, a complexidade de tempo de calcular a próxima matriz é O (n) e O(m) ao combinar.

 O acima é a explicação do algoritmo KMP. Se houver alguma falha ou melhor compreensão do código, por favor, deixe uma mensagem na área de comentários para discutir e progredir juntos!

Acho que você gosta

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