1. 程式人生 > >ac自動機最詳細的講解,讓你一次學會ac自動機。【轉載】

ac自動機最詳細的講解,讓你一次學會ac自動機。【轉載】

在沒學ac自動機之前,覺得ac自動機是個很神奇,很高深,很難的演算法,學完之後發現,ac自動機確實很神奇,很高深,但是卻並不難。 
我說ac自動機很神奇,在於這個演算法中失配指標的妙處(好比kmp演算法中的next陣列),說它高深,是因為這個不是一般的演算法,而是建立在兩個普通演算法的基礎之上,而這兩個演算法就是kmp與字典樹。所以,如果在看這篇部落格之前,你還不會字典樹或者kmp演算法,那麼請先學習字典樹或者kmp演算法之後再來看這篇部落格。好了,閒話扯完了,下面進入正題。

在學習一個新東西之前,一定要知道這個東西是什麼,有什麼用,我們學它的目的是什麼,如果對這些東西沒有一個清楚的把握,我不認為你能學好這個新知識。 
那麼首先我們來說一下ac自動機是什麼。下面是我從百度上找的。Aho-Corasick automaton,該演算法在1975年產生於貝爾實驗室,是著名的多模匹配演算法。 
從上面我們可以知道,ac自動機其實就是一種多模匹配演算法,那麼你可能會問什麼叫做多模匹配演算法。下面是我對多模匹配的理解,與多模與之對於的是單模,單模就是給你一個單詞,然後給你一個字串,問你這個單詞是否在這個字串中出現過(匹配),這個問題可以用kmp演算法在比較高效的效率上完成這個任務。那麼現在我們換個問題,給你很多個單詞,然後給你一段字串,問你有多少個單詞在這個字串中出現過,當然我們暴力做,用每一個單詞對字串做kmp,這樣雖然理論上可行,但是時間複雜度非常之高,當單詞的個數比較多並且字串很長的情況下不能有效的解決這個問題,所以這時候就要用到我們的ac自動機演算法了。 
對於上面的文字,我已經回答了什麼是多模匹配和我們為什麼要學習ac自動機那就是ac自動機的作用是什麼等一系列問題。下面是ac自動機的具體實現步驟以及模板程式碼。 
1.把所有的單詞建立一個字典樹。 
在建立字典樹之前,我們先定義每個字典樹上節點的結構體變數

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

其中fail 是失配指標 
sum是這個節點是不是一個單詞的結尾,以及相應的個數。 
下面是字典樹的建立過程

void Insert(char *s)
{
    node *p = root;
    for(int i = 0; s[i]; i++)
    {
        int x = s[i] - 'a';
        if(p->next[x] == NULL)
        {
            newnode=(struct node *)malloc(sizeof(struct node));
            for(int j=0;j<26;j++) newnode->next[j] = 0;
            newnode->sum = 0;newnode->fail = 0;
            p->next[x]=newnode;
        }
        p = p->next[x];
    }
    p->sum++;
}

注意在建立字典樹的過程中,先讓每個節點的fail指標先為空。 
下面就是ac自動機最關鍵的一步,求解每個節點的失配指標,我在這裡先說一下失配指標具體表示什麼,每個節點的失配指標指向的是以當前節點表示的字元為最後一個字元的最長當前字串的字尾字串的最後一個節點。可能在這裡你可能不怎麼懂,沒關係,光看上面的文字,肯定難以看懂,下面我用一個圖來簡單的表示一下,有助於你理解。

假如我們有四個單詞,abcd, bce, abd, cd,那麼我們建立字典樹如下: 
這裡寫圖片描述 
首先我們讓與根節點直接相連的節點的fail直接指向root,為了讓你更好的理解fail指標,我們以節點x,y,z為例,我們讓從圖中我們可以看出x節點的fail指向了y節點,y節點的fail指向了z節點,為什麼會這樣指,因為x節點表示字串abc,而字典樹中含有最長,且以c結尾,且是abc的字尾的字串bc(以y節點結尾的),同理,以y節點表示的字串是bc,而以c結尾,且是bc的字尾的最長字串是c(以z節點結尾的)。這就是fail指標指向的目標,那麼我們得到了這個fail指標在匹配中有什麼用呢,我們還是用上面的那個圖來舉例說明一下,假設文字串是abce,通過字典樹我們可以看出,通過abc,所以我們可以匹配到x節點,但是到後面,我們發現d與e不匹配,這時我們就需要用到當前節點的fail了,因為x的fail指向的是y節點,所以我們直接跳到y節點,這是發現y節點後面有e,匹配上了,所以單詞bce就在文字串abce中被檢測出來了。當然這只是最簡單的一種情況。 
下面是構造fail指標的具體程式碼。 
基於佇列(bfs)實現的。

