这节课我们学习Trie字符串。这个算法的主要应用就是字符串的快速存储和查找。我们通过下面这个题来讲 Tire字符串统计 ,另外说个题外话,本人是从ACwing里学习的算法知识,希望大家支持一下y总(ACwing大佬),如果觉得我这里的知识讲得不够全面,可以去网站里付费学习。
Trie字符串统计
来分析一下这道题:
第一步,理解题意:这道题需要输入一个数字n,表示操作的数量,之后输入n行操作,操作分为两种,一种是插入操作,另一种是查询操作,查询被查询的字符串有多少个,输出数字。
第二步,暴力破解:如果暴力地来做的话,比较简单的就是无向图了,直接存入数据。
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <cstring>
using namespace std;
const int N = 100010;
int main(){
char op[2];
char str[N];
int n;
unordered_map <string,int> at;
scanf("%d",&n);
while(n --){
scanf("%s%s",op,str);
if(op[0] == 'I') {
at[str] ++;
}
else{
printf("%d\n",at[str]);
}
}
return 0;
}
还是比较简单易懂的,就是如果插入,就让值加一,如果查询,就直接输出。
第三步,算法优化:由于我们这节课主要学习的是Trie,那么我们还是用这个方法做一下。原理很简单,就是构造一个树,使每一个字符从前向后,由根节点向子节点延伸,在结束的位置做上标记,用来查询。如图:如果需要分别插入abcd,abc,acef,ccd这四个字符串,那么树应该张成这样
左侧的数字表示这个串的数量
我们看一下代码
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100010;
int son[N][26], cnt[N], idx;
char str[N];
void insert(char str[]){
int p = 0;
for(int i = 0; str[i]; i ++ ){
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
cnt[p] ++;
}
int quary(char str[]){
int p = 0;
for(int i = 0; str[i]; i ++ ){
int u = str[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
int main(){
int n;
cin >> n;
for(int i = 0 ; i < n; i ++ ){
char op[2];
scanf("%s%s",op,str);
if(op[0] == 'I') insert(str);
else printf("%d\n",quary(str));
}
return 0;
}
插入和查找都各自写了一个函数,但大体上都是一样的,我们一个一个来看。
int p = 0; 设置一个变量 p ,用来记录位置,int son[N][26], cnt[N], idx; 设置son数组,来存储Trie,cnt 存储字符串的数量,idx 用来进行枚举。char str[N]; 用来记录字符串。在插入的循环中,int u = str[i] - 'a'; 表示将第 i 个字符转换成0-26个数字中的一个,if(!son[p][u]) son[p][u] = ++idx; 如果之前没有存入这个字符串中的这个字符,那么就创建一个,(由于idx是动态增加的,所以在son中的位置也是动态改变的),p = son[p][u]; 将下一个字符定到son中一个新的位置上。 cnt[p] ++; 在最后,记录这个字符串。
在查找的过程中也是一样,只不过是 if(!son[p][u]) return 0; 中,如果没有找到这个字符,那么就返回 0,表示没有这个字符串。
如果想要深刻理解,建议自己手动模仿一下过程,如果想要速成,那么就把每句话的含义理解,会默写即可。