如何高效的存储和查找字符串?
问题:
假如有以下字符串:aab,aa,ab,问:字符串aa是否在上述字符串中?
Tire树:高效地查找和存储字符串
主要思想是利用字符串的公共前缀来节约存储空间,很好地利用了串的公共前缀,节约了存储空间。
字典树主要包含两种操作,插入和查找
拿上述例子来说:
如何存储?
先假设有下列这棵树
当存储aab后,这棵树上一些节点被填充完毕:
接下来,如果说再插入aa呢?
很自然的,我们想到在以插入地字符串结尾打上一个标记$。
如此一来,这颗树就成了下面的样子:
最后,插入ab。
如何查找?
假如要查找字符串ab是否已经存在, 只需像查找树的节点一样,一一比较每个字符,最后查看是否有$标记即可。
代码实现:
如图用一个数组son[N][a-z]来表示第N层树上a-z是否存在。其值表示是第几个插入的节点。
如图使用一个cnt[i]来标记,第i个插入的字符串结尾$的个数。(i从1开始)
如果cnt[i]==2,说明从根节点沿路径到这里所对应的字符串数量为2。
const int N = 1e5 + 10;
int son[N][26], cnt[N], idx;//idx从1开始,idx等于0表示空节点,根节点,idx每加1,表示增加了一个子节点
//idx++ 表示新建立一个子节点
//son[i][0] 就表示节点i的孩子是a的子节点对应的idx
//cnt[i]以该子节点结尾的标记了多少次
//插入
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 query(char str[])
{
int p = 0;//从根节点出发
for (int i = 0; str[i]; i++)
{
//获取字母对应编号
int u = str[i] - 'a';
//是否存在
if (!son[p][u])return 0;//不存在则返回0
p = son[p][u];//存在则走到子节点
}
//返回结尾标记
return cnt[p];
}
class Trie {
public:
const static int N=3e5+10;
int son[N][26];
int cnt[N];
int idx;
Trie() {
idx=0;
memset(son,0,sizeof(son));
memset(cnt,0,sizeof(cnt));
}
void insert(string word) {
int p=0;//从第0层出发
for(int i=0;i<word.size();i++)
{
int u=word[i]-'a';
if(!son[p][u])son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;//打标
}
bool search(string word) {
//从第0层出发
int p=0;
for(int i=0;i<word.size();i++)
{
int u=word[i]-'a';
if(!son[p][u])return false;
p=son[p][u];
}
return cnt[p]>0;
}
bool startsWith(string prefix) {
//从第0层出发
int p=0;
for(int i=0;i<prefix.size();i++)
{
int u=prefix[i]-'a';
if(!son[p][u])return false;
p=son[p][u];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/