[アルゴリズム] AC自動機/ ACアルゴリズム - 速い複数文字列マッチング


ACオートマトン

容認されました

アホ - Corasick


プロパティ

オートマトンAC / ACアルゴリズム(アホ-Corasickオートマトン)、周知のマルチパターンマッチングアルゴリズムです。


事前知識

  1. トライ(重要
  2. KMPアルゴリズム(次は、配列の役割を理解します)

アルゴリズムの複雑性分析の典型的な例

典型的な例である:メインストリングSが与えられると、所定のパターンのT、Q番号がメインストリングのストリングSに存在する所定のパターン列の複数

KMPアルゴリズムでは、メインストリング長の長さのnはMのパターン文字列の複雑さはO(N + M)

直接にKMPアルゴリズムは、このような質問をコピーする場合、パターン文字列マッチング処理の必要性をもう一度

パターン文字列tが存在する場合、複雑性O((N + M)T )

大型メイン文字列の長さ、文字列またはパターンは、多くのことを与えられた場合には、アルゴリズムの複雑さは、高KMPなります場合でも、

だから、AC自動することができ、機械生まれのO(N + MT)を複雑に答えを得るために、

メインストリング上トライ、O(n)を確立するのに費やされた前記O(MT)は横断費やさ

だから、その時の複雑さは、小さな範囲内に制御することができます

(スペース・トライの複雑さの欠点を継承......)


フェイルポインタについて

AC最大の特徴は、自動機械の添加は、各ノードのポインタが失敗と呼ばれます

このポインタの役割とKMPアルゴリズムの役割は、アレイの横に非常に類似しています

KMPアルゴリズムは、位置合わせパターンマッチングjに応じて次の位置として次に[J]を参照するだけです

ACは、各ノードのオートマトンであり、失敗整合されるべき次の位置へのポインタを有しています

オートマトンは、メインストリングの照合を完了するために、O(n)とすることができ、このためです


設定を失敗するポインタ:

  ノードへのトライノードでの電流マッチングは、i番目のメインストリングの位置に対応する場合、メインの文字列が示すノードに存在しない子ノードのI + 1位に見出さミスマッチノードNDの発生を

  この時点で、我々は見つけるとする必要がある文字列の接尾辞で構成されるこのケースでは、ルートノードにルートノードから同じ最長のパターン文字列のプレフィックス

  ノードにノードポインタポイントに失敗することに対応し、最長プレフィックスの最後の文字でなければなりません

特殊な状況を除き、見つけることができ、ノードは、文字と表し、その失敗したノードポインタによって表される文字を同じれます


なぜを探してその接尾最長プレフィックスと同じです -

  これは、その後、現在の処理は、概略的なサフィックス文字列の接尾辞で、接頭文字列が別のパターンの接頭辞で、KMPアルゴリズムの本質です。のみすべての答えを見つけることであることを保証するために、最長プレフィックスの位置に移動させます。可能な限り、あるビューのKMPアルゴリズムポイントの観点から、小振幅一部が落下しない正しい答えを確保するために、パターン文字列の右位置にシフト。

  例としてabcabcabcabcメインストリング、abcabcabにパターン文字列で、2と一致する(すなわち回答)に対応する位置:

A B C A B C A B C A B C
A B C A B C A B
A B C A B C A B C A B C
A B C A B C A B

パターン文字列、2種類があり、同じ接尾辞の接頭辞の場合:ababcab

この時点で選択されるべきである長い、運動の振幅は、表1、表2の上部に移動させる8-5 = 3であります

短い接頭辞と接尾辞は= 6 8-2シフトレンジのために選択した場合、それはなります

A B C A B C A B C A B C
A B C A B C A B

境界を越えたパターン文字列ので、マッチの端部は、次いで、専用表1の場合に結果を記録します

これは、次に表すKMPアレイであるプレフィクスの最長の長さと同じサフィックス理由

ACオートマトンは、それを探している接尾辞のその最長のプレフィックスと同様の理由であります


詳細は以下を参照し、「プロセスの成果」を確立するプロセスのために




下記について

以下のコードではHDU 2222キーワード検索のケース

タイトルは、元の質問を意味します。

Tの数を考慮すると、Tは与えるために、サンプルグループを表す各数N(N <= 10000)、Nワードのパターン文字列として表現さ

次のNワード線は50以上のワード長

メインの文字列の最後の行は、文字列の長さは、100万人以上の主要ではありません

各サンプル、メインストリングの出力の存在のために数文字列パターン

(与えられた単語はそう、繰り返しがあるかもしれない文字列の異なるモードとしての言葉を繰り返し、マスター列が再び表示された場合は、当然のことながら、繰り返しこの言葉をメイン列に表示された単語の同じ種類を加えた回数場合は答えます。より1時間よりも、その後の答えはカウントされません)

(それは文字列パターンが行われているされたような答えが再び一人でKMPとなります)




ポインタのセットアップ手順(スキーム)の失敗

今、次の4つのがある場合の言葉は

abs

abi

wasabi

binary

確立トライは、以下に示します

A

あなたはポインタを失敗構築を開始する必要がツリーの建設を完了

まず、特別な事情を考慮してください。

  所定 - (後の反復の終了として識別される)NULLにルートポインタポイントフェイル

  ルートノードへのポインタのすべての存在(単に文字ではなく、接頭辞と接尾辞のいずれかと同じ)にルートノード点のすべての子ノードをフェイル

そして、ケース後の反復を考慮してください。

  今処理ノードに、そのノードを想定

  この時点で同じサフィックスプレフィックス長が同じ任意のサフィックス、対応する接頭辞のない存在がないことを示す、ゼロである場合、ノードポインタが失敗ルートを指している必要があり

  この時点で同じ文字プレフィックスの唯一のノードを表すの存在を示し、同じ長サフィックス1に等しい場合、失敗するノードポインタは、サブルートノードに隣接しなければならない親のルートノードに今点をノードポインタフェイル

  同じプレフィックス長は1サフィックスケースよりも大きい場合に限り、ノードポインタの親ノードとして、代表ノードの文字がCであることを確認するように指示されていなければならない場合にポイントに失敗し、そのノードが失敗に直接指し示すことができる親ノードのノードをフェイル子ノードは、ノードCが指す(存在する場合)。子ノードcは(このノードが存在しない説明)NULLである場合、そのノードは、反復が見つけて、それを続けるべき失敗パスは子供cを持つノード上で失敗があるかどうか最後の反復ポインタルートが失敗した場合、それはNULL、反復の直接終わりであるが、手動でノードを変更するには、この時間は、残りのNULLではなくポイントに失敗のルートであります


反復ことを確実にするために、

これは、ノードのより大きな深さは失敗し、あなた自身を構築するためにノードの坪ポインタ小さな深さを失敗する可能性があり、

そして深さは、すべてのノードに見いだすことができる以下元のノード(ルートが近づく方向を意味するよりも)よりも深さへのポインタをフェイル

我々は幅優先探索が失敗構築するために使用できるように、ルートから普及し始めました


最初のルートノードと第1層の全ての考慮して、以下の構築物は、失敗します

B

場合は、各ノードのBFSの子、または子ノードに対応する文字が存在するか否かを再びからzのフル検索するための辞書式ます

手動で処理されたすべてのノードの最初の層の後に、第一の層のすべてのノードは、キューに押し込まれます

このルートB root-> A->ルック

「」が見つかりノードが文字「B」に対応した子ノードである、それが処理されているの我々のサイクルの子ノード

「B」は、親ノード「A」、およびルート失敗する「」のポインタであります

我々はルートに持っているので、親ノードbとして子ノードの「b」は、対応する文字が存在しているかどうかを確認するためにルートを観察しないために

明らかに「バイナリ」の「バイナリ」最初の「B」の失敗B点とすることができる第一の経路「B」、それが存在する場合、root-> A->「B」であります

C

完成した建物は失敗し、「B」プッシュキューを入れ、「」の次の処理、すなわちroot-> W-> A続けた後、

キューに要素までは、図ポインタの全体説明が建設各地で失敗します

D




マッチング機能を実現

ツリーの建設が完了した後、マスター文字列パターン文字列に一致しないようになりました

我々は、各単語終了位置対応するフラグのノードにマークプラス1(初期フラグ0)させ

各ノードは、フラグ値の以上であります

E

ルートから始まるツリー全体としてのパターン文字列にマッチし、最初の文字からの主な列

「wasabinaryabi」​​の例として、メインの文字列で

ルート移動から「W」ツリーへ、次いでポインタ「W」の文字を表すノードに見つかったルートノードに対応する「W」第メインストリング文字を指し

6番目の文字「I」もラインダウンまでは、回答フラグは繰り返し回数を防止するために設定された後に追加、「私は」ノードフラグが、この言葉に合わせて、メインの文字列1であることがわかっ0

「I」ノードが子ノードを有していないため、ノードが処理され、次の「I」ノードは、示されるように、失敗する指摘の、すなわち、root-> A-> B->最後のノードI、「I」

単語にフラグ値1、および今回の試合を見つけ、回答フラグが追加した後、0に設定されています

その後、処理は、「I」のノードの失敗、あまりにも「Y」の最終的に右下隅に中間メインの文字列「inary」プロセスに続いて、0に回答セットの追加後も継続し続け

このとき、「y」は根に直接失敗し、ルートがメイン一致する文字列に戻って直接続けています

最後に、仕上げた後、「ABI」、オリジナルの「I」の位置フラグがそう答えに寄与しない、0に設定されています

一次文字列マッチング、出力回答3の後、マッチング処理を指しwasabiabibinary三つの言葉

注、そのたびにノードが一致し、失敗し、このノードからのパスを歩いて開始する必要がある、すべての答えに参加するには、パス上の旗、次のような状況が存在することになります

図は、2つの単語が含まれていますabcdeし、bcdこれは、包括的でない単語が完全に別の単語の中に含まれている特殊なケースであり、

F

主な文字列が「ABCDE」であると仮定すると、再度たびにノードへのパスポイントの場合に失敗したまま、最後の試合は、唯一の出会い「E」、このノード、一つだけ答えを終えました

しかし、明らかに、BCDはまた、「ABCDE」に含まれている、我々は再び失敗するすべてのパスを扱わなければなりません

限り答えに参加するには、このフラグのポイント「BCD」「D」、「BCD」に今回の失敗の「D」ノード、「D」を扱って再びすべての散歩、など

詳細なコードは、参照と一致します




コードの実装

次のように最初に、変数ノードの構造が定義されています

struct node
{
    int flag;
    node *next[26],*fail;
};

node *root;

char str[1000050];

現在のノード内の記録フラグが終わる単語の数であります

フラグと同じツリーの通常の辞書の意味と次のアレイ


た。AddNodeの成果

void addNode()
{
    node *nd=root;
    int i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        if(nd->next[id]==NULL)
        {
            nd->next[id]=new node;
            nd->next[id]->flag=0;
            for(int j=0;j<26;j++)
                nd->next[id]->next[j]=NULL;
        }
        nd=nd->next[id];
    }
    nd->flag++;
}

  この時点でトライ同様の貢献は、ポインタが失敗する初期化されないことがあり


