1. 程式人生 > >【AC自動機】【字符串】【字典樹】AC自動機 學習筆記

【AC自動機】【字符串】【字典樹】AC自動機 學習筆記

none ring mem ems 如何 top 暴力 繼續 編號

blog:www.wjyyy.top

AC自動機是一種毒瘤的方便的多模式串匹配算法。基於字典樹,用到了類似KMP的思維。

AC自動機與KMP不同的是,AC自動機可以同時匹配多個模式串,而復雜度不會達到太高。如果用KMP多次匹配字符串,復雜度就是\(O(k(n+m))\)。

我們知道,如果讓一個字符串頭對頭或者完全匹配其他字符串,用字典樹來匹配是最為方便的。但是如果匹配過程中發現當前節點沒有目標兒子,就發生了失配。在KMP字符串匹配中,失配可以跳到給當前位置預處理出的nxt,繼續匹配。

而AC自動機在字典樹上,我們如何找出每個節點失配位置呢?我們知道,像KMP一樣,失配位置是唯一確定的。而在字典樹上,一條路徑唯一對應了一個子串,因此也是唯一確定的。

KMP中的nxt數組是由變量j承接了前一個位置的nxt,我們考慮在AC自動機中也讓失配指針從父節點轉移過來。那麽如此一來,當前節點(設為‘c‘)的失配指針就會從當前父節點的失配指針一直沿失配指針遞歸,找到第一個有以‘c‘字符為兒子的節點,把當前節點的失配指針連接到這個節點的‘c‘兒子上。如此做下去,會發現有了失配指針的樹變成了一個圖。但是如果上面的回溯過程找到根了還沒有找到怎麽辦?

正常情況下,字典樹的根是不帶任何字符的,也就是說它是一個空節點,也是重新匹配的開始。如果我們一開始匹配就出現了失誤,也就是根節點都沒有這個兒子,我們當然要留在自己位置上繼續做,因此根節點的失配指針指向自己(同時防止越界)。同理,根節點的兒子們失配指針指向根節點,因為在這裏失配了,接下來只有兩種情況:一是根節點也沒有這個兒子,於是回歸到根節點的一般情況;二是根節點有這個兒子,根節點有這個兒子我們就通過當前節點的失配指針先走到根節點,再走到這個兒子去。

於是,我們的Trie樹變成了Trie圖。

技術分享圖片

可以根據上面的前兩幅圖更清楚的了解AC自動機,其中紅色邊是fail邊。我們可以發現一個有趣的事情,fail指針可以構成一棵有向樹,註意到每條單獨的鏈都沒有分支,而且一條鏈上的字母總是一致的,因此可能在以後的題目或者優化中出現。(就像KMP的nxt一樣)

實際上在構建AC自動機時,我們的失配指針並不這樣建。為了減小常數(也許是這個原因),我們認為當前節點如果沒有兒子‘c‘,就把當前節點代表‘c‘兒子的指針連向當前節點失配指針的‘c‘兒子。因為一個點的失配指針指向的節點總是比這個點淺,所以我們用BFS來做,深度較淺的點總比深度深的點先被訪問,也因此,當前節點的失配指針的‘c‘兒子一定有位置,即使不是它真正的兒子,也一定是它通過失配指針索引得到的。在最壞的情況下,如果失配指針回溯的過程中怎麽也找不到這樣的兒子,自然而然當前節點的‘c‘兒子就連向根了。

與字典樹類似,AC自動機成功匹配就是找到了一個單詞的結尾,我們在構建字典樹時就應該把每個模式串的結尾做上標記。但是如果兩個模式串有包含關系怎麽辦?有兩種方法可以完成,一是訪問到每個節點時暴力跳fail指針,直到遞歸到根,對答案的貢獻就是這條路徑的標記數;二是構建fail樹,跳就是沿著fail樹在跳,只需要預處理出fail樹上每個節點到根路徑上標記的數目(前綴和),就可以在當前節點記錄答案。看上去第二種方法復雜度更優,但是它有局限性。也就是當確切地統計每個模式串出現的次數時,這種直接用fail樹統計出現次數和的方法不能適用。

Code of luoguP3796:

這個題要註意重復的模式串統計問題

#include<cstdio>
#include<cstring>
#include<vector>
using std::vector;
vector<int> same[155];//與某一個模式串相同的模式串編號
struct node
{
    int End,num;//num表示相同模式串個數,End表示是否為結束位置
    node *ch[26];
    node *fail;
    node()
    {
        memset(ch,0,sizeof(ch));
        fail=NULL;
        End=0;
        num=0;
    }
    void build(char *c,int i)//構建字典樹
    {
        if(*c==‘\0‘)
        {
            End=1;
            if(!num)
                num=i;
            same[num].push_back(i);//如果發現這裏已經有單詞結束了,那麽一定是重復的,直接向原來的後面加編號就好了
            return;
        }
        if(!ch[*c-‘a‘])
            ch[*c-‘a‘]=new node();
        ch[*c-‘a‘]->build(c+1,i);
    }
}*root=new node();
char t[200][200];
node *q[1000011];//用隊列完成BFS
int l=0,r=0;
void Fail()//構建fail指針
{
    root->fail=root;//沒有這句話貌似也可以,為了保險起見,防止越界
    for(int i=0;i<26;++i)//根節點的兒子失配指針都指向自己
        if(!root->ch[i])//沒有這個兒子就指向失配指針的這個兒子,而失配指針是自己,為了不紊亂和方便,這個兒子就指向自己
            root->ch[i]=root;
        else
        {
            root->ch[i]->fail=root;//設置失配指針
            q[++r]=root->ch[i];
        }
    while(l<r)
    {
        node *p=q[++l];
        for(int i=0;i<26;++i)
            if(p->ch[i])
            {
                p->ch[i]->fail=p->fail->ch[i];//有這個兒子就設置失配指針到自己的失配指針,自己的失配指針指向的地方一定已經完成工作了
                q[++r]=p->ch[i];
            }
            else
                p->ch[i]=p->fail->ch[i];
    }
    return;
}
char s[1000010];
int cnt[155];
void match()
{
    int ans=0;
    scanf("%s",s);
    node *now=root;
    for(int i=0;s[i]!=‘\0‘;++i)//開始匹配
    {
        now=now->ch[s[i]-‘a‘];
        cnt[now->num]+=now->End;
        node *p=now;
        while(p!=root)//暴力跳fail
        {
            p=p->fail;
            cnt[p->num]+=p->End;
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    while(n)
    {
        root=new node();
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;++i)
        {
            scanf("%s",t[i]);
            root->build(t[i],i);
        }
        Fail();
        match();
        int mx=0;
        for(int i=1;i<=n;++i)
        {
            for(vector<int>::iterator it=same[i].begin();it!=same[i].end();++it)//處理相同模式串
                cnt[*it]=cnt[i];
            if(mx<cnt[i])
            {
                cnt[0]=1;
                mx=cnt[i];
            }
            else if(mx==cnt[i])
                ++cnt[0];
        }
        printf("%d\n",mx);
        for(int i=1;i<=n;++i)
            if(cnt[i]==mx)
                printf("%s\n",t[i]);
        scanf("%d",&n);
    }
    return 0;
}

【AC自動機】【字符串】【字典樹】AC自動機 學習筆記