void build_fail_pointer()
{
    head = 0;
    tail = 1;
    q[head] = root;
    node *p;
    node *temp;
    while(head < tail)
    {
        temp = q[head++];
        for(int i = 0; i <= 25; i++)
        {
            if(temp->next[i])
            {
                if(temp == root)
                {
                    temp->next[i]->fail = root;
                }
                else
                {
                    p = temp->fail;
                    while(p)
                    {
                        if(p->next[i])
                        {
                            temp->next[i]->fail = p->next[i];
                            break;
                        }
                        p = p->fail;
                    }
                    if(p == NULL) temp->next[i]->fail = root;
                }
                q[tail++] = temp->next[i];
            }
        }
    }
}

最後是利用前面求得的fail指標進行匹配。 
程式碼如下:

void ac_automation(char *ch)
{
    node *p = root;
    int len = strlen(ch);
    for(int i = 0; i < len; i++)
    {
        int x = ch[i] - 'a';
        while(!p->next[x] && p != root) p = p->fail;
        p = p->next[x];
        if(!p) p = root;
        node *temp = p;
        while(temp != root)
        {
           if(temp->sum >= 0)
           {
               cnt += temp->sum;
               temp->sum = -1;
           }
           else break;
           temp = temp->fail;
        }
    }
}

如果你還沒有看懂ac自動機,沒關係,細細品味,你一定能成功的。

