所写内容,是对自己所学知识的一个记录罢了。
1.简介
最近在做中国人名识别的时候,看到一篇文章是基于角色的人名识别,而角色字典中有2个角色是用双数组Tire树来建立的,当时没有看懂,于是来先学习Trie树。
Trie树,又称字典树,单词查找树或者前缀树等,是一种快速 检索的多叉树结构。比如,英文字母的字典树是一个20叉树,数字的字典树是一个10叉树。
字典树(Trie)可以保存一些字符串-->值的对应关系。这和java中的HashMap的功能相同,都是KEY-VALUE的映射,只不过Trie树的KEY是字符串而已(当然不仅限于字符串)。如果我们有and,as,at,cn,com这些关键词,我们可以构建如下的Trie树。
由上图,我们可以归为Trie树的基本性质为:
(1)根节点不包含字符,除根节点外每个节点只包含一个字符
(2)从根节点到某一个节点,路径上经过的字符链接起来,为该节点对应的字符串
(3)每个节点的所有子节点包含的字符串不相同。
2.复杂度
Trie的插入(Insert)、删除( Delete)和查找(Find)都非常简单,用一个一重循环即可,即第i 次循环找到前i 个字母所对应的子树,然后进行相应的操作。
(1)插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节
点标记为红色,表示该单词已插入Trie树。
(2)查询过程
同样的,从根开始按照单词的字母顺序遍历Trie树,一旦发现某个节点标记不能存在或者遍历完成而最优的节点
未标记为红色,则表示该单词不存在;反之,则表示该单词存在。
Trie树的强大之处在于它的时间复杂度,它的插入和查询时间复杂度都为 O(N) ,其中N为KEY的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(N) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。例如,英文字母的Trie树的空间复杂度要为26^N。实现这棵字母树,我们用最常见的数组保存(静态开辟内存)即可,当然也可以开动态的指针类型(动态开辟内存)。
Trie树的一些特性
(1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
(2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3)每个节点的所有子节点包含的字符都不相同。
(4)如果字符的种数为N,则每个节点的出度为N,这也是空间换时间的体现,浪费了很多的空间。
(5)插入查找的复杂度为O(N),N为字符串长度。
3.应用
Trie树是一种树形结构,是一种哈希树的变种。所以二者有着千丝万缕的关系。Trie的核心思想是牺牲空间换取时间的效率。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
(1)词频统计
典型的应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎用于文本词频统计。但是Hash或者堆也可以解决这个问题,那为什么还要用Trie树呢?因为当内存有限的时候,我们可以用Trie树来压缩空间,因为公共前缀都是用一个节点保存的。最大限度的减少无谓的字符串比较,查询效率比哈希表要高。
(2)前缀匹配
这个就是我在开始提到的人名识别的角色的建立用的。如果想深入了解,可参考http://www.hankcs.com/nlp/segment/ictclas-the-hmm-name-recognition.html。好了,想要大家了解一下还有这么个应用而已^_^。继续往下看,还拿上面的图来说,如果我想获得所有以“a”开头的字符串,从图中可以名下的看到是and,as,at。那么,如果不用Trie树呢?我们最容易想到的就是对字符串集从头到尾进行搜索 ,看每个字符串是否为字符串集中的前缀子串,其复杂度为O(N^2)。这个复杂度可是算很高的了。可能还会有人想到用hash,我们使用hash存下所有字符串的所有前缀子串。建立存有子串的hash复杂度为O(N*len)。查询的复杂度为O(N)。那使用Trie树的有点在哪呢?如果我们要查询如字符串"abcd"是否为某个字符串的前缀时,显然以"b","c","d"等等不是以"a"开头的字符串就不用查找了,这样迅速缩小了查找范围且提高了查找的针对性。所以建立Trie树的复杂度为O(N*len),而建立+查询在Trie树是可以同时执行的,建立的过程也就是查询的过程,而hash是不能实现这个功能的所以总的复杂度为O(N*len),实际查询的复杂度只是O(len)。
(3)排序
Trie是一棵多叉树,只要先序遍历整棵树(先序是树的一个基本遍历,后面也记录一下,省的忘了 ^_^),输出相应的字符串便是按字典序排序的结果。例如@ 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
下面以二叉树为例子,讲述一下先序,中序及后序遍历。一般约定遍历时左节点优于右节点。
前序:先遍历根节点,在处理左右节点。即:根-->左-->右
中序:先遍历左节点,然后处理根节点,左右处理右节点。即:左-->根-->右
后序:先遍历左右节点,再处理根节点。即:左-->右-->根
(4)作为其他数据结构和算法的辅助结构
例如:后缀树(这个在前面提到的人名识别也有应用),AC自动机等
还在继续学习中。。。先记录到这。。。
4.参考资料
(1)http://dongxicheng.org/structure/trietree/
(2)http://blog.csdn.net/hackbuteer1/article/details/7964147