Anmerkungen zum Algorithmus (1) - KMP-Algorithmus

Inhalt

Brute-Force-Matching (BF)-Algorithmus

Basiskonzept

Analysieren von BF-Algorithmen

Code 

Ein kleiner Test

Zeitkomplexität des BF-Algorithmus

KMP-Algorithmus

Basiskonzept

Analysieren von KMP-Algorithmen

Bringen Sie das nächste Array heraus

Code

Erläuterung des Tastencodes

Ein kleiner Test

Zeitkomplexität des KMP-Algorithmus


Brute-Force-Matching (BF)-Algorithmus

Basiskonzept

Der BF-Algorithmus, der Brute-Force-Algorithmus , ist ein gängiger Mustervergleichsalgorithmus. Die Idee des BF-Algorithmus besteht darin, das erste Zeichen der Zielzeichenfolge S mit dem ersten Zeichen der Musterzeichenfolge abzugleichen. Wenn sie gleich sind, fahren Sie fort zum Vergleichen von S. Das zweite Zeichen von S und das zweite Zeichen von T. Wenn sie nicht gleich sind, vergleichen Sie das zweite Zeichen von S und das erste Zeichen von T und vergleichen Sie sie der Reihe nach, bis das endgültige Übereinstimmungsergebnis erhalten wird. Der BF-Algorithmus ist ein Brute-Force-Algorithmus.

Analysieren von BF-Algorithmen

Nur auf die Definition zu schauen ist undurchsichtig und schwer zu verstehen.Als nächstes werde ich ein Beispiel geben, um mit Ihnen zu lernen:

Angenommen, wir geben die Zeichenfolge "ababcabcdabcde" als Hauptzeichenfolge und dann die Teilzeichenfolge "abcd" an. Jetzt müssen wir herausfinden, ob die Teilzeichenfolge in der Hauptzeichenfolge vorkommt, den ersten übereinstimmenden Index in der Hauptzeichenfolge zurückgeben, Fehler zurückgeben - 1.

  

Für dieses Problem können wir uns leicht vorstellen: Abgleich von links nach rechts, wenn die Zeichen gleich sind, werden sie alle um eins nach hinten verschoben; Beginnen Sie mit 0 tiefgestellt, beim nächsten Mal beginnen Sie mit 1 tiefgestellt)

Wir können so initialisieren:

Nach unseren Vorstellungen müssen wir dann vergleichen, ob die Zahlen, auf die der i-Zeiger und der j-Zeiger zeigen, konsistent sind. Wenn sie konsistent sind, bewegen sie sich rückwärts. Wenn sie inkonsistent sind, wie unten gezeigt:

Wenn b und d nicht gleich sind, dann wird der i-Zeiger zur nächsten Position des Zeigers gerade jetzt zurückgebracht (der Zeiger hat gerade mit der tiefgestellten 0 begonnen), und der j-Zeiger wird zur tiefgestellten 0 zurückgebracht und beginnt erneut.

Code 

Beginnen wir basierend auf der obigen Analyse mit dem Schreiben des Codes:

C-Code:

#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;
}

Java-Code:

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

Ein kleiner Test

Durch die obige Studie habe ich ein vorläufiges Verständnis des BF-Algorithmus. Um ein tieferes Verständnis und eine Anwendung zu haben, werde ich die folgenden Testfragen mit Ihnen bearbeiten:

Testfragen hier >> strStr() implementieren

Interessierte Partner können es ausprobieren, und wir werden es im nächsten Kapitel gemeinsam diskutieren;

Zeitkomplexität des BF-Algorithmus

Der beste Fall ist, dass die Zeitkomplexität des Abgleichs vom ersten Mal an O(1) ist;

Der schlimmste Fall ist, dass jedes Mal, wenn der letzte abgeglichen wird, nur festgestellt wird, dass er sich von der Hauptzeichenfolge unterscheidet, z. B. "aaaaab", Teilzeichenfolge "aab".

 

 

 

 

