1. 程式人生 > >【轉載】多模式串匹配之AC自動機

【轉載】多模式串匹配之AC自動機

content 2.4 n) ptr float char toggle msg align

原文地址:https://www.cnblogs.com/codeape/p/3845375.html

目錄[隱藏]
  • 一、概述
  • 二、AC算法思想
  • 三、字典樹tire的構造
  • 四、搜索路徑的確定
  • 附錄:
    • 附1:
    • 附2:AC算法的偽代碼實現描述
    • 附3:
    • 下載:

一、概述

AC自動機算法全稱Aho-Corasick算法,是一種字符串多模式匹配算法。該算法在1975年產生於貝爾實驗室,是著名的多模匹配算法之一。AC算法用於在一段文本中查找多個模式字符串,即給你很多字符串,再給你一段文本,讓你在文本中找這些串是否出現過,出現過多少次,分別在哪裏出現。 該算法應用有限自動機巧妙地將字符比較轉化為了狀態轉移。此算法有兩個特點,一個是掃描文本時完全不需要回溯,另一個是時間復雜度為O(n),時間復雜度與關鍵字的數目和長度無關,但所需時間和文本長度以及所有關鍵字的總長度成正比。
AC算法有三個主要步驟,一個是字典樹tire的構造,一個是搜索路徑的確定(即構造失敗指針),還有就是模式匹配過程。 學習AC自動機算法之前,最好先熟悉KMP算法,因為KMP算法與字典樹tire的構造很是類似。KMP算法是一種經典的單字符串匹配算法。

二、AC算法思想

AC算法思想:用多模式串建立一個確定性的樹形有限狀態機,以主串作為該有限狀態機的輸入,使狀態機進行狀態的轉換,當到達某些特定的狀態時,說明發生模式匹配。 下圖是多模式he/ she/ his /hers構成的一個確定性有限狀態機,做幾點說明: 技術分享圖片
圖2.1 1、 該狀態機優先按照實線標註的狀態轉換路徑進行轉換,當所有實線標註的狀態轉換路徑條件不能滿足時,按照虛線的狀態轉換路徑進行狀態轉換。如:狀態0時,當輸入h,則轉換到狀態1;輸入s,則轉換到狀態3;否則轉換到狀態0。 2、 匹配過程如下:從狀態0開始進行狀態轉換,主串作為輸入。如主串為:ushers,狀態轉換的過程是這樣的: 技術分享圖片

圖2.2 3、 當狀態轉移到2,5,7,9等紅色狀態點時,說明發生了模式匹配。 如主串為:ushers,則在狀態5、2、9等狀態時發生模式匹配,匹配的模 式串有she、he、hers。 定義: 在預處理階段,AC自動機算法建立了三個函數,轉向函數goto,失效函數failure和輸出函數output,由此構造了一個樹型有限自動機。 轉向函數,指的是一種狀態之間的轉向關系。g(pre, x)=next:狀態pre在輸入一個字符x後轉換為狀態next(上圖中的實線部分)。如果在模式串中不存在這樣的轉換,則next=failstate失效函數,指的也是狀態和狀態之間一種轉向關系。f(per)=next:是在比較失配的情況下使用的轉換關系。在構造轉向函數時,把不存在的轉換用failstate
表示,但是failstate不是一個具體的狀態,狀態機轉換轉換到failstate狀態的時候就不知道該往哪轉了。所以就要在狀態機中找到一個有意義的狀態代替failstate,當出現failstate狀態時,自動切換到那個狀態。 這個狀態節點應該具有這樣的特征:從這個狀態節點向上直到樹根節點(狀態0)所經歷的輸入字符,和從產生failstate狀態的那個狀態節點向上所經歷的輸入字符串完全相同。而且這個狀態節點,是所有具備這些條件的節點中深度最大的那個節點。如果不存在滿足條件的狀態節點,則失效函數為0。 累死了。舉例子說吧,對狀態9輸入任何一個字符都會產生failstate狀態,需要失效函數。狀態3向上到狀態0經過的輸入字符串為s;而由狀態9向上的輸入字符串為sreh。字符串s相同,並且狀態3是滿足此條件的唯一節點,則 f(9)=3。 說來說去,失效函數就是要幹這麽件事兒: 技術分享圖片
圖2.3 意思就是說,在比較模式串1發生失配時,找一個模式串2,使得P2[0…j-1] = P1[i-j+1i]。然後繼續比較模式串2。看上面那個圖,想起點兒什麽東西沒有?對了,是KMP算法。有人說AC算法就是KMP算法在多模式匹配情況下的擴展。 輸出函數,指的是狀態和模式串之間的一種關系。output(i)={P},表示當狀態機到達狀態i時,模式串集合{P}中的所有模式串可能已經完成匹配。

