文字列学習(KMPアルゴリズム)

文字列の質問の場合、私はテンプレートの使用方法しか知らず、テンプレートの質問なしでボードを変更する方法もわかりません。
アルゴリズム自体の意味を理解していなかったに違いないので、過去数日間、各アルゴリズム自体とそのより一般的な拡張機能のいくつかを詳細に調査しました。将来的には問題解決への考え方を広げていきたいと思います。

KMPアルゴリズムの詳細な説明

人間の目で最適化された文字列照合

文字列内の位置ポインタiとjを使用して説明します。最初の位置インデックスは0から始まり、0番目の位置と呼びます。人工的に検索した場合、失敗した位置(i = 3)に一致するメイン文字列が最初のA以外にAがないため、最初の場所に戻ることは絶対にありません。なぜメインが存在するのかを知ることができます。文字列の前に?最初の3文字が一致していることはすでにわかっているからです。(これは非常に重要です)。過去の移動は間違いなく一致しません!以下に示すように、私は移動できません。jを移動するだけでよいという考えがあります。

ここに画像の説明を挿入します
大きな牛は「ブルートフォースクラッキング」の非効率的な方法に耐えられなかったので、3頭はKMPアルゴリズムを開発しました。考え方は上で見たものと同じです。「部分的に一致した有効な情報を使用して、iポインターがバックトラックしないようにし、jポインターを変更することで、パターン文字列を可能な限り有効な位置に移動します。」

したがって、KMP全体の重要なポイントは、特定の文字がメインの文字列と一致しない場合、jポインタをどこに移動するかを知っておく必要があるということです。

次に、jの運動法則を自分で調べてみましょう。

ここに画像の説明を挿入します

図に示すように、CとDが一致しない場合、jをどこに移動しますか?明らかにそもそも。どうして?同じ前にAがあるから。

ここに画像の説明を挿入します

同じ状況を次の図に示します。

ここに画像の説明を挿入します

前の2つの文字が同じであるため、jポインターを2番目の位置に移動できます。

ここに画像の説明を挿入します

この時点で、おそらく、一致が失敗すると、次の位置kがjだけ移動するという手がかりを見ることができます。このようなプロパティがあります。最初のk文字は、jの前の最後のk文字と同じです。

数式を使ってこのように表現すると

P [0〜k-1] == P [jk〜j-1]

これは非常に重要です。覚えにくい場合は、次の図から理解できます。

ここに画像の説明を挿入します
これを理解すると、jを位置kに直接移動できる理由を理解できるはずです。

理由:

T [i]!= P [j]の場合

T [ij〜i-1] == P [0〜j-1]があります

P [0〜k-1]から== P [jk〜j-1]

必然的に:T [ik〜i-1] == P [0〜k-1]

KMPアルゴリズムの性質

KMPアルゴリズムは、一致する部分文字列のこのプロパティを使用して、一致速度を向上させます。他の多くのバージョンの説明の性質は、次のように説明することもできます。部分文字列の接頭辞と接尾辞が長さkの最長の部分文字列を繰り返す場合、次に一致する部分文字列jをk番目のビットに移動できます(0番目の位置の部分文字は0です)。この解釈を、繰り返される最大の部分文字列解釈として定義します。

「aba」では、接頭辞セットは最後の文字「a」を削除した後の部分文字列{a、ab}のセットであり、接尾辞セットは最初の文字aを削除した後の部分文字列{a、ba}のセットです。 2つの中で最も長く繰り返される部分文字列はa、k = 1です。

コンピューターに分解されるステップは次のとおりです。

1)接頭辞preを見つけて、pre [0〜m]に設定します。

2)接尾辞postを見つけて、post [0〜n]として設定します。

3)接頭辞preから、最初に最大長s [0〜m]を部分文字列として使用します。つまり、kの初期値をmに設定し、post [n-m + 1〜n]と比較します。

それらが同じである場合、pre [0〜m]は繰り返される最大の部分文字列であり、長さはmであ​​るため、k = mです。

それらが同じでない場合、k = k-1;縮小されたプレフィックスのサブストリングの1文字がテールに従ってサフィックスのサブストリングと整列され、それらが同じであるかどうかを確認するために比較が実行されます。

これは、重複する部分文字列が見つかるか、kが見つからなくなるまで続きます。