Wenn man sich das Bild oben ansieht, wird der Rest bis auf das letzte Mal jedes Mal bis zum Ende angepasst, nur um herauszufinden, ah, wir sind anders.

In diesem Fall, in der obigen Abbildung, ist die Musterzeichenfolge in den ersten 3-mal, und jedes Mal, wenn es 3-mal übereinstimmt, und nicht übereinstimmt, bis zum 4. Mal, alle Übereinstimmungen, keine Notwendigkeit, sich weiter zu bewegen, also die Zahl der Spiele ist (6 - 3 + 1) * 3 = 12 Mal.

Es ist ersichtlich, dass für die Länge des Hauptstrings von n und die Länge des Musterstrings von m die Zeitkomplexität im ungünstigsten Fall O((n - m + 1) * m) = O(n * m) ist ).
Ich glaube, dass denkende Freunde feststellen werden, dass es für die Suche überhaupt nicht nötig ist, i an die Position 1 zu verschieben, da die vorherigen Zeichen alle übereinstimmen, dann i an die Position 1 und j an verschieben die Position von 0 , die Position ist versetzt, und offensichtlich wird sie nicht übereinstimmen, dann können wir die unnötigen Schritte oben verwerfen, die Zeigerrückverfolgung reduzieren , um den Algorithmus zu vereinfachen, es gibt eine Idee, i-Position bewegt sich nicht, muss sich nur bewegen die j-position , die uns heute führt Der Protagonist kmp-Algorithmus.

KMP-Algorithmus

Basiskonzept

Der KMP-Algorithmus ist ein verbesserter String-Matching-Algorithmus , der von DEKnuth, JH Morris und VRPratt vorgeschlagen wurde, daher nennen die Leute ihn Knuth-Morris-Platt-Operation (kurz KMP-Algorithmus). Der Kern des KMP - Algorithmus besteht darin , die Informationen nach dem Abgleichfehler zu verwenden , um die Abgleichzeiten zwischen der Musterfolge und der Hauptfolge zu minimieren , um den Zweck eines schnellen Abgleichs zu erreichen . Die spezifische Implementierung erfolgt durch eine next()-Funktion , und die Funktion selbst enthält die lokalen Übereinstimmungsinformationen der Musterzeichenfolge. Die Zeitkomplexität des KMP-Algorithmus ist O(m+n).

Unterschied: Der einzige Unterschied zwischen K MP und BF besteht darin, dass i meiner Hauptsaite nicht zurückgeht und j sich nicht auf Position 0 bewegt.

Analysieren von KMP-Algorithmen

Angenommen, wir geben die Zeichenfolge "ababcabcdabcde" als Hauptzeichenfolge und dann die Teilzeichenfolge "abcd" an. Jetzt müssen wir herausfinden, ob die Teilzeichenfolge in der Hauptzeichenfolge vorkommt, den ersten übereinstimmenden Index in der Hauptzeichenfolge zurückgeben, Fehler zurückgeben - 1.

1. Geben Sie zunächst ein Beispiel, warum der Hauptstring nicht zurückgerollt wird

2.j Fallback-Standort

Wie also fällt j auf die Kapsel an der Position von Index 2 zurück? Unten führen wir zum nächsten Array

Bringen Sie das nächste Array heraus

Die Essenz von KMP ist das next-Array: Das heißt, es wird dargestellt durch next[j] = k ;, different j entspricht einem K-Wert, und dieses K ist die Position des j, das Sie in der Zukunft verschieben möchten . Und der Wert von K wird wie folgt berechnet:

  •  Regel: Finden Sie zwei gleiche echte Teilzeichenfolgen (mit Ausnahme von sich selbst), die mit dem erfolgreichen Teil übereinstimmen, wobei eine mit dem tiefgestellten Zeichen 0 beginnt und die andere mit dem tiefgestellten Zeichen j-1 endet.
  • Egal welche Daten next[0] = -1; next[1] = 0; hier beginnen wir mit Indizes und die Anzahl der Nennungen beginnt bei 1;

Übungen zum Finden des nächsten Arrays:  

