【hihoCoder】1036 Trie图

题目:http://hihocoder.com/problemset/problem/1036

给一个词典dict,词典中包含了一些单词words。要求判断给定的一个文本串text中是否包含这个字典中的单词words。

相关基础的理解

1. 与用KMP解决的问题的差别

KMP:输入原串S和一个模式串T,判断T是否出现在S中。通过对T计算next数组,避免原串S的回溯。

现在的问题:输入文本串text和多个单词words,判断words中是否有出现在text中。同样希望输入的text不用进行回溯。那么解决方案?

2. trie树识别

把所有的单词构建成trie树,输入ab的识别过程如下:每个节点会通过a/b/c到达下一个节点,当识别到终点(单词的最后一个字母,图中用两个圆表示)时说明整个单词都匹配成功

以上面的trie树为例,如果输入的text串为 ac

顺序 1 2 3 4
路径 1->2 2->3   1-4
过程说明 a-a匹配成功 c-b失配 text在c处失配,下一个用c再去匹配(不回溯) c-c匹配成功

3. trie图(最关键的)

当匹配完成text的前面一部分时(即text‘),text‘有多个后缀串(不包含text的第一个字母的子串),trie树中可能包含多个text‘的后缀。当发生失配时,应该去往这些后缀中最长的一个。

1)最长后缀: text串匹配到3位置(babd)时发生失配(d和e),由3到5( ab-babd)

2)后缀指针:指向它的最长后缀的位置。在trie树中,为每个点添加后缀指针即可构成trie图

3)计算后缀指针:已知父节点的后缀节点,父节点经过字母ch到达子节点。那么子节点的后缀指针应该指向父节点的后缀节点经过当前字母到达的节点(按下图的顺序查找子节点后缀),直到根节点(根节点的后缀为自己)

例:虚线指向的是后缀。当前点为4,4的父节点为3(3-4间为e),3的后缀节点为5,5经过e到达7,所以3的后缀节点为7

 源码

代码说明:

1. 节点的结构体中有next[26],表明经过26个字母分别可达的节点

2. 在为每个节点添加后缀指针时,有两种情况:

1)节点经过next[i]有可达位置(子节点):将子节点放入队列(需要计算后缀);计算子节点的后缀=当前点后缀的next[i];

2)节点经过next[i]无可达位置:当前点的next[i]=当前点后缀点的next[i] 避免计算过程中不断地回溯寻找后缀节点

 1 #include <iostream>
 2 #include <queue>
 3 #include <string>
 4 using namespace std;
 5
 6 static int nodeCnt = 0;
 7 struct Node
 8 {
 9     Node()
10     {
11         post = 0;
12         for(int i = 0; i < 26; i++)
13             next[i] = 0;
14         post = 0;
15         end = false;
16     }
17     int post;//后缀点的位置
18     int next[26];//经过26个字母的下一个可达的位置
19     bool end;//是否为终点
20 }nodes[1000000];//最多有1000000个点
21
22 //将str添加到trie图中
23 void build(string str)
24 {
25     int strLen = str.length(), i = 0, current = 0;
26     while(i < strLen)
27     {
28         if(nodes[current].next[str[i]-‘a‘] == 0)//若当前点经过str[i]可达的位置未设置
29             nodes[current].next[str[i]-‘a‘] = ++nodeCnt;
30         current = nodes[current].next[str[i]-‘a‘];
31         i++;
32     }
33     nodes[current].end = true;
34 }
35 //为trie图中的每个点添加它指向的后缀点位置
36 void addPost()
37 {
38     queue<int> _int_nodes;
39     _int_nodes.push(0);
40     int current;
41     while(!_int_nodes.empty())
42     {
43         current = _int_nodes.front();
44         _int_nodes.pop();
45         for(int i = 0; i < 26; i++)
46         {
47             if(nodes[current].next[i] != 0)
48             {
49                 _int_nodes.push(nodes[current].next[i]);
50                 if(current != 0)//不是根结点,需要设置当前点的子节点的后缀=父结点的后缀经过i到达的点
51                     nodes[nodes[current].next[i]].post = nodes[nodes[current].post].next[i];
52             }
53             else //nodes[current].next[i] == -1当前点经过i没有可达的
54                 nodes[current].next[i] = nodes[nodes[current].post].next[i];
55         }
56     }
57 }
58
59 //查找str
60 bool search(string str)
61 {
62     int strLen = str.length(), i = 0, current = 0;
63     while( i < strLen)
64     {
65         if(nodes[nodes[current].next[str[i]-‘a‘]].end)
66             return true;
67         current = nodes[current].next[str[i]-‘a‘];
68         i++;
69     }
70     return false;
71 }
72 int main()
73 {
74     int cnt;
75     string str;
76     cin>>cnt;
77     while(cnt-- > 0)
78     {
79         cin>>str;
80         build(str);
81     }
82     addPost();
83     cin>>str;
84     cout<<(search(str)?"YES":"NO")<<endl;
85     return 0;
86 }

