引言
考虑这样的问题:
给出模式串S1,S2,S3…Sn 和文本串txt , 问txt是多少个模式串的前缀
egs:
S1: fusufusu
S2: fusualex
S3: anmikusu
txt: fusu
ans = 2 (txt是S1,S2的一个前缀)
如果我们是暴力匹配每一个模式串和文本串,那么时间复杂度为O(n * m * k)(稍微表示一下?)
那么有无快一点的法子呢?Tire 字典树
字典树
字典树是什么
这里说的树不是那个令人头疼用指针搞出来的树,只是二维数组被抽象成树,请放心观看 (
在算法中通常是以英文字母作为字符串的元素, 如果考虑单个模式串,那么将它化成一棵树的样子会是什么亚子呢?(以fusufusu)为例子
是的,长得有点抽象了,但是我们确实叫他树:)。接下来我们加入其他模式串
貌似还是很抽象,不管了!
我们会对每一个节点进行编号,每个节点会有26个字母个子节点,那么就会构成一个二维数组 tire[maxn][26]; 这就是字典树了
字典树构建
目前只有一个根节点:
现在假设要插入eng , 且有一个p指针指向root节点(这里的指针不是c语言中的指针,只是一个箭头而已)
当前指针p没有指向e的子节点,那么创建一个新的节点,值赋值为e,修改p指向新节点
接下来的ng同样的过程建立
当前遍历到了eng模式串的结尾,那么ed[p]++(前文说了ed数组统计模式串结尾个数)
现在插入一个新的模式串enh
同样p指向root,发现当前指向节点具有子节点,那么p直接指向e,同样的n也是如此
但是之后p不具备h这个新节点,那么新建节点同时赋值为h,p移动到新节点
同时ed[p]++;
代码实现:
首先声明需要的变量:
- tire[maxn][26] 字典树储存
- cnt 记录除根节点外,节点总数
- ed[maxn] 记录以i结尾的模式串的个数ed[i]
void insert(char str[]) { //插入模式串(构造字典树)
int p = 0; //p为当前所在节点位置
int len = strlen(str);
for(int i=0;i<len;i++) {
if( !tire[p][str[i]-'a'] ) { //当前节点的str[i]子节点不存在
tire[p][str[i]-'a'] = ++cnt; //创建新节点
clear(cnt); //初始化该节点
}
p = tire[p][str[i]-'a']; //当前节点移动到下一位
}
ed[p]++;
}
void clear(int pos){ //节点初始化
for(int i=0;i<26;i++){
tire[pos][i] = 0;
}
}
str[i] - ‘a’ 条件是模式串只包含小写字母,而字母的编码是按顺序且‘a’编号最小
字典树query
如果只是查询文本串是否在众多模式串中出现过,那么很简单,只需要拿着文本串在字典树中匹配一遍,看看能不能匹配到文本串结尾
bool query(char txt[]){ //查询文本串是否在模式串中
int p = 0; //p指针
int len = strlen(txt);
for(int i = 0;i < len;i++){
if( tire[p][ txt[i]-'a' ] ){ //p有txt[i]这个子节点
p = tire[p][ txt[i]-'a' ]
} else{ //没有, 直接返回false
return false;
}
}
return true;
}
引言中的问题是,文本串是多少个模式串中前缀,那么 需要加入一个新的数组countNum[maxn] ,作用和ed数组类似,count数组记录的是有多少个模式串经过了当前节点
那么inster函数稍微修改一下:
void insert(char str[]) { //插入模式串(构造字典树)
int p = 0; //pos为当前所在节点位置
int len = strlen(str);
for(int i=0;i<len;i++) {
if(!tire[p][str[i]-'a']) { //当前节点的str[i]子节点不存在
tire[p][str[i]-'a'] = ++cnt; //创建新节点
clear(cnt); //初始化该节点
}
p = tire[p][str[i]-'a']; //当前节点移动到下一位
countNum[p]++; //记录多少个模式串经过
}
ed[p]++;
}
上述query函数也需修改:
int query(char txt[]){ //查询文本串是否在模式串中
int p = 0; //p指针
int len = strlen(txt);
for(int i = 0;i < len;i++){
if( tire[p][ txt[i]-'a' ] ){ //p有txt[i]这个子节点
p = tire[p][ txt[i]-'a' ];
} else{ //没有, 直接返回0
return 0;
}
}
return countNum[p];
}
end
练习题地址:洛谷-字典树模板(注意这题含大小写字母和数字)
字典树是很基础的数据结构了,基本不需要长篇大论就可以讲清楚,如果文章有错误或者不足之处请指出
另外,字典树+KMP(思想)可以引出一个新的算法:AC自动机
用于解决多模式串匹配问题
自我引流一下:
浅谈KMP
AC自动机+拓扑优化
感谢看到最后 😃😃