字符串匹配算法(AC自动机 Aho-Corasick)

1. 多模式串匹配

  • 前面学的BF、RK、BM、KMP都是单模式串匹配算法(一个模式串,一个主串)
  • 多模式串匹配,即在一个主串中查找多个模式串(Trie树是多模式匹配)
  • 比如实现多个敏感词过滤;单模式需要一遍遍的,扫描,过滤,扫描,过滤;多模式扫描一遍,过滤完成

2. 经典多模式串匹配–AC自动机

AC自动机算法(Aho-Corasick算法),是在Trie树之上,加了类似 KMP 的 next 数组。

class ACNode    //AC自动机的Trie树节点类,假设只有26个字母的数据集
{
public:
    char data;
    ACNode *children[charNum];
    size_t count;   //记录这个节点被多少个单词占用
    bool isEndOfWord;//是否是一个单词的结束字符
    size_t freq;    //单词插入的频次
    int length;     //当isEndOFWord为True时,记录模式串长度
    ACNode *fail; //失败指针
    ACNode(char ch = '/'):data(ch), isEndOfWord(false),
                            count(0), freq(0),length(-1),fail(NULL)
    {
        memset(children,0,sizeof(ACNode*) * charNum);
    }
    ~ACNode(){}
};

2.1 AC自动机构建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void buildFailPointer()
{
    queue<ACNode*> ACNode_queue;
    ACNode_queue.push(root);
    ACNode *p, *pchild, *q, *qchild;
    int i;
    while(!ACNode_queue.empty())//用队列按层遍历
    {
        p = ACNode_queue.front();//队首的节点p
        ACNode_queue.pop();
        for(i = 0; i < charNum; ++i)
        {
            pchild = p->children[i];//找到p的非空子节点pc
            if(pchild == NULL)
                continue;
            if(p == root)
                pchild->fail = root;
            else
            {
                q = p->fail;    //q为p的失效指针
                while(q != NULL)    //q不为空
                {
                    qchild = q->children[pchild->data-'a'];//字符等于pc的qc
                    if(qchild != NULL)//qc存在
                    {
                        pchild->fail = qchild;//链接pc失败指针到qc
                        break;//找到了就跳出循环
                    }
                    q = q->fail;//qc不存在,就再去上一个失效点找
                }
                if(q == NULL)//最后找到root处还没找到
                    pchild->fail = root;//pc的失效指针指向root
            }
            ACNode_queue.push(pchild);//把p的非空子节点pc入队
        }
    }
}

在这里插入图片描述

2.2 在AC自动机上匹配主串

void match(const string &maintext)  //maintext是主串
{
    int n = maintext.size();
    ACNode *p = root, *temp;//模式串从root开始
    int index, pos;
    for(int i = 0; i < n; ++i)//主串从i=0开始
    {
        index = maintext[i]-'a';//子节点下标
        while(p->children[index] == NULL && p != root)
        {//p不为root,且 子节点为空(找不到那个i对应的字符)
            p = p->fail;    //失败指针发挥作用的地方
        }
        p = p->children[index];
        if(p == NULL)
            p = root;   //如果没有匹配的,从root开始重新匹配
        temp = p;
        while(temp != root)//打印出可以匹配的模式串
        {
            if(temp->isEndOfWord == true)
            {
                pos = i-temp->length+1;
                cout << "Found " << maintext.substr(pos,temp->length) << " at "
                     "position(start from 0) "<< pos << " at " << maintext << endl;
            }
            temp = temp->fail;
        }
    }
}

主程序

Trie textlib;
string a("abcd"), b("bcd"), c("c");
textlib.insert(a);
textlib.insert(a);
textlib.insert(b);
textlib.insert(c);
textlib.buildFailPointer();
textlib.match("abcdc");

在这里插入图片描述
在Trie树基础上的AC自动机完整代码(请点击查看)

2.3 复杂度分析

  • 构建AC自动机
  1. 构建Trie树,时间复杂度O(m*len),其中len表示敏感词平均长度,m 表示敏感词个数
  2. 构建失败指针,每个节点构建失败指针不会超过len次(树的平均高度),整个失败指针就是O(k*len), k 是节点个数
  • 匹配复杂度
    for循环依次遍历主串中每个字符,for循环内部的while复杂度O(len),总的复杂度O(n*len),敏感词不会很长,所以近似于O(n)
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页