建設フェイルポインタbuildFailPointer

void buildFailPointer()
{
    queue<node*> q; //bfs容器
    root->fail=NULL; //根节点fail置空
    for(int i=0;i<26;i++)
    {
        if(root->next[i]!=NULL) //第一层所有出现过的节点的fail全部指向root,并加入队列准备搜索
        {
            root->next[i]->fail=root;
            q.push(root->next[i]);
        }
    }
    while(!q.empty())
    {
        node *nd=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(nd->next[i]!=NULL) //如果这个子节点存在
            {
                node *tmp=nd->fail; //tmp储存当前处理的nd->next[i]的父节点的fail指针
                while(tmp!=NULL) //重复迭代
                {
                    if(tmp->next[i]!=NULL) //直到出现某次迭代的节点存在一个子节点,代表的字符与当前处理的nd->next[i]代表的字符相同时,停止迭代
                    {
                        nd->next[i]->fail=tmp->next[i]; //那么当前处理的节点的fail就可以指向迭代到的这个节点的对应子节点
                        break;
                    }
                    tmp=tmp->fail; //如果上述子节点不存在,继续迭代fail指针
                }
                if(tmp==NULL) //如果最后tmp指向NULL,说明最后一次迭代到了root节点且没有找到答案,说明不存在任何前缀与当前的后缀相同,此时让fail指向root节点即可
                    nd->next[i]->fail=root;
                q.push(nd->next[i]); //推入队列
            }
        }
    }
}

  ノード検索処理は、ND->次の[I]は、NDはND->次の[I]は、親ノードであるため