时间: 2024-10-26 02:45:07

【hihoCoder】1036 Trie图的相关文章

hihoCoder #1036 : Trie图 (AC自动机)

#1036 : Trie图 时间限制:20000ms 单点时限:1000ms 内存限制:512MB 描述 前情回顾 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:"枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功."这样非常朴素的想法,但是这样的算法时间复杂度是相当

hihocoder 1036 Trie图(AC自动机)

传送门 Description 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:“枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功.”这样非常朴素的想法,但是这样的算法时间复杂度是相当高的,如果说词典的词语数量为N,每个词语长度为L,文章的长度为M,那么需要进行的计算次数是在N

hihoCoder#1036 Trie图

原题地址 看了这篇博文,总算是把Trie图弄明白了 Runtime Error了无数次,一直不知道为什么,于是写了个脚本生成了一组大数据,发现果然段错误了. 调试了一下午,总算闹明白了,为什么呢? 1. 空间超大的变量不要放在函数里,会爆栈,应该弄成全局变量或者是从堆上动态分配. 2. 看清题目的数据范围,一开始我的MAX_NODE设的是1024... 代码: 1 #include <iostream> 2 #include <cstring> 3 4 using namespac

hiho一下 第二周&amp;第四周:从Trie树到Trie图

hihocoder #1014 题目地址:http://hihocoder.com/problemset/problem/1014 hihocoder #1036 题目地址: http://hihocoder.com/problemset/problem/1036 trie图其实就是trie树+KMP #1014trie树 #include<stdio.h> #include <algorithm> #include <cstring> #include <str

Trie图 &amp; AC自动机初学(1)

题目来源于:Hihocoder 时间限制:20000ms 单点时限:1000ms 内存限制:512MB 描述 前情回顾 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:"枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功."这样非常朴素的想法,但是这样的算法时间复杂度是

Trie 图

时间限制:20000ms 单点时限:1000ms 内存限制:512MB 描述 前情回顾 上回说到,小Hi和小Ho接受到了河蟹先生伟大而光荣的任务:河蟹先生将要给与他们一篇从互联网上收集来的文章,和一本厚厚的河蟹词典,而他们要做的是判断这篇文章中是否存在那些属于河蟹词典中的词语. 当时,小Hi和小Ho的水平还是十分有限,他们只能够想到:“枚举每一个单词,然后枚举文章中可能的起始位置,然后进行匹配,看能否成功.”这样非常朴素的想法,但是这样的算法时间复杂度是相当高的,如果说词典的词语数量为N,每个词

Trie图

DFA 确定性有限状态自动机 DFA确定性有限状态自动机是一种图结构的数据结构,可以由(Q, q0, A, Sigma, Delta)来描述,其中Q为状态集,q0为初始状态,A为终态集合,Sigma为字母表,Delta为转移函数.它表示从唯一一个起始状态q0开始,经过有限步的Delta转移,转移是根据字母表Sigma中的元素来进行,最终到达终态集合A中的某个状态的状态移动.  如图所示是一个终态集合为{"nano"}的DFA.     DFA只能有一个起点而可以有多个终点.每个节点都有

hdu2457 Trie图+dp

hdu2457 给定n个模式串, 和一个文本串 问如果修改最少的字符串使得文本串不包含模式串, 输出最少的次数,如果不能修改成功,则输出-1 dp[i][j] 表示长度为i的字符串, 到达状态j(Trie图中的结点)所需要修改的最少次数 那么dp[0->n][0->size] = INF ,  dp[0][root] = 0,  n代表字符串长度, size代表状态数 那么答案就是  min{dp[n][size]} 我们根据模式串所建的Trie图, 进行模拟构造不包含模式串的字符串 从第一个

[hiho 04]Trie图

题目描述 Trie 图就是在 Trie 树上建立 fail 指针,类似于KMP算法中的next数组的作用. 这个数据结构的作用是判断一个字符串中是否包含一组字符串中的任意一个. 结构体定义是这样的: typedef struct trie_node { trie_node *nodes[26]; trie_node *fail = NULL; bool word_end = false; trie_node() { for (int i = 0; i < 26; i++) { nodes[i]