例: 模式串為:he/ she/ hers/ his 時,如圖2.4所示。 轉向函數: 技術分享圖片
圖2.4 失效函數: 技術分享圖片
圖2.5 輸出函數: 技術分享圖片
圖2.6

三、字典樹tire的構造

這個比較好理解,就是把要匹配的一些字符串添加到樹結構中去。樹邊就是單詞中的字符,單詞中最後一個字符的連接節點添加標誌,以表示改節點路徑包含1個字典中的字符串,搜索到此節點就表示找到了字典中的某個單詞,可以直接輸出。 Trie是一個樹形結構的狀態裝換圖,從一個結點到它的各個子結點的邊上有不同的標號。Trie的葉子結點表示識別到的關鍵字。 當我們的模式串在Tire上進行匹配時,如果與當前節點的關鍵字不能繼續匹配的時候,就應該去當前節點的失敗指針所指向的節點繼續進行匹配。 例子:某字典P={he,she,his,hers}對應的字典樹如下圖: 技術分享圖片
圖3.1 圖中有數字的節點到根節點的路勁正好對應字典中的字符串,數字表述單詞在字典中的順序,也可以是其他標誌。

四、搜索路徑的確定

我的理解是:利用後綴字符串來確定。後綴字符串就是某個字符串的後面的一部分。比如abcde的後綴字符串有bcde,cde,de和e。 假定目標字符串為ushers,字典為上圖(圖1)所示。 搜索過程目標字符串指針指向的字符和字典中的字符會有以下幾種情況: a. 當前字符匹配。表示從當前節點沿著樹邊有一條路徑可以到達目標字符,此時只需沿該路徑走向下一個節點繼續匹配即可,目標字符串指針移向下個字符繼續匹配; 如:當指針指到s處,此時字典樹指針處於根,要從根到s處,可以看到圖中有一條從根經s連接到的節點,因此字典樹節點指針指向此節點,目標字符串指針移動到下一字符h繼續匹配;顯然當前節點有一條經h連接到的節點,於是重復操作到有數字標誌的節點2處,表示已找到,該匹配字符串就是"she",輸出該字符串的位置後,目標字符串指針增1指向"r",字典指針指向數字2節點,進行下次匹配。 b. 當前字符無匹配。表示當前節點的任何一條邊都無法達到要匹配的字符,此時不能沿現有路徑前進,只能回溯,回溯到存在的最長的後綴字符串處,如果沒有任何後綴字符串匹配則回溯到樹根處。然後從當前回溯節點判斷是否可以到達目標字符串字符。 如:接上,由於數字2節點無經"r"的連接,因此回溯,she的後綴字符串he在字典樹中,因此字典樹指針指向帶有數字1的標誌節點,由於帶有標誌,直接輸出該節點"HE"(存疑,很多文章沒有提到此處需要輸出,正常路徑移動的字典指針節點要判斷是否可以輸出,那麽由回溯路徑改變的字典指針指向的節點要不要判斷是否輸出?),然後從數字1節點判斷是否有經"r"到下一節點的路徑,顯然圖中有。因此字典樹節點指向下一節點,重復以上操作,最後找到"hers",此時匹配搜索也結束了。 以上兩種情況直到目標字符串指針直到末尾結束匹配。在匹配過程中遇到有標誌的節點說明找到了字典中的某個詞,可以直接輸出。 輸出說明: 每次目標串指針移動前都需要判斷當前節點是否可以輸出,並遞歸的判斷當前節點回溯路徑上的節點是否可以輸出(其實就是判斷所有後綴字符串,she匹配時,其後綴he也會匹配,即使she不匹配,其後綴he也可能匹配,因此需遞歸判斷後綴字符串),直到樹根結束遞歸。 由於固定字典的字符串的後綴字符串都是已知的,因此可以在字典樹結構中存儲匹配失敗的路徑方向,因此只要字典樹構造完畢,就可以根據字典樹的路徑進行匹配了,效率非常快。以上就是我對該算法的全部過程的理解,疏漏之處在所難免。