如果你看懂了上面的講解,那麼我們可以做一道模板題來試試,以hdu2222為例,ac程式碼如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 5;
const int MAX = 10000000;
int cnt;
struct node{
    node *next[26];
    node *fail;
    int sum;
};
node *root;
char key[70];
node *q[MAX];
int head,tail;
node *newnode;
char pattern[maxn];
int N;
void Insert(char *s)
{
    node *p = root;
    for(int i = 0; s[i]; i++)
    {
        int x = s[i] - 'a';
        if(p->next[x] == NULL)
        {
            newnode=(struct node *)malloc(sizeof(struct node));
            for(int j=0;j<26;j++) newnode->next[j] = 0;
            newnode->sum = 0;newnode->fail = 0;
            p->next[x]=newnode;
        }
        p = p->next[x];
    }
    p->sum++;
}
void build_fail_pointer()
{
    head = 0;
    tail = 1;
    q[head] = root;
    node *p;
    node *temp;
    while(head < tail)
    {
        temp = q[head++];
        for(int i = 0; i <= 25; i++)
        {
            if(temp->next[i])
            {
                if(temp == root)
                {
                    temp->next[i]->fail = root;
                }
                else
                {
                    p = temp->fail;
                    while(p)
                    {
                        if(p->next[i])
                        {
                            temp->next[i]->fail = p->next[i];
                            break;
                        }
                        p = p->fail;
                    }
                    if(p == NULL) temp->next[i]->fail = root;
                }
                q[tail++] = temp->next[i];
            }
        }
    }
}
void ac_automation(char *ch)
{
    node *p = root;
    int len = strlen(ch);
    for(int i = 0; i < len; i++)
    {
        int x = ch[i] - 'a';
        while(!p->next[x] && p != root) p = p->fail;
        p = p->next[x];
        if(!p) p = root;
        node *temp = p;
        while(temp != root)
        {
           if(temp->sum >= 0)
           {
               cnt += temp->sum;
               temp->sum = -1;
           }
           else break;
           temp = temp->fail;
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        root=(struct node *)malloc(sizeof(struct node));
        for(int j=0;j<26;j++) root->next[j] = 0;
        root->fail = 0;
        root->sum = 0;
        scanf("%d",&N);
        getchar();
        for(int i = 1; i <= N; i++)
        {
            gets(key);
            Insert(key);
        }
        gets(pattern);
        cnt = 0;
        build_fail_pointer();
        ac_automation(pattern);
        printf("%d\n",cnt);
    }
    return 0;
}

相關推薦

ac自動機詳細講解學會ac自動機轉載

在沒學ac自動機之前,覺得ac自動機是個很神奇,很高深,很難的演算法,學完之後發現,ac自動機確實很神奇,很高深,但是卻並不難。  我說ac自動機很神奇,在於這個演算法中失配指標的妙處(好比kmp演算法中的next陣列),說它高深,是因為這個不是一般的演算法,而是建立在兩

try catch finally 的用法知道多少?詳細到位的講解配合程式碼例項講解輕鬆掌握和理解

廢話就不多說了,關於 try catch 相信各位已經不陌生了,但是真正意義上會用它呢,還是有欠缺的,為什麼這麼說呢?因為博主也 是在做一個專案的時候遇到了這樣的問題,去看了下 API 才想起來,於是乎就順便寫了這篇部落格分享出來,方便大家觀看和學習以 及理解,等下筆者會按

java 常用關鍵詞詳細解析入門java

java語法中有一些詞被賦予了特殊含義,他們有固定用法,我們必須遵從,下面我就簡單的說一說用法及格式: 關鍵字包括abstract  boolean  break  byte  case  catch  char  class  continue  default  do

安卓JNI精細化講解徹底瞭解JNI():環境搭建與HelloWord

目錄 1、基礎概念 ├──1.1、JNI ├──1.2、NDK ├──1.3、CMake與ndk-build 2、環境搭建 3、Native C++ 專案(HelloWord案例) ├── 3.1、專案建立(java、kotlin) ├── 3.2、CMake的應用(詳細講解) ├── 3.3、ndk-bui

20個優秀手機界面扁平化設計秒看懂扁平化

古語常說:去粗取精,去偽存真。自小念念不忘的句子,不僅是教會我們為人處世的道理,更是準確的揭示了事物的本質和規律。自13年興起的Flat設計趨勢,也恰恰好符合了這一規律。去除冗余、厚重和繁雜的裝飾效果,這是Flat設計的核心意義。 道理仿佛異常簡單,但要做出優秀

快速將Word轉PDF技巧看就會

圖標 get lan bsp 來吧 ref 安裝軟件 怎麽 img 關於Word轉PDF這個問題,在日常辦公中,我們還是比較經常遇到的,有的時候著急轉換文件,但是電腦上沒有軟件怎麽辦?有不需要安裝軟件就直接解決Word轉PDF這個問題辦法嗎? 答案是有的,今天就為大家介紹

CSS3新增的偽類選擇器體驗使用CSS3的快感

CSS選擇器用於選擇你想要的元素的樣式的模式。偽類元素主要用於對已有選擇器做進一步的限制,對已有選擇器能匹配的元素做進一步的過濾。 偽類選擇器 1.UI元素狀態偽類選擇器 UI元素狀態偽類選擇器主要用於根據UI元素的狀態進行篩選,UI元素狀態偽類選擇器有如下幾個。

5個Excel快速查詢刪除重複值技巧鍵解決重複小煩惱!

現在在職場中,少不了要用到Excel技巧,雖然有時很快捷,但不能保證在使用的過程中沒有重複的資料的出現,想要排查重複資料,一個個的看,那多浪費時間,所以呀,今天小編就來教給大家5個很實用的查詢,刪除、統計重複值的Excel小技巧,希望大家能用上。 1.重複資料提醒 公式:=COUNTIF(A:A,A1)=

2 張圖秒理解 CountDownLatch、CyclicBarrier

CountDownLatch (倒數閂,Latch:門閂) 經常用於 監聽某些初始化操作,等 初始化執行緒 全部執行完畢後,才通知 主執行緒 繼續工作 a) 即 一個執行緒處於阻塞的狀態下,他要收到 多少次通知,才能被 甦醒,並繼續往下執行 b) 注意:只能阻塞 一個執行

