一道有趣的面试:Trie 树及其改进

0x00 导言

Trie 树是一种常见的数据结构,用以解决在给定单词在字典中是否存在的问题,而且支持动态的增删词典内容,常见的实现结构如下:

struct node{
    bool is_word ;
    struct node * [26];
};

一道有趣的面试:Trie 树及其改进_第1张图片
对于任意词典,查找给定单词的效率为O(1),比hash还要快。hash虽然也是O(1),但是hash不能保证没有冲突,即使预先设计了一个良好的 hash 算法,动态修改词典内容也会导致冲突问题。

0x01 一种改进思路

但是 Trie 本身也有不足的地方:

  1. 内存占用较多,每个节点都会开辟26个指针;
  2. 另外Trie 是一颗前缀树,一颗 Trie 树通常会包含很多浪费的尾链,读者可以想象下垂柳的结构。

这里分享一种笔者曾经用过的改进方法:

  1. 内存分配策略修改,原来的实现通常是每次 new 方法生成一个新的节点,这里我们换一个思路,使用vector,借助STL 的内存分配器帮我们管理内存。这种方式有如下好处:一方面简化了内存维护工作;另一方面,vector支持索引访问,可以将指针优化为索引,指针占用的内存将减少一半。
  2. 单链压缩,对于链上每个节点只有一个指针不为空的情况,压缩为一个节点。

新的 Trie 树结构如下:
一道有趣的面试:Trie 树及其改进_第2张图片

值得注意的是,这里每个节点只有26个int大小,s 复用了第2、3个索引位置。
第0个索引位置进行了多重复用:
1. 如果idx[0] == 0,表示这是一个压缩节点,s = (char *)(&idx[2]) 表示字符串首地址,并表示是一个单词。
2. 如果idx[0] == -1,表示这是一个空索引,隐形的叶子结点。
3. 如果idx[0] == +1,没有意义。
4. 如果idx[0] == +N(+N > 1),表示这是一个非叶子结点且不是一个单词。
5. 如果idx[0] == -N (-N < -1),表示这是一个非叶子结点,但是一个单词。

此时节点定义如下:

struct node{
    int idx [26];
};

这种情况虽然实现起来稍微麻烦些,但也不会太多,最主要的是相对常规做法可以节省不少内存占用。另外同样可以处理动态修改字典的需求。代码就不贴了,有兴趣的读者可以自行尝试。

Trie 改进还有很多优秀的结论,对于离线需求,比如不修改词典,还有一种双数组Trie树,进一步节省内存,而且可以处理中文等其他编码,结合AC自动机可以实现高效率的文章分词。如果将Trie改进为Trie图,还可以进行多模式串匹配加速。今天就不展开了,等另开文章给大家分享。


欢迎大家关注作者公众号,一起进步~
一道有趣的面试:Trie 树及其改进_第3张图片

你可能感兴趣的:(数据结构,算法入门,积累)