KMPアルゴリズムの詳細
まず、KMPアルゴリズムについて説明します。
KMPアルゴリズムによって解決される問題は、文字列(メイン文字列とも呼ばれます)内のパターンを見つけることです。簡単に言えば、私たちが通常言うキーワード検索です。パターン文字列はキーワード(以下ではPと呼びます)です。メイン文字列(以下ではTと呼びます)にある場合は特定の位置を返し、そうでない場合は-1(一般的に使用される手段)を返します。
この種の問題の解決について
- ブルートフォース法、時間計算量O(N ∗ MN * MN∗M)(Nはメイン文字列の長さ、Mはパターン文字列の長さ)、パターン文字列は、一致が成功するまでメイン文字列の各位置を一致させようとします。
たとえば
、2つの文字列が一致すると、 P文字列はメイン文字列から始まりますT最初の文字が一致し始め、赤い位置は最初の文字が一致しない位置です。このとき、
一致しない文字の添え字はi = j = 3です。次の一致は、Pが添え字0から始まり、Tが添え字1から始まり、このプロセスを繰り返すことです。
この方法は暴力的すぎるため、次のようになります。より高速な方法であるKMPアルゴリズムを導入する - KMPアルゴリズムの時間計算量O(NNN) Nの主文字列の長さは
通常の考え方です。上図の一致の場合、初めて不一致が発生したときは、P文字列のaと添え字3を考えるのが自然です。 T文字列、つまり一致する文字では、図を参照してください。
つまり、T i添え字文字列定数、P j文字列はゼロになります。つまり、この部分一致の利点には有効な情報があり、ポインタjを変更してもポインタiは抑制されません。パターン文字列を可能な限り有効な場所に移動してみてください。
データセットをもう一度見てみましょう。
下付き文字3でTとPが一致しない場合、上記のように、iは移動せず、P文字列は有効な位置に移動します。次に比較を開始すると、位置はj = 2です。
実際、これはプッシュプロセスのようなものです。jの位置は、PとTが一致しない前の、文字列内の最長の共通プレフィックスサフィックスの値によって決定されます。
まず、接頭辞と接尾辞を説明します。例として文字列を取り上げます。
接頭辞は、次の図に示すように、最初の文字を含み、最後の文字を含まないすべての連続した部分文字列です。
接尾辞は、最後の文字と最初の文字列は含まれません。文字のすべての連続する部分文字列
ここで、パターン文字列の次の配列を見つけたいと思います。next[i]は、最初のi-1文字の最長の共通プレフィックス接尾辞の値を表します。人工規制next [0] = -1、next [1] = 0;
- 文字が1つしかない場合は、その前に文字がないので意味がありません。
- 2文字の場合、上記のP文字列を例にとると、next [1]はbの前のaの最長の共通プレフィックスサフィックスの値を検索しますが、文字が1つしかないため、プレフィックスには最後のプレフィックスが含まれません文字と接尾辞に最初の文字が含まれていないため、値は0です。
next [6]の場合、フロントabcabcの最長の共通プレフィックスとサフィックスはabcであるため、next [6] = 3
次の配列を解く方法
next [2]から始めます。前述のように、0と1の位置は人為的に指定された値です。以下はnext [i](i> = 2)の解法です。
- まず、ここでkの値を指定する必要があります。kの値はnext [i-1]の値を表します。これは、最初のi-2文字の最長の共通プレフィックスとサフィックスの値です。
- next [i]を解くときは、i-1番目の文字とk番目の文字が同じかどうかを比較する必要があります。
- 同じ場合、next [i] = next [i-1] +1;
- それ以外の場合、k = 0になるまで、k = next [k]。
例:
初期値k = 0; nex [0] = -1、next [1] = 0;
i = 2の場合、p [1]!= p [k](k = 0)はb!=
akを意味します= 0 //つまり、最後に到達し、まだ一致していません。k== 0なので、next [2] = 0とします
。i= 3の場合、p [2]!= p [k] (k = 0)、k = = 0なので、next [3] = 0と
し、最後にi = 4の場合、p [3] == p [k](k = 0)なので、next [4 ] = ++ k;つまり、next [4] = 1;
i = 5の場合、p [4] = p [k](k = 1)なので、next [5] = ++ k;つまりnext [5 ] = 2;
同じGetnext [6] = 3
しかし、私はそれを再び実行した後に初めて知りました。。。この例では、k = next [k]の使用法は使用されていません。次に、添え字5のcがaになるようにP文字列を変更します
。i= 6、k = 2、p [6]!= p [2]、k = next [k]の場合、つまりk = 0
p [0] == p [6]なので、next [6] = ++ k = 1;
Pを2つの部分に分割して、
次の配列を見ることができます。
次はKMPアルゴリズムの部分です
- 2つの文字列s1、s2を入力します(s1はメイン文字列、s2はパターン文字列です)
- s2の次の配列を取得します
- s1の位置とs2の位置をそれぞれ表す2つのポインタi1とi2を定義します
- s1 [i1] == s2 [i2]、i1 ++、i2 ++の場合
- それ以外の場合、i2 = next [i2]
- i2を0に移動すると、つまりnext [i2] ==-1の場合、i1 ++はパターン文字列の最初の文字列と一致し、一致せず、メイン文字列の次の文字列とのみ一致します。
- 最後に、i2がs2の長さに等しいかどうかを判断します。これは、i1-i2を返すのと同じです。そうでない場合は、-1を返します。
たとえば
、P文字列の次の配列が計算され、i1 = i2 = 6の場合、2つの文字列は異なります。このとき、i1は移動せず、i2 = next [6] = 3です。次の図に示すように
、一致は正常に発生し、i1 -i2 = 3を返します。
以下はコードです()
次の配列を探す
void get_next()
{
Next[0]=-1;
Next[1]=0;
int i=2,k=0;//i是模式串的起始位置,即从第三个字符开始匹配,k是i-1个字符要匹配的位置
int len=s2.size();
while(i<len)
{
if(s2[i-1]==s2[k])//如果i-1和k相等,i后移准备匹配下一个位置,k后移
Next[i++]=++k;
else if(k>0)//没有匹配成功,k移动到next[k]的位置
k=Next[k];
else
Next[i++]=0;//移动到头了,next[i]只能为0了
}
}
km²
int kmp()
{
int i1=0,i2=0;
int len1=s1.size();
int len2=s2.size();
get_next();//获得Next数组
while(i1<len1&&i2<len2)//i1没到头,i2也没到头
{
if(s1[i1]==s2[i2])//相等就齐头并进
{
i1++;
i2++;
}
else if(next[i2]==-1)//模式串到头都没有和主串能匹配的字符,主串往后移
i1++;
else
i2=next[i2];//匹配不成功,i2移动
}
return i2==len2?i1-i2:-1;//i2到头证明匹配成功,否则返回-1
}
テンプレートの質問:HDU-1711
ピット:これは文字ではなく数字です。整数配列を使用して
ACコードを受信します
#include<iostream>
#include<cstdio>
#include<string.h>
#include<queue>
#include<cmath>
#include<fstream>
using namespace std;
int Next[200005];
int s1[1000005];
int s2[1000005];
int a,b;
void get_next()
{
Next[0]=-1;
Next[1]=0;
int i=2,k=0;//i是模式串的起始位置,即从第三个字符开始匹配,k是i-1个字符要匹配的位置
int len=b;
while(i<len)
{
if(s2[i-1]==s2[k])//如果i-1和k相等,i后移准备匹配下一个位置,k后移
Next[i++]=++k;
else if(k>0)//没有匹配成功,k移动到next[k]的位置
k=Next[k];
else
Next[i++]=0;//移动到头了,next[i]只能为0了
}
}
int kmp()
{
int i1=0,i2=0;
get_next();
int len1=a;
int len2=b;
while(i2<len2&&i1<len1)//i1没到头,i2也没到头
{
if(s1[i1]==s2[i2])//相等就齐头并进
{
i1++;
i2++;
}
else if(Next[i2]==-1)//模式串到头都没有和主串能匹配的字符,主串往后移
i1++;
else
i2=Next[i2];//匹配不成功,i2移动
}
return i2==len2?i1-i2:-1;//i2到头证明匹配成功,否则返回-1
}
int main(void)
{
int t;
cin>>t;
while(t--)
{
//memset(Next,0,sizeof(Next));
int ans=kmp();
if(ans!=-1)
cout<<ans+1<<endl;
else
cout<<ans<<endl;
}
return 0;
}
Logu P3375
ピットポイント:次の配列には最初のn-1文字の最長のプレフィックスサフィックス値が含まれていないため、最初に役に立たない文字を追加し、出力時に1から次の配列を出力します
。ACコード
#include<iostream>
#include<cstdio>
#include<string.h>
#include<queue>
#include<cmath>
using namespace std;
int next[2000005];
string s1,s2;
void get_next()
{
next[0]=-1;
next[1]=0;
int i=2,k=0;//i是模式串的起始位置,即从第三个字符开始匹配,k是i-1个字符要匹配的位置
int len=s2.size();
while(i<len)
{
if(s2[i-1]==s2[k])//如果i-1和k相等,i后移准备匹配下一个位置,k后移
next[i++]=++k;
else if(k>0)//没有匹配成功,k移动到next[k]的位置
k=next[k];
else
next[i++]=0;//移动到头了,next[i]只能为0了
}
}
int kmp()
{
int i1=0,i2=0;
get_next();
s2=s2.substr(0,s2.size()-1);
int len1=s1.size();
int len2=s2.size();
while(i1<len1)//i1没到头,i2也没到头
{
if(s1[i1]==s2[i2])//相等就齐头并进
{
i1++;
i2++;
}
else if(next[i2]==-1)//模式串到头都没有和主串能匹配的字符,主串往后移
i1++;
else
i2=next[i2];//匹配不成功,i2移动
if(i2==len2)
{
printf("%d\n",i1-i2+1);
i2=next[i2]; //再次匹配
i1--;
}
}
//return i2==len2?i1-i2:-1;//i2到头证明匹配成功,否则返回-1
}
int main(void)
{
cin>>s1>>s2;
s2+="$";
kmp();
next[0]++;
int len=s2.size();
for(int i=1;i<=len;i++)
{
printf("%d ",next[i]);
}
return 0;
}
午後中ずっと書きましたが、終わりました。見たら親指を立ててください、ありがとうございます