Trie(字典树、前缀树)是一种用于高效存储和检索字符串的数据结构。
高效的前缀查询:能够快速判断一个字符串的前缀是否存在,以及查找具有特定前缀的所有字符串。
节省空间:对于有共同前缀的字符串,只存储共同前缀部分一次,避免了重复存储。
插入和查找的时间复杂度通常为 O (m),其中 m 是要插入或查找的字符串的长度。
Trie 由节点组成,每个节点可能有多个子节点,通常用数组或哈希表来表示子节点的链接。每个节点还可能有一个标志位,表示是否为一个字符串的结束位置。
例如,对于字符串 "apple"、"app"、"banana" 构建的 Trie 如下:
root
/ \
a b
/ \
p a
/ \ /
p l n
/ \ /
e n a
/
n
自动补全和搜索建议。
字符串匹配和模式匹配问题。
词频统计。
维护一个字符串集合,支持两种操作:
I x
向集合中插入一个字符串 xx;Q x
询问一个字符串在集合中出现了多少次。共有 NN 个操作,所有输入的字符串总长度不超过 105105,字符串仅包含小写英文字母。
第一行包含整数 NN,表示操作数。
接下来 NN 行,每行包含一个操作指令,指令为 I x
或 Q x
中的一种。
对于每个询问指令 Q x
,都要输出一个整数作为结果,表示 xx 在集合中出现的次数。
每个结果占一行。
1≤N≤2∗1041≤N≤2∗104
5
I abc
Q abc
Q ab
I ab
Q ab
1
0
1
比较好的课AcWing 835. Trie字符串统计 - AcWing
#include
using namespace std;
const int N=100010;
int son[N][26],cnt[N],idx;
//cnt[N]表示结尾节点,idx为节点分配索引
char str[N];
void insert(char *str){
//void insert(char str[])
int p=0;//根节点
for(int i=0;str[i];i++){
//【重点】c++中str[]的结尾是/0,所以到结尾也停止循环了
int u=str[i]-'a';
//将a到z映射成0-25
if(!son[p][u])son[p][u]=++idx;
//创建不存在的节点
p=son[p][u];
}
cnt[p]++;//创建结尾节点
}
int query(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;
scanf("%d",&n);
while(n--){
char op[2];
scanf("%s%s",op,str);
if(*op=='I')insert(str);
else printf("%d\n",query(str));
}
return 0;
}
//来自y总
int son[N][26], cnt[N], idx;
char str[N];
cnt[N]表示结尾节点
idx为节点分配索引
void insert(char *str){
//void insert(char str[])
int p=0;//根节点
for(int i=0;str[i];i++){
//【重点】c++中str[]的结尾是/0,所以到结尾也停止循环了
int u=str[i]-'a';
//将a到z映射成0-25
if(!son[p][u])son[p][u]=++idx;
//创建不存在的节点
p=son[p][u];
}
cnt[p]++;//创建结尾节点
}
p
为根节点(索引为 0)。'a'
的差值得到对应字母的索引 u
。cnt[p]
【重点】
c++中str[]的结尾是/0,所以到结尾也停止循环了
【变形】
str[N]
void insert(char *str){}
可以变为
void insert(char str[])
在 C 和 C++ 中,void insert(char *str)
和 void insert(char str[])
这两种形式在功能上是完全等价的,之所以会存在这两种形式,主要有以下原因:
对于 void insert(char *str)
这种形式,使用指针来表示数组是 C 语言中常见的方式。指针能够更直接地表达指向一段连续内存的概念,这与数组在内存中的存储方式相契合。
而 void insert(char str[])
这种形式,看起来更像是在直接传递一个数组。它在语法上更直观,对于一些初学者来说,可能更容易理解为在传递一个数组给函数。
然而,需要注意的是,无论使用哪种形式,在函数内部对参数的处理方式是相同的,都是通过指针来访问数组的元素。并且在传递参数时,实际上传递的是数组的首地址,而不是整个数组的拷贝。这样做可以节省内存和提高函数调用的效率,因为不需要复制整个数组的数据。
总的来说,这两种形式的存在主要是为了满足不同开发者的编程习惯和对语法的理解偏好,在实际功能上没有差异。
int query(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;
scanf("%d",&n);
while(n--){
char op[2];
scanf("%s%s",op,str);
if(*op=='I')insert(str);
else printf("%d\n",query(str));
}
return 0;
}
n
,表示操作的次数。n
次,每次读取一个操作字符 op
和一个字符串 str
。'I'
,调用 insert
函数插入字符串;否则调用 query
函数查询字符串出现的次数并输出。