AC自動機-hdu2222

這篇博客我以hdu2222這道模版題爲例詳細的講解一下AC自動機。

AC自動機

簡介
AC自動機(Aho-Corasick automation),該算法在1975年產生于貝爾實驗室,是著名的多模匹配算法之一。
一個常見的例子就是給出n個單詞,再給出一段包含m個字符的文章,讓你找出有多少個單詞在文章裏出現過。
要學AC自動機最好先有KMP和字典樹Trie的基礎,AC自動機實際上就像是在Trie上進行KMP的算法。

字典樹Trie:http://blog.csdn.net/williamsun0122/article/details/71056547

KMP:http://blog.csdn.net/WilliamSun0122/article/details/75576868

思想

我們以5個單詞say,she,shr,he,her和字符串yasherhs爲例,問多少個單詞在字符串中出現過。

運用AC自動機的時候先構造Trie樹再運用KMP的思想即可。這裏的Trie樹有所不同,具體構造如下:

struct node{
    node *next[maxn];
    node *fail;
    int num;
    node()
    {
        for(int i=0;i<26;i++) next[i] = NULL;
        fail = NULL;
        num = 0;
    }
};

可以看出多了fail指針。
這裏的fail指針就類似KMP裏面的next數組,是匹配失敗後的指向。
先把單詞插入到Trie樹中:

void ac_insert()
{
    node *p = root;
    for(int i=0;word[i];i++)
    {
        int id = word[i]-'a';
        if(p->next[id]==NULL) p->next[id] = new node;
        p = p->next[id];
    }
    p->num++;
}

構造Trie樹如圖

然後我們詳細講解一下AC自動機最重要的部分,就是fail失敗指針的求解。我們對着圖看我的講解。

我們是用BFS來構造fail指針,並且運用的是KMP的思想。
最開始先讓root入隊。隊列{root}
第1次循環時處理與root相連的字符即h和s。因爲第一個字符不匹配需要重新匹配,所以h和s的fail指針都指向root(root是Trie入口,沒有實際含義)即圖中(1)和(2)。此時隊列{h,s}
第二次循環時處理與h相連的字符即e。噹噹前字符e失配時應該將其fail指針指向當前字符串(he)的最長後綴和Trie樹中前綴的匹配(就是相當於指向其父節點h的fail指針指向的節點,看其有沒有當前節點e的匹配,有的話就指向它,沒有的話可以一直向上找直到fail指針指向root)。這裏h的fail指針指向root,而root沒有e的匹配,所以把e的fail指針指向root。此時隊列{s,e}
第三次循環時處理與s相連的字符a和h。其中a與第二次循環的e情況相同,將其fail指針指向root。h其父節點s的fail指針指向root,而root有h的匹配,所以把h的fail指針指向與root相連的h節點。
之後情況依此類推。
代碼如下:

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

最後就是AC自動機的多模匹配。匹配過程也與KMP的類似,並不是很難,我直接貼代碼,代碼裏面有一些註釋。

int ac_automation()
{
    int ans=0;
    node *p = root;
    node *tmp;
    for(int i=0;str[i];i++)
    {
        int id = str[i]-'a';
        //噹噹前節點不是root且沒有該字符(id)的匹配就一直想上找
        while(p->next[id]==NULL && p!=root) p = p->fail;
        //找到有id的匹配不然就是root
        if(p->next[id]!=NULL) p = p->next[id];
        tmp = p;
        //找到該匹配後要循環向上計數,不然可能會有遺漏。
        //比如she和he不循環向上找的話就只會加she一個
        while(tmp!=root)
        {
            if(tmp->num>0)
            {
                ans += tmp->num;
                //計數後將其置0,避免重複計數
                tmp->num = 0;
            }
            else tmp = tmp->fail;
        }
    }
    return ans;
}

以上就是我對AC自動機的理解,希望對大家有幫助。

參考博客:http://blog.csdn.net/niushuai666/article/details/7002823

hdu2222

這題就是個模版題,看懂上面的內容之後就可以直接A了。

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

const int maxn = 1e6+5;

int t,n;

char word[55],str[maxn];

struct node{
    node *next[26];
    node *fail;
    int num;
    node()
    {
        for(int i=0;i<26;i++) next[i] = NULL;
        fail = NULL;
        num = 0;
    }
};

node *root;

void init()
{
    root = new node;
}

void ac_insert()
{
    node *p = root;
    for(int i=0;word[i];i++)
    {
        int id = word[i]-'a';
        if(p->next[id]==NULL) p->next[id] = new node;
        p = p->next[id];
    }
    p->num++;
}

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

int ac_automation()
{
    int ans=0;
    node *p = root;
    node *tmp;
    for(int i=0;str[i];i++)
    {
        int id = str[i]-'a';
        while(p->next[id]==NULL && p!=root) p = p->fail;
        if(p->next[id]!=NULL) p = p->next[id];
        tmp = p;
        while(tmp!=root)
        {
            if(tmp->num>0)
            {
                ans += tmp->num;
                tmp->num = 0;
            }
            else tmp = tmp->fail;
        }
    }
    return ans;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d",&n);
        while(n--)
        {
            scanf("%s",word);
            ac_insert();
        }
        ac_setFail();
        scanf("%s",str);
        printf("%d\n",ac_automation());
    }
    return 0;
}
發佈了82 篇原創文章 · 獲贊 37 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章