Übung 1: Zum Beispiel für "ababcabcdabcde", das nächste Array finden?

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

Übung 2: Finden Sie das nächste Array von "abcabcabcabcdabcde"? "
-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0

Hier kommt der Kern:
Hier sollte jeder kein Problem damit haben, wie man das nächste Array findet.Die nächste Frage ist, ob wir wissen, dass next[i] = k; wie findet man next[i+1] = ?
, wenn wir können pass next Der Wert von [i] durch eine Reihe von Konvertierungen, um den Wert von next[i+1] zu erhalten, dann können wir diesen Teil implementieren.
Wie geht das?

Nehmen wir zuerst an: next[i] = k wird aufgestellt, dann wird diese Formel aufgestellt: P0...Pk-1 = Px...Pi-1, erhalte: P0...Pk-1 = Pi-k..Pi -1; Analyse wie unten gezeigt:


Dann nehmen wir an, dass wenn Pk = Pi, wir P0...Pk = Pi-k..Pi erhalten können, dann ist dies next[i+1] = k+1;



Also: was ist mit Pk != Pi ?


 

Code

C-Code:

#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;
}

Java-Code:

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));
}

Erläuterung des Tastencodes

anders{

   j=weiter[j]

}

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


 Frage : Warum gibt es trotzdem ein j==-1?

Wie in der Abbildung unten gezeigt: Wenn das erste Zeichen nicht übereinstimmt, sind i, j zu diesem Zeitpunkt beide 0 , j=next[j] >> j=next[0] >> j=-1;  zu diesem Zeitpunkt j ist -1 , Wenn Sie j==-1 nicht hinzufügen, endet das Programm und gibt keine Übereinstimmung zurück, aber wenn Sie sich die Abbildung unten genau ansehen, stimmt P[5]~P[8] mit der Teilzeichenfolge überein, also die Antwort ist offensichtlich falsch.Wir sollten also den Fall von j==-1 hinzufügen und ihn von Anfang an durchlaufen lassen;

 

   next[0] = -1;
    next[1] = 0;
    int k =0;//k
    int j = 2 des vorherigen Elements;//nächstes Element

 Gemäß unseren Vorschriften sind die erste und zweite Zahl des nächsten Arrays -1 und 0, also gibt es kein Problem. k=0 ist der Wert des vorherigen Elements k und j=2 ist das nächste Element.

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



Gemäß dem obigen Inhalt können wir wissen, dass p[j]==p[k], next[i]=k; dann können wir next[i+1]=k+1 ableiten; wie in der Abbildung unten gezeigt, aber hier ist i j- 1, darauf sollte jeder achten, p[j]==p[k]>>sub[j-1]==sub[k];next[i+1]=k+1 >>weiter[j]= k+1;

 

Sonst
        {             k = next[k];         } 

Dieser Wissenspunkt wurde oben erwähnt, wenn p[j]!=p[k], k zurückrollt, immer p[j]==p[k] findet und dieses dann next[i+1]=k+1 verwendet ;

Ein kleiner Test

Thema hier >> wiederholte Teilzeichenfolgen 

 Interessierte Partner können es ausprobieren, und wir werden es im nächsten Kapitel gemeinsam diskutieren;

Zeitkomplexität des KMP-Algorithmus

Angenommen, um die Startposition der N-Saite in der M-Saite zu finden, sind die Längen m bzw. n, unter Verwendung des KMP-Algorithmus wird allgemein angenommen, dass die Zeitkomplexität O (m + n) ist, dh die Zeitkomplexität der Berechnung des nächsten Arrays ist O (n) und O (m) beim Abgleich.

 Das Obige ist die Erklärung des KMP-Algorithmus. Wenn es Mängel oder bessere Einblicke in den Code gibt, hinterlassen Sie bitte eine Nachricht im Kommentarbereich, um gemeinsam zu diskutieren und Fortschritte zu erzielen!

Ich denke du magst

Origin blog.csdn.net/m0_58367586/article/details/123073696
Empfohlen
Rangfolge