index:
今天学了 t r i e trie trie树,然后就想来写一写,不然等会模板都记不得了。
操作:
插入字符串:
当需要插入一个字符串S时,我们令一个指针P起初指向根节点
(可以理解为以根节点为起点,做好扫描准备),
然后依次遍历S中的每一个字符c:
1、若P的c字符指针指向一个已经存在的节点Q,则令P=Q。
2、若P的c字符指针指向空,则新建一个节点Q,令P的c字符指针指向Q,然后令P=Q。
当S中的字符扫描完毕时,在当前节点P上标记它是一个字符串的末尾
标记末尾是因为判断是不是为一个完整的单词而不只是一个单词的前缀
检索字符串:
当需要检索一个字符串S在Trie中是否存在时,
我们令一个指针P起初指向根节点,然后依次遍历S中的每个字符c:
1、若P的c字符指针指向空,则说明S没有被插入过Trie中,return 0;结束检索。
2、若P的c字符指针指向一个已经存在的节点Q,则令P=Q。
当S中的字符扫描完毕时,若当前节点P被标记为一个字符串的末尾,
则说明S在Trie中存在,
否则说明S没有被插入过Trie
(我们可以在插入操作结束时标记结束的节点为end[P]=true,
检索时S被遍历完时返回return end[P],若是true则这个串插入过,
若是false则这个串只是包含于比它更长的串中)。
模板:
直接放了吧。主要分为两个板块 :
1.插入
2.检索
插入:
template <typename T>
void Insert(T *c) { //插入一个字符串
int len = strlen(c);
int P = 1;// P = 1表示从1号节点开始
for(int j = 0;j < len;j ++) {
int root = c[j] - 'a';
if(!trie[P][root]) {//如果没有这条边
trie[P][root] = ++ cnt;//加入这条边并编号
}
P = trie[P][root];//将用到的这条边指向下一个节点
}
flag[P] = true;//尾节点标记形成一个完整的单词
}
检索:
template <typename T>
bool find(T *c) {//检索一个字符串
int len = strlen(c);
int P = 1;//P = 1表示从1号节点开始
for(int j = 0;j < len;j ++) {
P = trie[P][c[j] - 'a'];
if(!P) {//如果这条边不存在就停止检索
return false;
}
}//查询的字符转都遍历完了,如果flag[P]为1,则有一个完整的字符串。如果flag[P]为0则查询的字符串仅仅是trie树中的前缀
return flag[P];
}
T1:前缀统计
题目描述:
给定N个字符串S1,S2...SN,接下来进行M次询问,
每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。
输入字符串的总长度不超过10^6,仅包含小写字母。
输入:
第一行两个整数N,M。接下来N行每行一个字符串Si。接下来M行每行一个字符串表示询问。
输出:
对于每个询问,输出一个整数表示答案
思路:
注意,已经知道了总长度,所以这个就是 t r i e trie trie的第一维的范围。
把这N个字符串插入一棵Trie树中,
Trie树的每个节点上存储一个整数tot,记录该节点是多少个字符串的末尾节点。
(有可能插入重复的字符串,所以尾节点不能只用bool做标记,这里要记录个数。)
对于每个询问,在Trie树中检索T,
在检索过程中累加途径中的每个节点的tot值,就是该询问的答案。
code:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int range = 100005;
int cnt = 1, n;
int trie[range * 26][26];
int flag[range];
template <typename T>
void Insert(T *c) {
int len = strlen(c);
int P = 0;
for(int j = 0;j < len;j ++) {
int root = c[j] - 'a';
if(!trie[P][root]) {
trie[P][root] = ++ cnt;
}
P = trie[P][root];
}
flag[P] ++;
}
template <typename T>
int find(T *c) {
int len = strlen(c);
int P = 0, sum = 0;
for(int j = 0;j < len;j ++) {
P = trie[P][c[j] - 'a'];
if(!P) {
return sum;
}
sum += flag[P];
}
return sum;
}
int main() {
char c[range];
int n, m;
scanf("%d %d",&n, &m);
for(int i = 0;i < n;i ++){
scanf("%s", c);
Insert(c);
}
while(m --){
scanf("%s", c);
printf("%d\n", find(c));
}
return 0;
}
总结:
1.可以看出在Trie中,字符数据都体现在树的边(指针)上,
2.树的节点仅保存一些额外信息,例如字符串结尾标记等。
3.其空间复杂度是O(N*C),N为节点个数,C为字符集的大小。
4.其时间复杂度是O(T*len),T为串的个数,len为串的长度。
5.在定义的的 t r i e trie trie数组中第一维是 r o o t root root根节点,第二维是有多少条边。