メインの文字列マッチングの木、クエリが尋ねた単語の種類数

int query()
{
    node *nd=root,*tmp;
    int ans=0,i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        while(nd->next[id]==NULL&&nd!=root) //如果nd没有字符为id的子节点的话,说明在这里失配,需要迭代指向fail,如果遇到根节点的话则无法继续迭代直接退出
            nd=nd->fail;
        if(nd->next[id]!=NULL) //针对于nd为根节点的情况,只有存在字符为id的子节点才改变nd的指向,否则nd继续保持指向根节点
            nd=nd->next[id];
        tmp=nd; //从nd开始走一遍fail路径,把所有完全包含于当前字符串的单词情况都考虑进来
        while(tmp!=root)
        {
            if(tmp->flag!=0)
            {
                ans+=tmp->flag;
                tmp->flag=0; //一定要置0
            }
            else
                break;
            tmp=tmp->fail;
        }
    }
    return ans;
}

  ND現在指さトライノード、すなわち、KMPアルゴリズムカーソルパターン文字列jの

  歩行パスが失敗した場合、あなたがノードフラグが0で発生した場合、それが渡された(または単語の終わりではありません)このパスの前にされてきた、あなたはこの道に沿って継続する必要があり、時間は保存しないでください




完全なコードHDU-2222

