アルゴリズムノート(1)-KMPアルゴリズム

コンテンツ

ブルートフォースマッチング(BF)アルゴリズム

基本コンセプト

BFアルゴリズムの分析

コード 

小さなテスト

BFアルゴリズムの時間計算量

KMPアルゴリズム

基本コンセプト

KMPアルゴリズムの分析

次の配列を引き出す

コード

キーコードの説明

小さなテスト

KMPアルゴリズムの時間計算量


ブルートフォースマッチング(BF)アルゴリズム

基本コンセプト

BFアルゴリズムであるブルートフォースアルゴリズムは、一般的なパターンマッチングアルゴリズムです。BFアルゴリズムの考え方は、ターゲット文字列Sの最初の文字をパターン文字列の最初の文字と一致させることです。これらが等しい場合は、続行します。 Sを比較します。Sの2番目の文字とTの2番目の文字。等しくない場合は、Sの2番目の文字とTの最初の文字を比較し、最終的な一致結果が得られるまで順番に比較します。BFアルゴリズムはブルートフォースアルゴリズムです。

BFアルゴリズムの分析

定義を見るだけではわかりにくく、理解するのが難しいです。次に、あなたと一緒に学ぶための例を示します。

文字列「ababcabcdabcde」をメイン文字列として指定し、次にサブ文字列「abcd」を指定するとします。次に、サブ文字列がメイン文字列に表示されるかどうかを確認し、メイン文字列で最初に一致する添え字を返し、failが戻ります- 1.1。

  

この問題については、簡単に考えることができます。左から右に一致し、文字が等しい場合は、すべて1つ後ろにシフトされます。0の添え字で開始し、次回は1の添え字で開始します)

次のように初期化できます。

私たちの考えによれば、iポインターとjポインターが指す数値が一貫しているかどうかを比較する必要があります。一貫している場合は、後方に移動します。一貫していない場合は、次のようになります。

bとdが等しくない場合、iポインターはポインターの次の位置に戻され(ポインターは0添え字から開始されたばかり)、jポインターは0添え字に戻されて再開されます。

コード 

上記の分析に基づいて、コードの記述を始めましょう。

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

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

小さなテスト

上記の調査を通じて、私はBFアルゴリズムの予備的な理解を持っています。より深い理解と応用を得るために、私はあなたと一緒に次のテストの質問を完了します。

ここで質問をテストする>>strStr()を実装する

興味のあるパートナーはそれを試すことができます。次の章で一緒に議論します。

BFアルゴリズムの時間計算量

最良のケースは、マッチングの時間計算量が最初からO(1)であるということです。

最悪の場合は、最後の文字列が一致するたびに、「aaaaab」、サブ文字列「aab」などのメイン文字列とは異なることがわかるだけです。

 

 

 

 

上の写真を見ると、前回を除いて、残りは毎回最後まで一致していますが、ああ、私たちが違うことがわかります。

この場合、上の図では、パターン文字列は最初の3回にあり、3回一致するたびに一致せず、4回目まですべてが一致するため、移動を続ける必要がないため、一致の数は(6-3 + 1)* 3=12回です。

nのメイン文字列の長さとmのパターン文字列の長さの場合、最悪の場合の時間計算量はO((n --m + 1)* m)= O(n * m )。
考える友達は、検索の場合、前の文字がすべて一致しているので、iを1の位置に移動する必要がまったくないことに気付くと思います。次に、iを1の位置に移動し、jをに移動します。位置が0の場合、位置はずれており、明らかに一致しません。次に、上記の不要な手順を破棄し、ポインターのバックトラックを減らしてアルゴリズムを簡略化できます。つまり、位置は移動せず、移動するだけで済みます。今日私たちを導くj位置主人公のkmpアルゴリズム。

KMPアルゴリズム

基本コンセプト

KMPアルゴリズムは、DEKnuth、JH Morris、およびVRPrattによって提案された改良された文字列照合アルゴリズムであるため、人々はそれをKnuth-Morris-Platt操作(略してKMPアルゴリズム)と呼びます。KMPアルゴリズムの中核は、マッチングの失敗後の情報を使用して、パターン文字列とメイン文字列の間のマッチング時間を最小限に抑え、高速マッチングの目的を達成することです特定の実装はnext()関数を介して行われ、関数自体にはパターン文字列のローカルマッチング情報が含まれています。KMPアルゴリズムの時間計算量はO(m + n)です。

