字典树&AC自动机(学习笔记)

字典树&AC自动机(学习笔记)

字典树中可以存储一系列字符串,当这些字符串有公共前缀时,可以快速找出。

字典树模板题

HihoCoder 1014 Trie树 题目链接

#include <iostream>
#include <cstdio>
#include <string>
#include <string.h>
using namespace std;

struct node
{   //为节省空间,不如把每个字母放在结构体里面
    bool isWord; //用于每个单词的末尾结点,表示这是一个单词
    int cnt;    //储存以每个前缀开头的单词数量
    node* nextt[26]; //链接到下一个结点,26个字母对应有26个子结点
};

void Init(node*& tree)
{
    tree = new node;
    tree->isWord=false;
    tree->cnt=0;
    for(int i=0 ; i<26 ; i++)
        tree->nextt[i]=NULL;
}

void buildtree(string s,node*& root)
{   //调用一次只添加一个字符串
    node* t=root;

    int x; //x代表26个字母各自的编号:0~a,1~b,2~c...
    for(int i=0 ; i<(int)s.length() ; i++)
    {
        x=s[i]-'a';
        if(!t->nextt[x]) //判断一下后继结点有没有当前字母
        {
            Init(t->nextt[x]); //没有的话就初始化----创建新的结点
        }
        t->nextt[x]->cnt++; //对添加字符串路径上的所有结点的计数器cnt+1
        t=t->nextt[x];  //指向后继结点
    }
    t->isWord=true; //这时就完成了一个单词的输入,单词的末尾结点标记:这是一个单词
}

int CountWord(string s,node* root) //不用修改任何结点,直接用node*即可
{   //利用前缀s,计算以s开头的单词有多少个
    node* t=root;
    int x;
    for(int i=0 ; i<(int)s.length() ; i++)
    {
        x=s[i]-'a';
        if(!t->nextt[x])
        {   //如果后继结点没有该字母,说明该前缀没有匹配成功,直接返回
            return 0;
        }
        t=t->nextt[x];
    }
    return t->cnt; //返回前缀最后一个字符处的cnt值
}

int main()
{
    int n,m; scanf("%d",&n);
    string s;
    node* tree=NULL; //创建这棵字典树的根结点
    Init(tree);
    while(n--)
    {
        cin>>s; //对于string类的输入,用scanf会出错(我也不知道为啥)
        buildtree(s,tree);
    }
    scanf("%d",&m);
    while(m--)
    {
        cin>>s;
        printf("%d\n",CountWord(s,tree)); //一直都是对这一棵树tree操作
    }
    return 0;
}

AC自动机

ac自动机是为了解决一个目标串匹配多个模式串出现的。建立在字典树之上,也用到了KMP的算法思想。
超详细讲解见这里:https://www.cnblogs.com/hyfhaha/p/10802604.html

模板题 HDU 2222 ---- Keywords Search

Problem Description
In the modern time, Search engine came into the life of everybody like Google, Baidu, etc.
Wiskey also wants to bring this feature to his image retrieval system.
Every image have a long description, when users type some keywords to find the image, the system will match the keywords with description of image and show the image which the most keywords be matched.
To simplify the problem, giving you a description of image, and some keywords, you should tell me how many keywords will be match.

Input
First line will contain one integer means how many cases will follow by.
Each case will contain two integers N means the number of keywords and N keywords follow. (N <= 10000)
Each keyword will only contains characters ‘a’-‘z’, and the length will be not longer than 50.
The last line is the description, and the length will be not longer than 1000000.

Output
Print how many keywords are contained in the description.

Sample Input

1
5
she
he
say
shr
her
yasherhs

Sample Output

3

#include <iostream>
#include <queue>
#include <string>
#include <string.h>
#include <cstdio>
using namespace std;
struct node
{
    bool isWord;
    int cnt;    //根据题意,稍微改变了一下cnt的意义
    node* nextt[26];
    node* fail; //ac自动机比字典树多了一个fail指针
};
void Init(node*& tree)
{
    tree =new node;
    tree->cnt=0;
    tree->fail=NULL;
    tree->isWord=false;
    for(int i=0 ; i<26 ; i++)
        tree->nextt[i]=NULL;
}
void buildtree(string s,node*& root)
{
    node* t=root;
    int x;
    for(int i=0 ; i<(int)s.length() ; i++)
    {
        x=s[i]-'a';
        if(!t->nextt[x])
        {
            Init(t->nextt[x]);
        }
        //t->nextt[x]->cnt++;
        t=t->nextt[x];
    }
    t->isWord=true;
    t->cnt++;
}
int CountWord(string s,node* root)
{
    node* t=root;
    int x;
    for(int i=0 ; i<(int)s.length() ; i++)
    {
        x=s[i]-'a';
        if(!t->nextt[x])
        {
            return 0;
        }
        t=t->nextt[x];
    }
    return t->cnt;
}

void GetFail(node*& root)
{   //类似于bfs,逐层设置fail指针
    queue<node*> q;
    //node* rt=root; //
    q.push(root);
    //node* t;
    node* t=NULL;

    while(!q.empty())
    {
        t=q.front();
        q.pop();
        for(int i=0 ; i<26 ; i++)
        {
            if(t->nextt[i]==NULL)
                continue;

            if(t == root) //把根结点的所有直接后继的fail指针指向根结点
                t->nextt[i]->fail=root;
            else
            {
                node* t2=t->fail; //t2储存fail指针指向的结点
                while(t2!=NULL)
                {
                    if(t2->nextt[i]==NULL) //fail所指结点的直接后继没有当前字母
                        t2=t2->fail; //继续向上推fail
                    else
                    {
                        t->nextt[i]->fail=t2->nextt[i];
                        break; //fail指针设置成功,退出循环
                    }
                }
                if(t2==NULL) //如果没有成功设置fail指针
                    t->nextt[i]->fail=root; //那么就直接让fail指向根结点
            }
            q.push(t->nextt[i]); //储存当前字母的结点入队
        }
    }
}
int work(string s,node* root)
{
    int ans=0;
    int len=s.length();
    node* t=root;
    for(int i=0 ; i<len ; i++)
    {
        while(!t->nextt[s[i]-'a'] && t!=root )
            t=t->fail; //没有匹配成功,直接从fail指针指向的结点开始匹配
        t=t->nextt[s[i]-'a']; //直到匹配成功,从后继结点开始匹配
        if(t==NULL) //如果t为空,说明fail指针最终指向了根结点
            t=root;
        node* tmp=t;
        while(tmp!=root)
        {
            if(tmp->cnt >= 0)
            {   //cnt>=0时,说明这段字符串还没有被匹配过
                ans+=tmp->cnt;
                tmp->cnt=-1;  //使用过了就赋值-1
            }
            else //被匹配过了就退出
                break;
            tmp=tmp->fail; //继续找,看看还能不能被匹配
        }
    }
    return ans;
}
/*
void deletetree(node*& tree)
{
    for(int i=0 ; i<26 ; i++)
    {
        if(!tree->nextt[i]) //??????
            deletetree(tree->nextt[i]);
    }
    delete tree;
}
*/
int main()
{
    int m,n;
    string s;

    scanf("%d",&m);
    while(m--)
    {
        scanf("%d",&n);
        node* tree=NULL;
        Init(tree); //忘记初始化了...
        while(n--)
        {
            cin >> s; //记住,要用cin输入string类字符串,并且要加头文件<string>
            buildtree(s,tree);
        }
        GetFail(tree);
        cin >> s;
        printf("%d\n",work(s,tree));
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值