#include<bits/stdc++.h>
using namespace std;

struct node
{
    int flag;
    node *next[26],*fail;
};

node *root;

char str[1000050];

void addNode()
{
    node *nd=root;
    int i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        if(nd->next[id]==NULL)
        {
            nd->next[id]=new node;
            nd->next[id]->flag=0;
            for(int j=0;j<26;j++)
                nd->next[id]->next[j]=NULL;
        }
        nd=nd->next[id];
    }
    nd->flag++;
}

void buildFailPointer()
{
    queue<node*> q;
    root->fail=NULL;
    for(int i=0;i<26;i++)
    {
        if(root->next[i]!=NULL)
        {
            root->next[i]->fail=root;
            q.push(root->next[i]);
        }
    }
    while(!q.empty())
    {
        node *nd=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            if(nd->next[i]!=NULL)
            {
                node *tmp=nd->fail;
                while(tmp!=NULL)
                {
                    if(tmp->next[i]!=NULL)
                    {
                        nd->next[i]->fail=tmp->next[i];
                        break;
                    }
                    tmp=tmp->fail;
                }
                if(tmp==NULL)
                    nd->next[i]->fail=root;
                q.push(nd->next[i]);
            }
        }
    }
}

int query()
{
    node *nd=root,*tmp;
    int ans=0,i,id;
    for(i=0;str[i]!='\0';i++)
    {
        id=str[i]-'a';
        while(nd->next[id]==NULL&&nd!=root)
            nd=nd->fail;
        if(nd->next[id]!=NULL)
            nd=nd->next[id];
        tmp=nd;
        while(tmp!=root)
        {
            if(tmp->flag!=0)
            {
                ans+=tmp->flag;
                tmp->flag=0;
            }
            else
                break;
            tmp=tmp->fail;
        }
    }
    return ans;
}

void solve()
{
    root=new node;
    root->flag=0;
    root->fail=NULL;
    for(int i=0;i<26;i++)
        root->next[i]=NULL;
    int n;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",str);
        addNode();
    }
    buildFailPointer();
    scanf("%s",str);
    printf("%d\n",query());
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
        solve();

    return 0;
}

おすすめ

転載: www.cnblogs.com/stelayuri/p/12578889.html