trie树,也称字典树,大家有没有想过你们在字典里查找一个单词是怎么样的。
首先,我们会查找第一个字符。
然后在第一个字符的范围内,查找第二个字符…以此类推
我们的字典树,就是这样的。
前置知识
字符串(都会?)
算法用途
快速搜索字符串
算法复杂度
时间
插入,删除,查询长度为 l e n \tt len len 的字符串都是 O ( l e n ) \tt O(len) O(len)
空间
n次插入,最大长度len,有x个不同字符: O ( m i n ( x × l e n × n , x l e n ) ) \tt O(min(x \times len \times n, x^{len})) O(min(x×len×n,xlen))
算法实现
插入
插入字符串 s t r \tt str str。
我们从根开始,往下走,根的深度为 0 \tt 0 0,当前深度为 i \tt i i,我们看看当前节点有没有子节点 s t r [ i ] \tt str[i] str[i]( s t r \tt str str 的第 i \tt i i 个字符的编号),有,继续往下走,没有,那就增加一个,然后继续走。直到执行完 s t r . s i z e ( ) \tt str.size() str.size() 次
查询
查询字符串str。
同理,我们从根开始,往下走,根的深度为 0 \tt 0 0,当前深度为 i \tt i i,我们看看当前节点有没有子节点 s t r [ i ] \tt str[i] str[i]( s t r \tt str str 的第 i \tt i i个字符的编号),有,继续往下走,没有,就是没有这个字符串了
删除
删除字符串str。
同上,只是查找到后把只属于这个字符串的东西删了就行了。
算法优化
我们可以发现这算法时间复杂度很好,但是空间太大了。
所以我们考虑用一下奇怪的优化
正常的话要储存基本的字符串我们要每个节点 128 \tt 128 128 个儿子,这样空间复杂度很大。
但是,我们可以考虑把一个字符拆分成 2 \tt 2 2 个一次插入,这样一个字符串长度就为 2 × l e n \tt 2 \times len 2×len,然后每个节点儿子个数就为 16 \tt 16 16 了
因为一般情况下都是 128 × l e n × n \tt 128 \times len \times n 128×len×n 比 12 8 l e n \tt 128 ^ {len} 128len 小
那原来空间为 128 × l e n × n \tt 128 \times len \times n 128×len×n,现在变为 16 × 2 × l e n × n = 32 × l e n × n \tt 16 \times 2 \times len \times n = 32 \times len \times n 16×2×len×n=32×len×n
减少了
4
\tt 4
4 倍!当然你也可以考虑拆成4份
O
(
16
×
l
e
n
×
n
)
\tt O(16 \times len \times n)
O(16×len×n)
代码
void add(string str)
{
int p = 0;
tree[p].cnt++;
for(int i = 0; i < str.size(); i++)
{
int a = str[i] >> 4;
if(tree[p].son[a])
{
p = tree[p].son[a];
}
else
{
if(sta.empty())
{
tree[p].son[a] = ++siz;
p = siz;
}
else
{
tree[p].son[a] = sta.top();
p = tree[p].son[a];
sta.pop();
}
}
tree[p].cnt++;
a = str[i] & 0x0f;
if(tree[p].son[a])
{
p = tree[p].son[a];
}
else
{
if(sta.empty())
{
tree[p].son[a] = ++siz;
p = siz;
}
else
{
tree[p].son[a] = sta.top();
p = tree[p].son[a];
sta.pop();
}
}
tree[p].cnt++;
}
}
void del(string str)
{
int p = 0;
tree[p].cnt--;
for(int i = 0; i < str.size(); i++)
{
int a = str[i] >> 4;
if(tree[p].son[a])
{
int nowp = p;
p = tree[p].son[a];
tree[p].cnt--;
if(!tree[p].cnt)
{
tree[nowp].son[a] = 0;
sta.push(p);
}
}
else
{
break;
}
a = str[i] & 0x0f;
if(tree[p].son[a])
{
int nowp = p;
p = tree[p].son[a];
tree[p].cnt--;
if(!tree[p].cnt)
{
tree[nowp].son[a] = 0;
sta.push(p);
}
}
else
{
break;
}
}
}
例题
P2580 于是他错误的点名开始了 \color{3498DB}{\texttt{P2580 于是他错误的点名开始了}} P2580 于是他错误的点名开始了
P4551 最长异或路径 \color{9D3DCF}{\texttt{P4551 最长异或路径}} P4551 最长异或路径