Trie结构是模式匹配中经常用到的经典结构,在字符串处理中发挥着重要的作用,比如分词算法,就会利用Trie结构将分句的已知词条先识别出来,然后再判断剩下的未识别部分是否是新的未知词。
经典的Trie结构如下图所示,
是一个典型的多叉树结构,为了保证用Trie结构进行模式匹配的效率,Trie结构的每一个节点往往会容纳输入字符集的所有字母构成的数组,以便实现高速查找,这样的缺点就是内存空间的大量浪费,因为越到Trie结构的叶节点,每个节点所包含的字母数量就越少,很可能出现为了几个字符,而多出几十个空位的情况.如上图,没有一个Trie节点的元素多于3个,但是为了算法的效率,每个节点都需要预留26个空位。读者可能会考虑对于Trie结构的每个节点,用散列表代替数组,改直接查找为散列查找,来减少空间的浪费,但是像Trie这样的基本处理工具,每秒钟可能需要对目标文本执行上万次的匹配操作,散列计算的开销很可能会成为影响程序效率的瓶颈。那么有没有既可以充分利用节点空间,又可以避免散列计算开销的Trie实现呢。Jun-Ichi Aoe(日本)于1989年提出的double-array结构,正是将Trie结构的高效率与空间的紧凑利用相结合的理想解决方案。
前面我们提到,可以用散列表来节省Trie结构节点的空间浪费,但是我们又不想对每一次字符查找都进行散列运算,实际上,我们使用散列表的初衷,无非也就是对于当前节点,我们去哪几个节点去匹配下一个输入字符。这里我们以字符集a-z为例,来考虑如何实现Trie结构的压缩存储。
如上图所示,我们假设有一个很长的数组BASE[1...n],此时对于集合K,我们可以将BASE[1...26],作为对应的Trie结构的根节点,这样对于第1个输入字符,肯定可以映射到BASE[1...26]中的一个,然后我们将BASE[27...52]作为当第1个输入字符是a时,第2个字符对应的区间。BASE[53...78]作为当第1个字符是b时,第2个字符的映射区间,依次下去,将BASE[27...702]对应Trie结构根节点26个字母所分别对应的的第2层顶点。此时我们实现了将Trie结构的各层顶点映射到一个一维数组上面。为了能够索引到对应的BASE数组中的正确位置,我们还需要一个CHECK数组与BASE中的元素一一对应,来标记当前字符是从哪个父节点索引过来的。比如说BASE[53]的a是从BASE[2]=b索引过来的,而不是从BASE[1]或者BASE[3]索引过来的,对应于模式串前缀ba,