附錄:

1

含匹配失敗的情況的路徑選擇的字典樹,實線表示匹配成功的正常路徑,虛線表示失敗的回溯路徑 技術分享圖片圖 附1.1

2AC算法的偽代碼實現描述

T為目標字符串,長度為m,q為字典樹的節點指針,g函數返回從節點q經過路徑T到達的下一節點指針,f函數返回節點q的回溯節點指針。flag判斷節點是否為標誌節點
q := 0; // initial state (root)
for i := 1 to m do
while g(q,T) = NULL do
q := f(q); //
回溯
q := g(q,T); // 前進
node:=q;
while(node!=root){
if flag(node) exist ; then print i, out(node);
node = f(node); //查找回溯節點
}
end for;

附3

一個簡單的AC算法實現源碼示例參考:
/*
程序說明:多模式串匹配的AC自動機算法
自動機算法可以參考《柔性字符串匹配》裏的相應章節,講的很清楚
*/

#include <stdio.h>
#include <string.h>

const  int MAXQ = 500000+10;
const  int MAXN = 1000000+10;
const  int MAXK = 26;  //自動機裏字符集的大小 
struct  TrieNode
{
    TrieNode* fail;
    TrieNode* next[MAXK];
    bool danger;   //該節點是否為某模式串的終結點 
    int  cnt;    //以該節點為終結點的模式串個數 
    TrieNode()
    {
        fail = NULL;
        memset(next, NULL, sizeof(next));
        danger = false;
        cnt = 0;
    }
}*que[MAXQ], *root;
//文本字符串
char  msg[MAXN];
int   N;
void  TrieInsert(char *s)
{
    int  i = 0;
    TrieNode *ptr = root;
    while(s)
    {
        int  idx = s-‘a‘;
        if(ptr->next[idx] == NULL)
            ptr->next[idx] = new TrieNode();
        ptr = ptr->next[idx];
        i++;
    }
    ptr->danger = true;
    ptr->cnt++;
}

void  Init()
{
    int  i;
    char  s[100];
    root = new TrieNode();
    
    printf("輸入模式串數量:");
    scanf("%d", &N);
    for(i = 0; i < N; i++)
    {
        printf("輸入第%d個模式串(共%d個):",i,N);
        scanf("%s", s);
        TrieInsert(s);
    }
}

void  Build_AC_Automation()
{
    int  rear = 1, front = 0, i;
    que[0] = root;
    root->fail = NULL;
    while(rear != front)
    {
        TrieNode *cur = que[front++];
        for(i = 0; i < 26; i++)
            if(cur->next != NULL)
            {
                if(cur == root)
                    cur->next->fail = root;
                else
                {
                    TrieNode *ptr = cur->fail;
                    while(ptr != NULL)
                    {
                        if(ptr->next != NULL)
                        {
                            cur->next->fail = ptr->next;
                            if(ptr->next->danger == true)
                                cur->next->danger = true;
                            break;
                        }
                        ptr = ptr->fail;
                    }
                    if(ptr == NULL) cur->next->fail = root;
                }
                que[rear++] = cur->next;
            }
    }
}
int  AC_Search()
{
    int  i = 0, ans = 0;
    TrieNode *ptr = root;
    while(msg)
    {
        int  idx = msg-‘a‘;
        while(ptr->next[idx] == NULL && ptr != root) ptr = ptr->fail;
        ptr = ptr->next[idx];
        if(ptr == NULL) ptr = root;
        TrieNode *tmp = ptr;
        while(tmp != NULL )&& tmp->cnt != -1)
        {
            ans += tmp->cnt; //統計文本中出現過的不同模式串數量
            tmp->cnt = -1;//對於每個模式串的出現只計算一次,如統計所有出現則應註釋該行
            tmp = tmp->fail;
        }
        i++;
    }
    return  ans;
}
int  main()
{
    int  T;
    printf("輸入測試次數:");
    scanf("%d", &T);
    while(T--)
    {
        Init();
        Build_AC_Automation();
        //文本 
        printf("輸入匹配文本:");
        scanf("%s", msg);
        printf("%dn", AC_Search());
    }
    getchar();
    return 0;
}

下載:

摘自snort的AC算法源碼實現等資料下載:技術分享圖片點擊下載此文件

【轉載】多模式串匹配之AC自動機