就像看小說一樣一個小時學會Python零基礎

思路:用shell程式設計.(Linux通常是bash而Windows是批處理指令碼).例如,在Windows上用ping ip 的命令依次測試各個機器並得到控制檯輸出.由於ping通的時候控制檯文字通常是"Reply from ... " 而不通的時候文字是"time out ... " ,所以,

主席樹(靜態) 圖文講解就懂 hdu2665為例

主席樹 先介紹一下主席樹,主席樹也稱函式式線段樹也稱可持久化線段樹。(其實就是支援查詢歷史版本,這個在看完之後就會了解) 其實主席樹就是很多線段樹組合的總體,從它的其它稱呼也可以看出來了,其實它本質上還是線段樹。 主席樹就是利用函數語言程式設計的思想來使線段樹支

五分鐘帶看完CSS3新增的偽類選擇器體驗使用CSS3的快感

CSS選擇器用於選擇你想要的元素的樣式的模式。偽類元素主要用於對已有選擇器做進一步的限制,對已有選擇器能匹配的元素做進一步的過濾。 偽類選擇器 1.UI元素狀態偽類選擇器 UI元素狀態偽類選擇器主要用於根據UI元素的狀態進行篩選,UI元素狀態偽類選擇器有如下幾個。

主席樹 (動態)圖文講解就懂 zoj2112為例

主席樹(動態) 其實靜態主席樹我們弄清楚之後,動態的可以很快學會。因為動態主席樹就是在靜態主席樹的基礎上增加了一批用樹狀陣列思維維護的線段樹。 我們以下面的例子講解。 5 3 3 2 1 4 7 Q 1 4 3 詢問區間[1,4]第3小數

劃分樹 圖文講解就懂 hdu2665為例

學主席樹的時候看到一篇部落格說是因為一個大牛當時不會劃分樹而想出的另一種解決區間第k小的問題,所以在學了主席樹之後我就學了下劃分樹。 劃分樹解決靜態區間第k小問題比主席樹的時空消耗都要少,不過好像不能解決動態區間第k小問題。 這篇部落格寫的非常好,所以我

iO開發 -Masonry學習看就會用看就能上手專案

在這裡問下大家,用的約束方式是哪種?近年來,約束這件事情在開發中的分量越來越重,不同機型的問世,使得原來使用係數的開發人員苦不堪言,一開始約束的使用讓很多人很不習慣,網上給出的Demo也層出不全,沒有人真正告訴你該怎麼來寫一個tableview,怎麼來寫一個scrollView,這對於不

Android 8.1適配規範及常見問題處理方式口吃上“奧利奧”

熱文導讀 | 點選標題閱讀來源:OPPO開發者平臺(ID:OPPOMOBILEOPEN)據 An

騰訊Bugly乾貨分享深入理解 ButterKnife的程式學會寫程式碼

0、引子 話說我們做程式設計師的,都應該多少是個懶人,我們總是想辦法驅使我們的電腦幫我們幹活,所以我們學會了各式各樣的語言來告訴電腦該做什麼——儘管,他們有時候也會誤會我們的意思。 突然有一天,我覺得有些程式碼其實,可以按照某種規則生成,但你又不能不

Spring Boot(三): 在Spring Boot中使用log4j2的console端豐富起來

maven依賴 <dependency> <groupId>org.springframework.boot</groupId>

32個Python爬蟲項目吃到撐

com music air 進行 使用 shee c-s 客戶端 查詢 整理了32個Python爬蟲項目。整理的原因是,爬蟲入門簡單快速,也非常適合新入門的小夥伴培養信心。所有鏈接指向GitHub,祝大家玩的愉快~O(∩_∩)O WechatSogou [

每天一道LeetCode-----買賣商品問題計算大利潤分別有交易交易交易的情況

Best Time to Buy and Sell Stock 給定一個價格序列prices,其中prices[i]代表第i天商品的價格,商家需要在某一天買入,然後在之後的某一天出售,計算可以獲得的最大利潤 本質就是計算prices[i]−price