次の配列を探す

さて、次のステップはポイントです。この(これらの)kをどのように見つけるのですか?Pの各位置で不一致が発生する可能性があるため、つまり、各位置jに対応するkを計算する必要があるため、次に保存する配列next [j] = kを使用します。これは、T [i]の場合を意味します。 != P [j]の場合、jポインタの次の位置。
別の非常に便利で同一の定義。添え字は0から始まるため、kの値は、実際には、jビットの前の部分文字列の最大繰り返し部分文字列の長さです。次の配列の定義を常に念頭に置いてください。以下の説明は、この定義に基づいています。

コード例1

void Getnext(int next[],String t) {
    
    
   int j=0,k=-1;
   next[0]=-1;
   while(j<t.length-1) {
    
    
      if(k == -1 || t[j] == t[k]) next[++j] = ++k;
      else k = next[k];
   }
}

最初に最初のものを見てみましょう。jが0の場合、この時点で一致するものがない場合はどうなりますか?

ここに画像の説明を挿入します

上の写真の場合、jはすでに左端にあり、移動できません。このとき、iポインタは後方に移動するはずです。したがって、next [0] = -1があります。このコードの初期化です。

jが1の場合はどうなりますか?
明らかに、jポインタは0の位置に戻す必要があります。目の前にこの場所しかないので~~~

ここに画像の説明を挿入します

以下が最も重要です。下の画像を参照してください。

ここに画像の説明を挿入します
ここに画像の説明を挿入します

これら2つの図を注意深く比較してください。

ルールが見つかりました:

P [k] == P [j]の場合、

有次[j + 1] ==次[j] + 1

P [k]!= P [j]の場合はどうなりますか?たとえば、次の図に示すように:

ここに画像の説明を挿入します

この場合、コードを見ると、次の文になっているはずです。k= next [k];なぜこのようになっているのですか?以下を見て理解してください。

ここに画像の説明を挿入します

これで、k = next [k]である理由がわかります。上記の例のように、最長のサフィックス文字列[A、B、A、B]は見つかりませんが、[A、B]や[B]などのプレフィックス文字列は引き続き見つかります。したがって、このプロセスは、Cがメインの文字列と異なる場合(つまり、kの位置が異なる場合)、文字列[A、B、A、C]を配置するようには見えません。もちろん、ポインタはnext [に移動します。 k]。。

メモリーポイント

1)kの値は、j​​ビットの前の部分文字列の最大繰り返し部分文字列の長さです。

2)次の配列が保存され、各位置jはkに対応します

次の配列解決アルゴリズムの最適化

最後に、上記のアルゴリズムの欠陥を見てください。最初の例を見てください:

ここに画像の説明を挿入します

明らかに、上記のアルゴリズムから次の配列を取得するときは、[-1、0、0、1]である必要があります。

したがって、次のステップは、jを最初の要素に移動することです。

ここに画像の説明を挿入します

このステップが完全に無意味であることを見つけるのは難しいことではありません。後者のBはもう一致しないため、前者のBも一致してはなりません。同じ状況が実際には2番目の要素Aでも発生します。

明らかに、問題の理由はP [j] == P [next [j]]です。

変更されたコード例

void Getnext(int next[],String t)
{
    
    
   int j=0,k=-1;
   next[0]=-1;
   while(j<t.length-1) {
    
    
      if(k == -1 || t[j] == t[k]) {
    
    
         if(t[++j]==t[++k])//当两个字符相同时,就跳过
            next[j] = next[k];
         else
            next[j] = k;
      }
      else k = next[k];
   }
}

KMPアルゴリズム

int KMP(String s,String t)
{
    
    
   int next[MaxSize],i=0;j=0;
   Getnext(t,next);
   while(i<s.length&&j<t.length) {
    
    
      if(j==-1 || s[i]==t[j]) {
    
    
         i++;
         j++;
      }
      else j=next[j];               //j回退
   }
   if(j>=t.length)
       return (i-t.length);         //匹配成功,返回子串的位置
   else
      return (-1);                  //没找到
}

参照ブログ投稿:https//www.cnblogs.com/dusf/p/kmp.html
https://blog.csdn.net/dark_cy/article/details/88698736

おすすめ

転載: blog.csdn.net/Bluesky_lt/article/details/113386079