多模式匹配——Trie树

题目:给一个字符串S和一个字符串数组T(T中的字符串要比S短许多),设计一个算法,在字符串S中查找T中的字符串。
思路: 字符串的多模式匹配问题。

我们把S称为目标串,T中的字符串称为模式串。设目标串S的长度为m,模式串的平均长度为 n,共有k个模式串。如果我们用KMP算法(或BM算法)去处理每个模式串,判断模式串是否在目标串中出现,匹配一个模式串和目标串的时间为O(m+n),所以总时间复杂度为:O(k(m+n))。一般实际应用中,目标串往往是一段文本,一篇文章,甚至是一个基因库,而模式串则是一些较短的字符串,也就是m一般要远大于n。这时候如果我们要匹配的模式串非常多(即k非常大),那么我们使用上述算法就会非常慢。这也是为什么KMP或BM一般只用于单模式匹配,而不用于多模式匹配。

那么有哪些算法可以解决多模式匹配问题呢?貌似还挺多的,Trie树,AC自动机,WM算法,后缀树等等。我们先从简单的Trie树入手来解决这个问题。

Trie树,又称为字典树,单词查找树或前缀树,是一种用于快速检索的多叉树结构。比如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。

回到我们的题目,现在要在字符串S中查找T中的字符串是否出现(或查找它们出现的位置),这要怎么和Trie扯上关系呢?

我们发现,如果一个串t是S的子串,那么t一定是S某个后缀的前缀。比如t = bc,那么它是后缀bcd的前缀;又比如说t = c,那么它是后缀cd的前缀。

因此,我们只需要将字符串S的所有后缀构成一棵Trie树(后缀Trie),然后查询模式串是否在该Trie树中出现即可。如果模式串t的长度为n,那么我们从根结点向下匹配,可以用O(n)的时间得出t是否为S的子串。

代码:

#include 
#include 
using namespace std;

class Trie{
public:
    static const int MAX_N = 100 * 100;// 100为主串长度
    static const int CLD_NUM = 26; // 每个结点的儿子数量(26个字母)
    int size; // 用到的当前结点编号
    int trie[MAX_N][CLD_NUM];

    Trie(const char* s);
    void insert(const char* s);
    bool find(const char* s);
};

Trie::Trie(const char* s){
    memset(trie[0], -1, sizeof(trie[0]));
    size = 1;
    while(*s){
        insert(s);
        ++s;
    }
}

void Trie::insert(const char* s){
    int p = 0;
    while(*s){
        int i = *s - 'a';
        if(-1 == trie[p][i]){
            memset(trie[size], -1, sizeof(trie[size]));
            trie[p][i] = size++;
        }
        p = trie[p][i];
        ++s;
    }
}

bool Trie::find(const char* s){
    int p = 0;
    while(*s){
        int i = *s - 'a';
        if(-1 == trie[p][i])
            return false;
        p = trie[p][i];
        ++s;
    }
    return true;
}

int main(){
    Trie tree("mississippi");
    string patt[] = {
        "is", "sip", "hi", "sis", "mississippa"
    };
    int n = 5;
    for(int i=0; i<n; ++i)
        cout<<tree.find((char*)&patt[i][0])<<endl;
    return 0;
}
后缀Trie的查找效率很优秀,如果你要查找一个长度为n的字符串,只需要O(n)的时间,比较次数就是字符串的长度,相当给力。但是,构造字符串S的后缀Trie却需要O(m2 )的时间, (m为S的长度),及O(m2 )的空间。

你可能感兴趣的:(多模式匹配——Trie树)