应用:
- trie树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
- 给你一个字典,构建一个字典的Trie树,然后每次询问给出字符串s,查询字典中以s作为前缀的单词个数.
- 插入一个字符串,就是建树的过程,查询一个字符串是否存在.
解决应用二:
Trie树的建立:
比如我们给了三个单词app, apple, add,我们按照以下规则创建了一颗Trie树.对于从树的根结点走到黑色结点的路径上的字母依次组合起来就是一个完整的单词.
当我们需要加入一个新的单词appart的时候,根据上面的规则,我们建立了下面的树.
如何使用Trie树:
如图,我们标记出了三号结点为绿色.从树的根结点走到三号节点经过的路径上的字母组成的单词是”ap”,如果我们想知道字典中以”ap”作为前缀的单词的个数.那么值需要统计以三号结点作为根结点的结点中黑色结点的个数.而且再建立字典Trie树的时候我们就可以进行记录.
记录方法:
L[T]L[T]从根结点到编号是TT的结点过程中经过边表示字母组成的单词作为前缀的在字典中的单词数目.设置L[T]L[T]的初始值是0.每当加入一个单词,如果达到了结点TT,就把L[T]L[T]加1.这样查询以ss作为前缀的字典中的单词个数的时候时间复杂度就是O(len(s))O(len(s))了.
时间复杂度分析:
假设建立了有NN个单词的每个单词的最大长度是LL的字典Trie树,那么插入一个单词的最坏时间复杂度是O(L)O(L),所以建树的总的时间复杂度是O(NL)O(NL).查询一个单词前缀的单词个数最坏时间复杂度是O(L)O(L).
解决应用三
任务:
设计一种数据结构,支持两种操作: 插入一个字符串: 查询一个字符串是否存在.
说明:
我们采用2个数组实现一个Trie树,child[i][j]child[i][j]代表以ii为根结点的子树, 字符jj代表的边连向哪一个结点child[i][j]=0child[i][j]=0说明没有结点.初始的时候根结点是1.flag[i]flag[i]代表结点ii是否是一个单词的结尾.
插入的时候,哦我们从根结点沿着字符串的每一个字符走向下一层的结点.如果该结点不存在那么就分配一个新的结点.对于最后插入的结点ii,
查找和插入的过程基本上是相同的,区别是,如果我们走到一个不存在的结点那么返回失败.如果我们停留在某一个结点ii,那么返回flag[i]flag[i]即可.
接口:
结构体: Trie
成员函数:
// 复杂度(O(length)),也就是str的长度
void insert(const char *str); // 插入字符串
// 复杂度(O(length)),也就是str的长度
bool query(const char *str); // 查询str是否在字典中
例题一
题意就是统计出以某个字符串为前缀的单词数量,首先构建出trie树并记录每个节点的访问次数,然后在上面查询就好了,模板题。
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdlib.h>
#include<queue>
#include<map>
#include<vector>
#include<math.h>
const int INF = 0x3f3f3f3f;
using namespace std;
typedef long long ll;
typedef double ld;
int i,j,k,l;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char str[])
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id])
tree[root][id]=++tot;
sum[tree[root][id]]++;//记录节点访问次数
root=tree[root][id];
}
//root在此对应某个单词,一一对应
}
int find_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id])
return 0;
root=tree[root][id];
}
return sum[root];//返回当前字符串结尾节点的访问次数,也就是作为前缀的出现次数
}
char ss[maxn];
int main()
{
tot=0;
while(gets(ss))
{
if(ss[0]=='\0')
break;
insert_(ss);
}
while(~scanf("%s",ss))
{
printf("%d\n",find_(ss));
}
return 0;
}
例题二:
题意就是出现的不同单词个数
直接把字符全部插入Trie树中,然后统计所有具有flagg标记的节点个数就好了。
也可以边插入边统计,如果当前字符串结尾下标已经被标记,就不对答案做贡献,否则ans++.
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdlib.h>
#include<queue>
#include<map>
#include<vector>
#include<math.h>
const int INF = 0x3f3f3f3f;
using namespace std;
typedef long long ll;
typedef double ld;
int i,j,k,l;
set<string> words;
int main()
{
string row,input;
while(getline(cin,row)&&row!="#")
{
words.clear();
stringstream str(row);
while(str >> input)
{
words.insert(input);
}
cout<<words.size()<<endl;
}
return 0;
}
例题三:
题意就是求一个能代表这个字符串的最短前缀,也就是只有这个字符串具有的前缀。
做法很显然,我们先构建好Trie树,然后对每个单词进行find,递归到直到节点出现次数为1,表示这个节点只有这一个单词走过,返回就ok。这里我用了string不断拼接字符,然后直接返回,减少了一些代码量。
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdlib.h>
#include<queue>
#include<map>
#include<vector>
#include<math.h>
const int INF = 0x3f3f3f3f;
using namespace std;
typedef long long ll;
typedef double ld;
int i,j,k,l;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;
root=tree[root][id];
}
}
string find_(char *str)
{
int len=strlen(str);
int root=0;
string ans="";
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
root=tree[root][id];
ans+=str[i];
if(sum[root]==1) return ans;
}
}
char ss[1005][25];
int main()
{
int tot=0;
while(scanf("%s",ss[tot++])!=EOF)
{
insert_(ss[tot-1]);
}
for(int i=0;i<tot;i++)
{
printf("%s %s\n",ss[i],find_(ss[i]).c_str());
}
return 0;
}