trie樹 & ac自動機

trie樹

trie樹就是字典樹,可以理解爲單詞樹,樹上每條邊是字母,被標記的節點表示根到這個節字母組成了單詞。
在這裏插入圖片描述
數據結構:用二維數組trie[maxn][N],tire[u][c]表示樹上編號爲u的父節點以邊爲c單詞連接到的兒子的編號。
創建trie樹:每次添加一個單詞,若當前路徑已建立此以連接此單詞爲邊的兒子節點就沿着走,否則建立新的節點。

學習鏈接:淺談Trie樹

Trie樹模板

const int maxn=5e5+7;
const int N=26;
struct Tire{
    int trie[maxn][N],tot;
    bool book[maxn];
    void Init(){
        memset(trie,0,sizeof trie);
        memset(book,0,sizeof book);
        tot=0;
    }
    void Insert(string a){
        int u=0;
        for(int i=0;i<a.size();++i){
            int v=a[i]-'a';
            if(trie[u][v]==0){
               trie[u][v]=++tot;
            }
            u=trie[u][v];
        }
        book[u]=true;
    }
}test;

例題:
Phone List

#include<iostream>
#include<set>
#include<string.h>
#include<string>
#include<stdio.h>
using namespace std;
struct Tire{
    int trie[100100][11],tot;
    bool book[100100];
    bool Insert(string a){
        int u=0;
        bool f=false;
        for(int i=0;i<a.size();++i){
            int v=a[i]-'0';
            if(book[u]==true) f=true;
            if(trie[u][v]==0){
               trie[u][v]=++tot;
            }
            u=trie[u][v];
        }

        if(book[u]==true) f=true;
        book[u]=true;
        for(int i=0;i<=9;++i){
            if(trie[u][i]!=0) {
                f=true;
                break;
            }
        }
        return f;
    }
    void Clear(){
        memset(trie,0,sizeof trie);
        memset(book,0,sizeof book);
        tot=0;
    }
}test;
string str;

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        test.Clear();
        scanf("%d",&n);
        bool ans=false;
        for(int i=1;i<=n;++i){
            cin>>str;
            if(test.Insert(str)){
                ans=true;
            }
        }
        if(ans) printf("NO\n");
        else printf("YES\n");
    }
    return 0;
}


ac自動機

kmp是單模式串匹配,ac自動機可以跑多模式串匹配。
ac自動機是在trie樹上增加fail指針,用來實現當前節點失配時,跳轉到前綴與已匹配部分相同後綴相同的節點上。
Fail指針的實質含義就是:如果一個點i的Fail指針指向j。那麼root到j的字符串是root到i的字符串的一個後綴。

舉個栗子:

i:4 j:7
root到i的字符串是“ABC”
root到j的字符串是“BC”
“BC”是“ABC”的一個後綴
所以i的Fail指針指向j

查詢時:從頭開始遍歷文本串,對於文本串上的每個字母,不停地在樹上找前綴與當前已匹配部分的後綴相同的節點。

學習鏈接:AC自動機講解超詳細AC自動機 算法詳解(圖解)及模板

ac自動機模板:

const int maxn=5e5+7;
const int N=26;
struct acAutomaton{
    int trie[maxn][N],cntword[maxn],fail[maxn],tot;
    void Clear(){
        memset(trie,0,sizeof trie);
        memset(cntword,0,sizeof cntword);
        memset(fail,0,sizeof fail);
        tot=0;
    }
    //創建字典樹
    void insertWord(string str){
        int u=0;
        for(int i=0;i<str.length();++i){
            int v=str[i]-'a';
            if(trie[u][v]==0){
                trie[u][v]=++tot;
            }
            u=trie[u][v];
        }
        ++cntword[u];
    }
    void getFail(){
        queue<int> q;
        //將根節點存在的子節點扔進隊列
        for(int i=0;i<26;++i){
            if(trie[0][i]){
                fail[trie[0][i]]=0;
                q.push(trie[0][i]);
            }
        }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;++i){
                if(trie[u][i]==0){
        //當前字母沒有爲i+'a'的子節點,將其子節點指向它fail指針的爲i+'a'子節點,(根節點的fail指針指向自己)
        //因爲當前後綴和fail的前綴相同,可以共用子節點
                    trie[u][i]=trie[fail[u]][i];
                }
                else {
        //爲的i+'a'子節點的fail指針指向父節點fail指針爲i+'a'的子節點
                    fail[trie[u][i]]=trie[fail[u]][i];
                    q.push(trie[u][i]);
                }
            }
        }
    }
    int query(string str){
        int u=0,ans=0;
        for(int i=0;i<str.size();++i){
       //對於一個字母,不停地在樹上找前綴與當前已匹配部分的後綴相同的節點,但是注意u指針不變
       //當單詞被計算過或fail不存在時結束
            u=trie[u][str[i]-'a'];
            for(int j=u;j&&cntword[j]!=-1;j=fail[j]){
                ans+=cntword[j];
                cntword[j]=-1;
            }
        }
        return ans;
    }
}Test;

例題:
P3808 【模板】AC自動機(簡單版)

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+7;
struct acAutomaton{
    int trie[maxn][26],cntword[maxn],fail[maxn],tot;
    void Clear(){
        memset(trie,0,sizeof trie);
        memset(cntword,0,sizeof cntword);
        memset(fail,0,sizeof fail);
        tot=0;
    }
    //創建字典樹
    void insertWord(string str){
        int u=0;
        for(int i=0;i<str.length();++i){
            int v=str[i]-'a';
            if(trie[u][v]==0){
                trie[u][v]=++tot;
            }
            u=trie[u][v];
        }
        ++cntword[u];
    }
    void getFail(){
        queue<int> q;
        //將根節點存在的子節點扔進隊列
        for(int i=0;i<26;++i){
            if(trie[0][i]){
                fail[trie[0][i]]=0;
                q.push(trie[0][i]);
            }
        }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;++i){
                if(trie[u][i]==0){
        //當前字母沒有爲i+'a'的子節點,將其子節點指向它fail指針的爲i+'a'子節點
        //因爲當前後綴和fail的前綴相同
                    trie[u][i]=trie[fail[u]][i];
                }
                else {
        //爲的i+'a'子節點的fail指針指向父節點fail指針爲i+'a'的子節點
                    fail[trie[u][i]]=trie[fail[u]][i];
                    q.push(trie[u][i]);
                }
            }
        }
    }
    int query(string str){
        int u=0,ans=0;
        for(int i=0;i<str.size();++i){
            //對於一個字母,不停地在樹上找與其後綴相同的串
            //當單詞被計算過或fail不存在結束
            u=trie[u][str[i]-'a'];
            for(int j=u;j&&cntword[j]!=-1;j=fail[j]){
                ans+=cntword[j];
                cntword[j]=-1;
            }
        }
        return ans;
    }
}Test;
string str;
int main(){
    int n;
    cin>>n;
    Test.Clear();
    for(int i=1;i<=n;++i){
        cin>>str;
        Test.insertWord(str);
    }
    Test.getFail();
    cin>>str;
    cout<<Test.query(str)<<endl;

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章