AC自動(dòng)機(jī)

AC自動(dòng)機(jī)(Aho-Corasick\ automaton)刻炒,可以解決多模板串匹配的問(wèn)題茂嗓』闭樱可以理解為可以一次性匹配很多串的KMP伍宦。在KMP中凉敲,有一個(gè)失配函數(shù)next化撕,在AC自動(dòng)機(jī)中儡毕,也有一個(gè)類(lèi)似的失配函數(shù)fail簇捍。類(lèi)似于KMP的轉(zhuǎn)移乙帮,每當(dāng)AC自動(dòng)機(jī)失配時(shí)杜漠,也會(huì)相應(yīng)地轉(zhuǎn)到當(dāng)前節(jié)點(diǎn)的fail

Trie

為了一次性高效地匹配字符串察净,我們需要用一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)所有的模板串驾茴。這個(gè)數(shù)據(jù)結(jié)構(gòu)就叫Trie∪蹋可以把Trie叫做字典樹(shù)沟涨,所以Trie是一棵樹(shù)。比如下面這個(gè)

Trie

這個(gè)就是一棵包含ab,abc,bd,dda四個(gè)串的Trie异吻。通過(guò)觀察裹赴,我們可以發(fā)現(xiàn)

  1. 根節(jié)點(diǎn)不包含字符

  2. 從根節(jié)點(diǎn)到某個(gè)節(jié)點(diǎn)簡(jiǎn)單路徑上的字母即是這個(gè)節(jié)點(diǎn)代表的串

  3. 串的終點(diǎn)有標(biāo)記

每次匹配,我們從Trie的根節(jié)點(diǎn)出發(fā)诀浪,嘗試用文本串去匹配Trie樹(shù)棋返,由于Trie樹(shù)將一些字符串壓縮到了一起,因此時(shí)間復(fù)雜度會(huì)大大降低雷猪。但注意睛竣,Trie樹(shù)是一種用空間換時(shí)間的做法

部分代碼:

void init() {memset(trie[0],0,sizeof(trie[0])),ncnt=0;}
void insert(char s[],int len,int n) {//插入新串
    int now=0,c;
    for (int i=1;i<=len;i++) {//查找路徑(沒(méi)有則添加)
        c=idx(s[i]);
        if (trie[now][c]==0)
            now=trie[now][c]=++ncnt,memset(trie[now],0,sizeof(trie[now]));
        else now=trie[now][c];
    }
    val[now]=n;//標(biāo)記終點(diǎn)
}

Trie樹(shù)的空間復(fù)雜度是O(nmt)n是串的總數(shù)求摇,m是最長(zhǎng)串的長(zhǎng)度射沟,t是節(jié)點(diǎn)字母的種類(lèi)

AC自動(dòng)機(jī)實(shí)現(xiàn)

我們引入一個(gè)新的數(shù)組last,代表與當(dāng)前節(jié)點(diǎn)有最長(zhǎng)后綴的串在Trie上的編號(hào)与境。很明顯验夯,假如當(dāng)前串匹配成功了,那么它的last也一定可以匹配成功(是它的后綴八さ蟆)

last的一個(gè)重要性質(zhì): 設(shè)p的失敗指針指向節(jié)點(diǎn)k挥转,則它應(yīng)該滿(mǎn)足這樣的性質(zhì):從rootk的字符串完全匹配于從p往上相同長(zhǎng)度的后綴

注意lastfail的區(qū)別:last是指當(dāng)前節(jié)點(diǎn)的某個(gè)出現(xiàn)在模板串之中的后綴,而fail則不要求出現(xiàn)在模板串中

如果當(dāng)前串匹配失敗,那么就一直跳fail绑谣。我們這樣考慮:

假設(shè)s[1,i]已經(jīng)匹配成功了党窜,第i個(gè)字符對(duì)應(yīng)Trie上的u號(hào)節(jié)點(diǎn),但是s[i+1]匹配失敗了借宵,很明顯幌衣,我們可以從u節(jié)點(diǎn)跳到它的fail。因?yàn)?span id="7g2v0mz" class="math-inline">s[1,i]已經(jīng)匹配成功了暇务,那么s[1,i]的某個(gè)后綴(對(duì)應(yīng)fail)也一定可以匹配成功

int c=idx(s[i]);
while (now&&!trie[now][c]) now=fail[now];

舉個(gè)例子

我們考慮這樣一個(gè)自動(dòng)機(jī)

AC自動(dòng)機(jī)

這是一棵倒著的Trie加上一些虛線(xiàn)箭頭一個(gè)AC自動(dòng)機(jī)的狀態(tài)圖泼掠,綠色的點(diǎn)代表有標(biāo)記。至于點(diǎn)上的數(shù)垦细,可以暫時(shí)不管择镇。

實(shí)線(xiàn)代表Trie上的邊,虛線(xiàn)代表fail指向的節(jié)點(diǎn)

觀察91這個(gè)點(diǎn)括改,它表示的串是SHE腻豌,它的fail指針指向76號(hào)點(diǎn),這個(gè)點(diǎn)表示的串是HE嘱能,可以發(fā)現(xiàn)吝梅,它是SHE的一個(gè)后綴。

我們考慮在這樣一棵Trie上匹配串SHERS

1.當(dāng)前位于根節(jié)點(diǎn)惹骂,對(duì)應(yīng)字符S苏携,因此走85號(hào)點(diǎn),匹配成功
2.當(dāng)前位于85號(hào)點(diǎn)对粪,對(duì)應(yīng)字符H右冻,因此走90號(hào)點(diǎn),匹配成功
3.當(dāng)前位于90號(hào)點(diǎn)著拭,對(duì)應(yīng)字符E纱扭,因此走91號(hào)點(diǎn),匹配成功
4.91號(hào)點(diǎn)被標(biāo)記過(guò)儡遮,匹配數(shù)+1
5.當(dāng)前位于91號(hào)點(diǎn)乳蛾,對(duì)應(yīng)字符R,這個(gè)點(diǎn)沒(méi)有字符為R的點(diǎn)鄙币,匹配失敗肃叶,跳到91號(hào)點(diǎn)的fail指針76號(hào)點(diǎn)
6.當(dāng)前位于76號(hào)點(diǎn),對(duì)應(yīng)字符R十嘿,因此走160號(hào)點(diǎn)因惭,匹配成功
7.當(dāng)前位于160號(hào)點(diǎn),對(duì)應(yīng)字符S详幽,因此走86號(hào)點(diǎn),匹配成功
8.至此,串SHERS已匹配完成

代碼實(shí)現(xiàn)如下

void query(char s[],int len) {
    int now=0;
    for (int i=1;i<=len;i++) {
        int c=idx(s[i]);
        while (now&&!trie[now][c]) now=fail[now];//匹配失敗唇聘,則一直跳fail指針
        now=trie[now][c];
        if (val[now]) cnt[val[now]]++;//匹配成功版姑,且當(dāng)前節(jié)點(diǎn)是某個(gè)模板串的末尾
        int las=last[now];
        while (val[las]) val[las]=0,cnt[val[las]]++,las=last[las];//當(dāng)前節(jié)點(diǎn)匹配成功,它的last也一定匹配成功
    }
}

接下來(lái)是AC自動(dòng)機(jī)的關(guān)鍵部分迟郎,即預(yù)處理fail指針與last指針

由于一個(gè)節(jié)點(diǎn)的fail的深度一定小于這個(gè)點(diǎn)的深度剥险,所以我們考慮用bfs實(shí)現(xiàn)

考慮從一個(gè)已經(jīng)求出faillast的節(jié)點(diǎn)now轉(zhuǎn)移到他的后繼節(jié)點(diǎn)son

如果nowfail指針有與son相同的后繼節(jié)點(diǎn),那么直接轉(zhuǎn)移到那個(gè)后繼節(jié)點(diǎn)宪肖。否則一直跳fail表制,直到根節(jié)點(diǎn)。

for (int i=0;i<128;i++) 
    if (trie[now][i]) {
        int f=fail[now];
        while (f&&!trie[f][i]) f=fail[f];
        int son=trie[now][i];
        fail[son]=trie[f][i];
    }

如果now節(jié)點(diǎn)所指向的fail的這個(gè)兒子已經(jīng)被標(biāo)記過(guò)控乾,即是某個(gè)模板串的結(jié)尾么介,就直接將其賦到son,否則一直跳last

last[son]=val[fail[son]]?fail[son]:last[fail[son]];

bfs實(shí)現(xiàn)如下

void bfs() {
    queue<int> q;
    while (!q.empty()) q.pop();
    fail[0]=last[0]=0;
    for (int i=0;i<26;i++) {
        int now=trie[0][i];
        if (now) fail[now]=last[now]=0,q.push(now);
    }
    while (!q.empty()) {
        int now=q.front();q.pop();
        for (int i=0;i<26;i++) 
            if (trie[now][i]) {
                int f=fail[now];
                while (f&&!trie[f][i]) f=fail[f];
                int son=trie[now][i];
                fail[son]=trie[f][i],last[son]=val[fail[son]]?fail[son]:last[fail[son]];
                q.push(son);
            }
    }
}

最后是AC自動(dòng)機(jī)模板代碼

int ncnt=0,trie[100010][26],val[100010];
int idx(char c) {return c-'a';}
void init() {memset(trie[0],0,sizeof(trie[0])),ncnt=0;}
void insert(char s[],int len,int n) {
    int now=0,c;
    for (int i=1;i<=len;i++) {
        c=idx(s[i]);
        if (trie[now][c]==0)
            now=trie[now][c]=++ncnt,memset(trie[now],0,sizeof(trie[now]));
        else now=trie[now][c];
    }
    val[now]=n;//val里存的是串的編號(hào)
}
int fail[100010],last[100010];
void bfs() {
    queue<int> q;
    while (!q.empty()) q.pop();
    fail[0]=last[0]=0;
    for (int i=0;i<26;i++) {
        int now=trie[0][i];
        if (now) fail[now]=last[now]=0,q.push(now);
    }
    while (!q.empty()) {
        int now=q.front();q.pop();
        for (int i=0;i<26;i++) 
            if (trie[now][i]) {
                int f=fail[now];
                while (f&&!trie[f][i]) f=fail[f];
                int son=trie[now][i];
                fail[son]=trie[f][i],last[son]=val[fail[son]]?fail[son]:last[fail[son]];
                q.push(son);
            }
    }
}
int cnt[510];
void query(char s[],int len) {
    int now=0;
    for (int i=1;i<=len;i++) {
        int c=idx(s[i]);
        while (now&&!trie[now][c]) now=fail[now];
        now=trie[now][c];
        if (val[now]) cnt[val[now]]++;
        int las=last[now];
        while (val[las]) val[las]=0,cnt[val[las]]++,las=last[las];
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜕衡,一起剝皮案震驚了整個(gè)濱河市壤短,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慨仿,老刑警劉巖久脯,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镰吆,居然都是意外死亡帘撰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)万皿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)摧找,“玉大人,你說(shuō)我怎么就攤上這事相寇∥坑冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵唤衫,是天一觀的道長(zhǎng)婆赠。 經(jīng)常有香客問(wèn)我,道長(zhǎng)佳励,這世上最難降的妖魔是什么休里? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮赃承,結(jié)果婚禮上妙黍,老公的妹妹穿的比我還像新娘。我一直安慰自己瞧剖,他們只是感情好拭嫁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布可免。 她就那樣靜靜地躺著,像睡著了一般做粤。 火紅的嫁衣襯著肌膚如雪浇借。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天怕品,我揣著相機(jī)與錄音妇垢,去河邊找鬼。 笑死肉康,一個(gè)胖子當(dāng)著我的面吹牛闯估,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吼和,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涨薪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了纹安?” 一聲冷哼從身側(cè)響起尤辱,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厢岂,沒(méi)想到半個(gè)月后光督,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塔粒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年结借,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卒茬。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡船老,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出圃酵,到底是詐尸還是另有隱情柳畔,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布郭赐,位于F島的核電站薪韩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捌锭。R本人自食惡果不足惜俘陷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望观谦。 院中可真熱鬧拉盾,春花似錦、人聲如沸豁状。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至夭禽,卻和暖如春屎暇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驻粟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凶异,地道東北人蜀撑。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剩彬,于是被迫代替她去往敵國(guó)和親酷麦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 文不達(dá)意喉恋,口齒不清沃饶,思想混亂,令人噴飯轻黑。(估計(jì)只有我自己才能看懂我在說(shuō)什么)簡(jiǎn)書(shū)沒(méi)有mathjax公式?jīng)]法愉快顯示...
    njzwj閱讀 1,180評(píng)論 1 2
  • 預(yù)備知識(shí) Trie(字典樹(shù))KMP字符串匹配算法 AC自動(dòng)機(jī)求解問(wèn)題的類(lèi)型 一句話(huà)概括就是:多模匹配糊肤。KMP求解的...
    ZJL_OIJR閱讀 1,009評(píng)論 0 1
  • 類(lèi)似于week2,3;然后這一節(jié)題目說(shuō)是Trie圖氓鄙,其實(shí)用AC自動(dòng)機(jī)更容易搜出來(lái)結(jié)果馆揉。對(duì)于一個(gè)M<=10^6的字符...
    vaisy閱讀 205評(píng)論 0 0
  • 可重疊與不可重疊匹配比如模式串為aba,字符串為abababab,若可重疊匹配,那么aba出現(xiàn)的次數(shù)為三次;若為不...
    Gitfan閱讀 642評(píng)論 0 0
  • fo0Old閱讀 309評(píng)論 0 0