違い: K MPとBFの唯一の違いは、メインストリングのiが戻らず、jが位置0に移動しないことです。

KMPアルゴリズムの分析

文字列「ababcabcdabcde」をメイン文字列として指定し、次にサブ文字列「abcd」を指定するとします。次に、サブ文字列がメイン文字列に表示されるかどうかを確認し、メイン文字列で最初に一致する添え字を返し、failが戻ります- 1.1。

1.まず、メインの文字列がロールバックされない理由を例に挙げます。

2.jフォールバックの場所

では、jはどのようにして下付き文字2の位置でカプセルにフォールバックするのでしょうか。以下では、次の配列に進みます

次の配列を引き出す

KMPの本質は次の配列です。つまり、next [j] = k ;で表され、異なるjはK値に対応し、このKは将来移動するjの位置ですそして、Kの値は次のように計算されます。

  •  規則:成功した部分に一致する2つの等しい適切な部分文字列(それ自体を除く)を見つけます。1つは下付き文字0文字で始まり、もう1つはj-1下付き文字で終わります。
  • next [0] = -1; next [1] = 0;ここでは、下付き文字から始め、言及された回数は1から始まります。

次の配列を見つけるための演習:  

演習1:たとえば、「ababcabcdabcde」の場合、次の配列を見つけますか?

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

演習2:「abcabcabcabcdabcde」の次の配列を見つけますか?"
-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0

ここで重要な
のは、次の配列を見つける方法に誰もが問題がないはずです。次の質問は、next [i]=k;次の[i+1]=を見つける方法がわかっているかどうかです
。 pass next [i]の値を、next [i + 1]の値を取得するための一連の変換を介して実行すると、この部分を実装できます。
それで、それをどのように行うのですか?

最初に仮定:next [i] = kが確立され、次にこの式が確立されます:P0 ... Pk-1 = Px ... Pi-1、get:P0 ... Pk-1=Pi-k。.Pi -1;以下に示す分析:


次に、Pk=Piの場合;P0... Pk=Pi-k..Pi;を取得できると仮定します。これはnext[i+ 1] = k+1;です。



だから:Pk!= Piはどうですか?


 

コード

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

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

キーコードの説明

そうしないと{

   j = next [j]

}

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


 質問なぜまだj ==-1があるのですか?

次の図に示すように、最初の文字が一致しない場合、この時点でi、jは両方とも0であり、j = next [j] >> j = next [0] >> j=-1; この時点でjは-1です。j==-1を追加しないと、プログラムは終了して一致を返しませんが、下の図をよく見ると、P [5]〜P [8]は部分文字列と一致するため、答えは明らかに間違っているので、j ==-1の場合を追加して、最初からトラバースさせる必要があります。

 

   next [0] = -1;
    next [1] = 0;
    int k = 0; // k
    int j=2前のアイテム;//次のアイテム

 規定によると、次の配列の1番目と2番目の数字は-1と0なので、問題ありません。k = 0は前のアイテムkの値であり、j=2は次のアイテムです。

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



上記の内容から、p [j] == p [k]、next [i] = kであることがわかり、次の図に示すように、 next [i + 1] = k+1;を推定できます。しかし、ここで私はj-1です、誰もがこれに注意を払う必要があります、p [j] == p [k] >> sub [j-1] == sub [k]; next [i + 1] = k + 1 >> next [j] = k + 1;

 

else
        {             k = next [k];         } 

この知識ポイントは上記のとおりです。p[j]!= p [k]の場合、kはロールバックし、常にp [j] == p [k]を見つけて、このnext [i + 1] = k+1を使用します。 ;

小さなテスト

ここのトピック>>繰り返される部分文字列 

 興味のあるパートナーはそれを試すことができます。次の章で一緒に議論します。

KMPアルゴリズムの時間計算量

Mストリング内のNストリングの開始位置を見つけるとすると、KMPアルゴリズムを使用して、長さはそれぞれmとnであり、時間計算量はO(m + n)、つまり時間計算量であると一般に考えられます。次の配列を計算するのはO(n)であり、マッチングの場合はO(m)です。

 上記はKMPアルゴリズムの説明です。コードに欠点やより良い洞察がある場合は、コメント領域にメッセージを残して、一緒に話し合い、進歩を遂げてください。! 

おすすめ

転載: blog.csdn.net/m0_58367586/article/details/123073696