KMP アルゴリズムは文字列マッチング アルゴリズムです. 一般的な意味は、最適化された方法を使用して、特定の文字列 A 内の文字列 B の位置をすばやく見つけることです. 従来のマッチング アルゴリズムと比較して、マッチング時間を効果的に短縮し、効率を向上させることができます. .
プレフィックスとサフィックス
KMP アルゴリズムを見る前に、質問を考えてみましょう: 特定の文字列 p が文字列 s に含まれているかどうかを取得したい場合。たとえば、文字列 s が「abcdabcdf」で文字列 p が「dab」の場合、以前の理解によれば、文字列 s の最初の文字から開始して、文字列 p の各文字を項目ごとに比較できます。不一致がある場合は、文字列 s の次の要素から始めて、文字列 p の各要素と項目ごとに比較する必要があります。
#include<iostream>
using namespace std;
const int N = 10, M = 4;
int main()
{
char s[N] = "abcdabcdf";
char p[M] = "dab";
for (int i = 0; i < N-1; i++)
{
int flag = 1;
int tmp = i;
for (int j = 0; j < M-1; j++)
{
if (s[tmp++] != p[j])
{
flag = 0;
break;
}
}
if (flag == 1)
{
cout <<"下标为:" << i << endl;
break;
}
}
return 0;
}
KMP文字列アルゴリズムが生まれたのは、まさに上記のソート方法が時間がかかるからです。KMP アルゴリズムは、空間と時間を交換する典型的な文字列マッチング アルゴリズムです。KMP を理解する前に、文字列の等しい接頭辞と接尾辞という 2 つの概念を理解する必要があります。例: 文字列 "dabbcda" があり、プレフィックスは文字列であり、最初の要素から後ろの任意の要素 (末尾を除く) までの文字列を指します。たとえば、前の文字列の接頭辞は (d, da, dab, dabb, dabbc, dabbcd) です; 同様に、その接尾辞は、最後の要素の終わりと前方の要素の開始 (開始要素を除く) を参照します。中間弦。前の文字列のサフィックスは (a、da、cda、bcda、bbcda、abbcda) です。等号の接頭辞\接尾辞は接頭辞と接尾辞が共有する文字列で、前の文字列の等号の接頭辞\接尾辞は文字列daです。
KMP アルゴリズム マッチングの原則
ここで、マッチング プロセスで KMP アルゴリズムを使用したマッチング プロセスを見てみましょう。文字列 p は、文字列 s と完全に一致した後の最初の要素の添字です。
最初に, p の各文字は通常どおり s 文字列の各文字と比較されます. 比較できない場合, p は s 文字列の次の文字の文字列を比較し続けます. 2 つと一致した後、KMP の利点は上記の要素が使用されている場合に明らかになります。
上記を実行する 3 回目までは、文字列 p の最初の 5 つの要素が文字列 s の中間要素と一致しますが、最後の要素は一致しません.このとき、KMP の処理方法は異なります.従来の加工方法。従来の処理方法では、文字列 p の a と文字列 s の b を次回も比較します。
KMP アルゴリズムは処理方法が異なります. s 文字列が部分的な p 文字列のみに一致する場合, s 文字列とは異なる p 文字列の最初の要素の左側にある要素のセットが部分文字列として使用されます.この部分文字列の値は、等しい接頭辞の最初の要素が、その最大の等しい接尾辞の最初の要素と以前に比較された s 文字列内の要素と再整列されます (つまり、部分文字列 p の接頭辞は、接尾辞の位置)。例として、3 回目の一致で p 文字列の最初の 5 文字が一致するが、次の d が一致しない場合、このとき KMP アルゴリズムを使用して文字列 p を d で分割し、左の文字列最大の等しい接頭辞/接尾辞は ab であるため、文字列 p の接頭辞はその接尾辞に逆になります。
また、文字列 p の最初の要素に戻るのではなく、文字列 s の対応する要素と比較するために、プレフィックスの後の最初の要素 m から開始します。そして、この要素 m の位置は、次の配列によって決定できます。次の配列には、p 文字列の各要素に対応する部分文字列の最大の等しいプレフィックス/サフィックスの長さが格納され、比較を開始する要素の位置は p[next[2] (次の配列に格納されている 3 番目の要素) です。 、d 要素はプレフィックス/サフィックスの最大長に対応します)]
次の配列
次の配列は、KMP アルゴリズムのストレージ パターン文字列 (上記の文字列 p) 内の特定の要素で終わる部分文字列の最大の等しいプレフィックス/サフィックスの長さです。例: 文字列 abcdabef. a で終わる部分文字列は a であり、その最大の等しい接頭辞/接尾辞は 0 です。同様に、b で終わる部分文字列の最大の等しい接頭辞/接尾辞は 0 であると計算でき、次の配列が対応します。各ビットには、パターン文字列の対応するビットで終わる文字列の最大の等しいプレフィックス/サフィックスの長さが格納されます。それらの関係を次の表に示します。
終了要素 | 部分文字列 | 等しいプレフィックス/サフィックスの最大長 | 次の配列の格納 |
a | a | 0 | 次[0]=0 |
b | ab | 0 | 次[1]=0 |
c | abc | 0 | 次の[2]=0 |
d | あいうえお | 0 | 次[3]=0 |
a | アブダ | 1 | 次[4]=1 |
b | アブダブ | 2 | 次[5]=2 |
e | abcdabe | 0 | 次[6]=0 |
へ | アブダベフ | 0 | 次[7]=0 |
次の配列の機能を理解したら、次の配列をコードで実装する方法を見てみましょう: まず、全体のコードを見てみましょう:
const int Smax = 100, Pmax = 10;
char s[Smax],p[Pmax];
int s_len, p_len;
int ne[Pmax];
for (int i = 1, j = 0; i < p_len; i++)
{
while (j && p[i] != p[j])
j = ne[j];
if (p[i] == p[j])
j++;
ne[i] = j;
}
ここで、 i は文字列 p の要素添字を表し、文字列 p の各要素に対応する部分文字列の最大の等しい接頭辞/接尾辞の長さを 1 つずつ見つける必要があります。ここでは、for ループを使用して文字列 p の各要素を計算します。文字列の最初の要素の最大の等しいプレフィックス/サフィックスの長さは 0 でなければならないため、添字 1 から開始します。j は、最大の等しいプレフィックス/サフィックスの長さです。for ループの内容を理解するために、シナリオを想定します。現在の文字列 p が添字 i を持つ要素と一致し、取得する現在の要素の最大プレフィックス/サフィックスには、取得した j のベースが必要です。 before は、p[i] と p[j] の要素に一致します。それらが同じ場合、最大のプレフィックス/サフィックスが追加され、j++ に対応します。
If it does not match, then enter the while loop. このループの意味は、以前に見つかった最大のプレフィックス/サフィックス (最大のプレフィックス/サフィックスを徐々に減らすプロセス) に基づいて、より小さい最大のプレフィックス/サフィックスを見つけることです。下の図の部分。
次の配列を理解すると、KMP アルゴリズムを実装するプロセスは次の配列を実装するプロセスと似ていることがわかりますが、パターン文字列のプレフィックスの最後の要素とサフィックスの最後の要素を比較する点が異なります。 s 文字列内の要素と p 文字列内の要素の比較。j を最後の要素と比較すると、比較が成功し、j が p の長さに追加されます。これは成功を意味します。このとき、i の現在位置が出力されます。i の先頭位置は、p の長さを引いた位置に 1 を加えた位置であることに注意してください。
したがって、一致するコードは次のとおりです。
//kmp匹配过程
for (int i = 0, j = 0; i < s_len; i++)
{
while (j && s[i] != p[j])
j = ne[j];
if (s[i] == p[j])
j++;
if (j == p_len)
{
cout << i - p_len+1<<" ";
j = ne[j];
}
合計コード
#include<iostream>
using namespace std;
const int Smax = 100, Pmax = 10;
char s[Smax],p[Pmax];
int s_len, p_len;
int ne[Pmax];
int main()
{
cin >> s_len >> s >> p_len >> p ;
//next数组求法
for (int i = 1, j = 0; i < p_len; i++)
{
while (j && p[i] != p[j])
j = ne[j];
if (p[i] == p[j])
j++;
ne[i] = j;
}
//kmp匹配过程
for (int i = 0, j = 0; i < s_len; i++)
{
while (j && s[i] != p[j])
j = ne[j];
if (s[i] == p[j])
j++;
if (j == p_len)
{
cout << i - p_len + 1 <<" ";
j = ne[j];
}
}
return 0;
}
以上が私のKMPアルゴリズム学習上の注意点ですが、私のKMPアルゴリズムに対する理解が不十分なため、ところどころ記述に問題や誤りがあるかもしれません.
参考文献: