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

來源:互聯網
上載者:User

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];//用隊列完成BFSint 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;}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.