您的位置:首页 > 其它

[模板]-AC自动机

2017-09-08 22:27 393 查看
问题描述:

给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

代码:

char t[1000010];//模式串
char s[1000010];//主串
struct Trie//字典树中的一个结点
{
Trie* fail;//失配指针
Trie* next[26];//该结点的儿子,最多有26个
int sum;//以该结点所代表的字母结尾的单词数
//    Trie()//初始化
//    {
//        fail = NULL;
//        memset(next,NULL,sizeof(next));
//        sum = 0;
//    }
};
Trie* root;
void init(Trie* node)//初始化函数
{
node -> fail = NULL;
for(int i = 0; i <= 25; ++i)
node -> next[i] = NULL;
node -> sum =0;
}
void BuildTree(char t[])//建树
{
Trie* p = root;//从根结点开始
Trie* q;
int len = strlen(t);//模式串t的长度
for(int i = 0; i <= len - 1; ++i)
{
int index = t[i] - 'a';//索引,a对应0,b对应1,以此类推
if(p -> next[index] == NULL)//如果没有边
{
q = (Trie*)malloc(sizeof(Trie));
init(q);
p -> next[index] = q;//就新建一个结点,产生一条边,新结点代表该字母
}
p = p -> next[index];//继续向下
}
p->sum++;//单词最后一个结点的sum加1
}
queue<Trie*> q;//队列,用于求失配指针
void GetFail(Trie* root)//广度优先搜索求失配指针
{
q.push(root);//首先将root加入队列
while(!q.empty())
{
Trie *t = q.front(),*p;//访问队首
q.pop();//弹出队首
for(int i = 0; i <= 25; ++
4000
i)
{
if(t -> next[i] != NULL)//如果t的该儿子结点存在
{
if(t == root)//如果t -> next[i]是第一层中的结点,就将其失配指针指向root
t ->next[i] ->fail = root;
else
{
p = t -> fail;//将父节点t的失配指针赋给扫描指针p
while(p != NULL)//扫描指针不为空(根结点的失配指针)时循环
{
if(p -> next[i] != NULL)//如果扫描指针p的儿子结点p -> next[i]不为空,即t -> next[i]与p -> next[i]相同
{
t -> next[i] -> fail = p -> next[i];//将t的儿子结点t -> next[i]的失配指针指向p -> next[i]
break;//跳出循环
}
p = p -> fail;//否则继续回溯
}
if(p == NULL)//如果p最终为root结点的fail指针(即NULL)
t -> next[i] -> fail = root;//将t -> next[i]的失配指针指向root
}
q.push(t -> next[i]);
}
}
}
}
int Aho_Corasick_Automaton(Trie* root)//匹配
{
Trie* p = root;//p为模式串指针
int len = strlen(s);//主串的长度
int cnt = 0;//计数
for(int i = 0; i <= len - 1; ++i)//i为主串指针
{
int index = s[i] - 'a';//索引
while(p -> next[index] == NULL && p != root)//当p -> next[index]为空或p不指向root时进行循环
p = p -> fail;//失配指针回溯
p = p -> next[index];//循环结束后将p指向p -> next[index]
if(p == NULL)//如果p为空,说明与s[i]匹配的字符不存在
p = root;
Trie* t = p;//指向p -> next[index]后,沿其失败指针回溯,判断其它结点是否与s[i]匹配
while(t != root)//如果t不指向root,否则不会进行循环
{
if(t->sum >= 0)//判断该结点是否被访问过
{
cnt += t -> sum;//累加
t -> sum = -1;//标记为已访问过
}
else//结点被访问过
break;//结束循环
t = t -> fail;//回溯失配指针,继续寻找下一个满足条件的结点
}
}
return cnt;
}
int main()
{
int n;//模式串个数
cin >> n;
root = (Trie*)malloc(sizeof(Trie));//根
init(root);
for(int i = 1; i <= n; ++i)
{
scanf("\n%s",t);//输入模式串
BuildTree(t);//建立字典树Trie
}
scanf("%s",s);//输入主串
GetFail(root);//获得失配指针
printf("%d\n",Aho_Corasick_Automaton(root));//打印答案
}


解决方法:小心使用未被初始化的变量。

Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串)

根节点(Trie的入口,没有实际含义)不包含字符,除根节点外每一个节点都只包含一个字符

AC自动机优化后可称Trie图
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: