1. 程式人生 > >【AC自動機】【資料結構】【樹】【Aho-Corasick automation】AC自動機理解(入門)

【AC自動機】【資料結構】【樹】【Aho-Corasick automation】AC自動機理解(入門)

引入

我們首先提出一個問題:
給出n個串每個串的長度m
然後給出一個長度為k的串,詢問前n個串中有多少個是匹配成了的

暴力搜尋

這題不是sb題目嗎?
隨隨便便O(kmn)跑過。
。。。。
n=10000 m=50
k=1000000
。。。。
好吧——我們用AC自動機吧

樣例

首先我們舉一個例子,我們有n=3個串he 和 her 和 she
然後我們通過構建Trie可以得到下圖
這裡寫圖片描述
這裡紅色的節點到根的路徑可以構成一個串(怎麼那麼像字尾自動機?)然後我們可以發現

正文

概念

我們使用Fail(i)表示i節點用虛線連線的邊,這樣的邊我們的作用就是當前節點到根的路徑構成的字串的最長的存在的字尾的串的位置。比如she 和 he 中 he 就是 she 的最長的字尾所以我們連線 e2->e這樣我們假設從e2無法再繼續伸展的時候我們就可以O(1)找到類似的串,然後繼續進行伸展得到如下的圖
這裡寫圖片描述


這樣比如我們匹配sher的時候我們首先沿著邊走到e2然後不需要重新搜尋,而是選擇立即跳轉到e節點然後搜尋到r節點

這樣我們就構造出了一個AC自動機的圖

構建方法

就使用上面的例子
這裡寫圖片描述
對於這樣的一個圖,我們首先佇列中有

Queue 0 1
h s

同時將深度為1的節點連線Fail到root
這裡寫圖片描述
然後我們掃描一遍h節點可以得到e節點然後我們掃描s節點可以發現s的Fail邊上的root節點存在和s的兒子h相同的另一個h那麼因為s的fail邊上的所有節點的字尾相同,所以我們有h2和h節點同時增加一個節點後仍然滿足上面的Fail邊的概念,所以我們h2->h得到
這裡寫圖片描述


同時我們的佇列變成了

Queue 0 1
2 h2

對於下面的伸展我們可以得到(同理):
這裡寫圖片描述
呵呵呵程式碼時間:

程式碼

void Insert(char *s){
    int len = strlen(s), u = root;
    for(int i=0;i<len;i++){
        int id = idx(s[i]);
        if(!pool[u].ch[id]) pool[u].ch[id] = ++ecnt;
        u = pool[u].ch[id];
    }
    pool[u].End_Flag++;
}
void
build(){ queue<int> que; for(int i=0;i<26;i++) if(pool[root].ch[i]) que.push(pool[root].ch[i]); int u, v, t, p; while(!que.empty()){ u = que.front(); que.pop(); for(int i=0;i<MAXK;i++) if(pool[u].ch[i]){ v = pool[u].ch[i]; que.push(v); p = pool[u].Fail; while(p && !pool[p].ch[i]) p = pool[p].Fail; t = pool[p].ch[i]; pool[v].Fail = t; pool[v].last = pool[t].End_Flag ? t : pool[t].last; } } }

感謝

感謝您閱讀這篇文章,如果有不足的地方請做出評論謝謝