C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++のZアルゴリズムとは?線形時間でパターン検索を行う手法を実装付きで解説

Zアルゴリズムは、文字列の中に特定のパターンが出現する位置を線形時間で検索するためのアルゴリズムです。文字列の長さを n、検索したいパターンのサイズを m とすると、計算にかかる時間は O(m+n) のオーダーに収まります。

Zアルゴリズムでは、「Z配列」と呼ばれるデータ構造を利用してパターンの出現位置を効率よく見つけます。

Z配列とは

Z配列は、元の文字列と同じ長さを持つ配列です。各要素には、文字列の i 番目の位置から始まる部分文字列のうち、文字列そのものの接頭辞(プレフィックス)と一致する最長の長さが格納されます。

例えば、文字列 "aabxaabxcaabxaabxay" の場合、各位置における「先頭からの一致長」を記録することで、後続の照合処理を大幅に高速化できます。

アルゴリズムの手順

このアルゴリズムでは、長さ n の文字列 S と、検索対象となる長さ m のパターン p が与えられます。

まず Z 配列を作成します。その後、i = 1 から n-1 まで文字列の各文字に対してループ処理を行い、1 ≤ L ≤ i ≤ R を満たすような接頭辞部分文字列 s[L-R] を管理していきます。

i-1 までの有効な区間 [L, R] とそこまでの Z 値をもとに、以下の手順で z[i] および新しい区間 [L, R] を計算します。

ステップ1:i > R の場合
 それ以上大きな接頭辞部分文字列は存在しません。
 そこで、S[0](先頭から始まる文字列)と S[i](インデックス i から始まる部分文字列)を比較して新しい区間を求め、
 z[i] = R - L + 1 として z[i] を計算します。

ステップ2:i ≤ R の場合
 区間 [L, R] は i まで拡張できます。k = i - L のとき、z[i] ≥ min(Z[k], R-i+1) が成り立ちます。
 ・ステップ2.1:Z[k] < R-i+1 の場合 → より長い接頭辞部分文字列 s[i] は存在しません。
 ・ステップ2.2:Z[k] ≥ R-i+1 の場合 → より長い部分文字列が存在する可能性があります。
  このとき L = i と更新し、S[R+1] 以降を照合しながら R を伸ばして [L, R] を更新します。

このプロセスにより、たった一回のループ処理ですべての Z 値を求めることができます。これが O(n) の線形時間を実現する鍵となっています。

パターン検索への応用

実際のパターン検索では、「パターン + 区切り文字($など)+ テキスト」という新しい文字列を作成し、その Z 配列を計算します。Z 値がパターンの長さと一致した位置が、テキスト内でのパターンの出現位置に対応します。

実装例

以下は、ZアルゴリズムをC++で実装したプログラムです。

#include<iostream>
using namespace std;
void createZarray(string str, int Z[]){
   int n = str.length();
   int L, R, k;
   L = R = 0;
   for (int i = 1; i < n; ++i){
      if (i > R){
         L = R = i;
         while (R<n && str[R-L] == str[R])
         R++;
         Z[i] = R-L;
         R--;
      } else {
         k = i-L;
         if (Z[k] < R-i+1)
            Z[i] = Z[k];
         else {
            L = i;
            while (R<n && str[R-L] == str[R])
               R++;
            Z[i] = R-L;
            R--;
         }
      }
   }
}
void zAlgorithm(string text, string pattern){
   string str = pattern+"$"+text;
   int len = str.length();
   int Z[len];
   createZarray(str, Z);
   for (int i = 0; i < len; ++i){
      if (Z[i] == pattern.length())
         cout<<(i-pattern.length()-1)<<"\t";
   }
}
int main(){
   string str = "Hello! Welcome To tutorials Point programming tutorial";
   string pattern = "tutorial";
   cout<<"The patter ' "<<pattern<<" ' is found in the string '"<<str<<" ' at index \t";
   zAlgorithm(str, pattern);
   return 0;
}

実行結果

The patter ' tutorial ' is found in the string 'Hello! Welcome To tutorials Point programming tutorial ' at index 18 46

この実行結果から、パターン "tutorial" はテキスト内のインデックス 18 と 46 の2箇所に出現していることがわかります。Zアルゴリズムを使えば、このように複数の出現位置も効率よく検出できます。


  1. C/C++で実装するバークレーアルゴリズム――分散システムの時刻同期を徹底解説

    バークレーアルゴリズムとは バークレーアルゴリズム(Berkeleys Algorithm)は、分散システムにおいて各ノードの時計を同期させるために用いられるアルゴリズムです。特に、以下のような状況にあるシステムで有効とされています。 マシンに正確な時刻源が存在しない場合 ネットワークやマシンにUTCサーバーが用意されていない場合 分散システムとは、物理的に離れた場所に配置された複数のノードが、ネットワークを介して相互に接続されたシステムのことを指します。各ノードの時計は独立して動作しているため、誤差が生じやすく、何らかの同期機構が必要になります。 バークレーアルゴリズムの仕組み このア

  2. C++で実装する有限オートマトン(Finite Automata)によるパターン検索アルゴリズム

    本記事では、文字列の中から特定のパターンを効率的に探し出す「有限オートマトン(Finite Automata)」アルゴリズムを、C++で実装する方法を解説します。 長さ n のテキスト text[0...n-1] と、長さ m のパターン pattern[0...m-1] が与えられたとき、テキスト内にパターンが出現するすべての位置(インデックス)を見つけるのが目的です。 有限オートマトン法の基本的な考え方 このアルゴリズムでは、まず前処理としてパターンから「状態遷移表」を作成します。これは、現在の状態と読み込んだ文字に応じて、次にどの状態へ遷移するかを表す2次元配列です。 遷移表さえ完成し