Trie树详解及其应用

本文详细介绍了Trie树(字典树)的概念、应用及其优势,包括其插入、查找和删除操作。Trie树是一种高效的数据结构,常用于字符串的统计和排序,尤其在搜索引擎和文本词频统计中。通过空间换取时间,Trie树能够以O(k)的时间复杂度完成插入和查询,避免了哈希表的碰撞问题。文章还展示了Trie树的实现细节,包括使用Java实现的TrieNode结构,并给出了插入和查询的具体代码。
摘要由CSDN通过智能技术生成

一、知识简介  
        最近在看字符串算法了,其中字典树AC自动机后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用。
        字典树(Trie)可以保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。
        Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(k) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。
        至于Trie树的实现,可以用数组,也可以用指针动态分配,我做题时为了方便就用了数组,静态分配空间。
        Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
        Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
        Trie树有一些特性:
        1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
        2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
        3)每个节点的所有子节点包含的字符都不相同。
        4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
        5)插入查找的复杂度为O(n),n为字符串长度。

        基本思想(以字母树为例):
        1、插入过程
        对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
        2、查询过程
        同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。

        二、字典树的数据结构:
        利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。
       下面以英文单词构建的字典树为例,这棵Trie树中每个结点包括26个孩子结点,因为总共有26个英文字母(假设单词都是小写字母组成)。
       则可声明包含Trie树的结点信息的结构体

  public class TrieNode {

      public TrieNode[] children; // 记录子节点

      public char data; // 记录该字符

      public int freq; // 记录该字符出现次数

      public TrieNode() {

             //因为有26个字母

             children = new TrieNode[26];

            freq = 0;

      }

}

        其中child是一个数组,存放着所有子结点。
        如给出字符串"abc","ab","bd","dda",根据该字符串序列构建一棵Trie树。则构建的树如下:

       

        Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中子数组不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
        如上图中:Trie树中存在的就是abc、ab、bd、dda四个单词。在实际的问题中可以将标记颜色的标志位改为数量count等其他符合题目要求的变量。
        已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:

        1、 最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。

        2、 使用hash:我们用hash存下所有字符串的所有的前缀子串。建立存有子串hash的复杂度为O(n*len)。查询的复杂度为O(n)* O(1)= O(n)。

        3、 使用Trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b、c、d....等不是以a开头的字符串就不用查找了,这样迅速缩小查找的范围和提高查找的针对性。所以建立Trie的复杂度为O(n*len),而建立+查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度只是O(len)。      

        Trie树的操作
        在Trie树中主要有3个操作,插入、查找和删除。一般情况下Trie树中很少存在删除单独某个结点的情况,因此只考虑删除整棵树。
        1、插入
        由于是26叉树,故可通过charArray[index]-‘a';来得知字符应该放在哪个孩子中。

public void insert(String word){

        if(TextUtils.isEmpty(word)){

                return;

       }

               insertNode(root,word.toCharArray(),0);

        }

private static void insertNode(TrieNode rootNode,char[]charArray,int index){

         int k=charArray[index]-'a';

                 if(k<0||k>25){

                           throw new RuntimeException("charArray[index] is not a alphabet!");

                  }

                  if(rootNode.children[k]==null){

                            rootNode.children[k]=new TrieNode();

                            rootNode.children[k].data=charArray[index];

                  }

                  if(index==charArray.length-1){

                          rootNode.children[k].freq++;

                          return;

                  }else{

                          insertNode(rootNode.children[k],charArray,index+1);

                  }

         }

        2、查找       

public int getFreq(String word){

if(TextUtils.isEmpty(word)){

 return 0;

}

return getFreq(root,word.toCharArray(),0);

}

private static int getFreq(TrieNode rootNode,char[]charArray,int index){

int k=charArray[index]-'a';

if(k<0||k>25){

    throw new RuntimeException("charArray[index] is not a alphabet!");

}

//it means the word is not in the tree

if(rootNode.children[k]==null){

      return 0;

}

if(index==charArray.length-1){

      return rootNode.children[k].freq;

}

return getFreq(rootNode.children[k],charArray,index+1);

}

        3、删除
        移除操作中,需要对词频进行减一操作。

public void remove(String word){

if(TextUtils.isEmpty(word)){

         return;

}

remove(root,word.toCharArray(),0);

}

private static void remove(TrieNode rootNode,char[]charArray,int index){

int k=charArray[index]-'a';

if(k<0||k>25){

      throw new RuntimeException("charArray[index] is not a alphabet!");

}

if(rootNode.children[k]==null){

        //it means we cannot find the word in this tree

       return;

}

if(index==charArray.length-1&&rootNode.children[k].freq >0){

         rootNode.children[k].freq--;

}

remove(rootNode.children[k],charArray,index+1);

}

参考:

1、https://blog.csdn.net/Hackbuteer1/article/details/7964147?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~default-7.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~default-7.control

2、http://www.zzvips.com/article/91169.html

       
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值