概念:字典树(TrieTree),是一种树形结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串,如01字典树)。主要思想是利用字符串的公共前缀来节约存储空间。很好地利用了串的公共前缀,节约了存储空间。字典树主要包含两种操作,插入和查找。
比如,我们要怎么用树存下单词"abc",“abb”,“bca”,"bc"呢?见图
在图中,红点代表有一个以此节点为终点的单词。然后,我们如果要查找某个单词如s=“abc”,就可以这样:
在这里,s=“abc” 的每一个字母都在树中被查到了,并且最后一个点是红色代表有一个在此结束的单词,查询成功。而 s=“bb” 的第二个字母没有在相应位置被查到,因此"bb"不在字典中。至于s=“ab” 虽然单词中每个点都被查到了,但是由于结尾的字母在树中没有标红,因此也是不在字典中。
对于字典树的每次查找,时间复杂度为log级别,比暴力快多了。
字典树主要是可以替代哈希表,完成一些工作。字典树更快。
字典树的链表实现很简单,就是简单的模拟,这里就不去写了。
字典树的数组实现的模板:(这也是最常用的)
//trie字典树
#define N 100010
//son表示指向孩子结点的位置,cnt表示标记的字符串的结尾,idx表示用到的位置
int son[N][26], cnt[N], idx;
//插入
void insert(char str[])
{
int p = 0;
for (int i = 0; str[i]; i++)//因为字符串是以\0结尾
{
int u = str[i] - 'a';//获取放入的列数
if (!son[p][u])son[p][u] = ++idx;//走完和其他字符串相同的地方
p = son[p][u]; //再让p指向下一个行
}
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]; //判断是否有这个结尾的字符串
}
例题:(这种数据结构题,我学习的时候一般只写三道题,以后就是重复的练习)
#include<bits/stdc++.h>
#include<string>
using namespace std;
int idx;
int son[3000005][65];
long long cnt[3000005];
char s[3000005];
int getnum(char x){
if(x>='A'&&x<='Z')
return x-'A';
else if(x>='a'&&x<='z')
return x-'a'+26;
else
return x-'0'+52;
}
void insert(char str[]){
int root = 1,len = strlen(str);
for(int i=0;i<len;i++){
int a = getnum(str[i]);
if(!son[root][a]) son[root][a] = ++idx;
root = son[root][a];
cnt[root]++;
}
}
int search(char str[]){
int root = 1,len = strlen(str);
for(int i=0;i<len;i++){
int a = getnum(str[i]);
if(!son[root][a]) return 0;
root = son[root][a];
}
return cnt[root];
}
void snowball(){
int s_num,t_num;
scanf("%d%d", &s_num, &t_num);
for(int i = 0;i < s_num;i++) scanf("%s",s),insert(s);
for(int i = 0;i < t_num;i++) scanf("%s",s),printf("%d\n",search(s));
}
int main() {
int n;
scanf("%d",&n);
for(int i=0; i<n; i++) {
for(int k = 1;k <=idx;k++) {
cnt[k] = 0;
for(int j = 0;j < 70;j++) son[k][j] = 0;
}
idx = 1;
snowball();
}
return 0;
}
超时了呜呜呜~~~
#include<bits/stdc++.h>
using namespace std;
#define N 500005
//son存当前点的所有儿子,cnt表示以当前点结尾的单词的个数,idx指向当前结点
//下标是0 的点,既是根结点,又是空结点
int son[N][52];
int cnt[N];
int f[N];
int temp[N];
int idx = 1;
long long ans = 0;
void insert(string str,int k){
int len = str.length();
int root = 1;
for(char s:str){
int a = s - 'A';
if(!son[root][a]) son[root][a] = ++idx;
root = son[root][a];
}
cnt[root] = k;//最后一个节点放入对应的排序序号;
}
int find(string str){
int root = 1;
for(char s:str){
int a = s - 'A'; //这里其实有问题,不知道为啥没报错
if(!son[root][a]) return 0;
root = son[root][a];
}
return cnt[root];
}
void merge_sort(int low,int high){
if(low >= high) return;
int mid = (low + high) >> 1;
merge_sort(low,mid);
merge_sort(mid+1,high);
int i,j,k;
for(i = low,j = mid + 1,k = 0;i <= mid&&j <= high;){
if(f[i] > f[j]) ans += mid - i + 1,temp[++k] = f[j++];
else temp[++k] = f[i++];
}
while(i <= mid) temp[++k] = f[i++];
while(j <= high) temp[++k] = f[j++];
for(int rr = 1;rr <= k;rr++)
f[low++] = temp[rr];
}
int main() {
int n;
cin>>n;
string s;
for(int i=0; i<n; i++){
cin>>s;
insert(s,i+1);
}
for(int i=1; i<=n; i++){
cin>>s;
f[i] =find(s);
}
merge_sort(1,n);
cout<<ans;
return 0;
}