zoukankan      html  css  js  c++  java
  • Trie树(留待自己总结)


    l Trie原理

    Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

     

    l Trie性质

    好多人说trie的根节点不包含任何字符信息,我所习惯的trie根节点却是包含信息的,而且认为这样也方便,下面说一下它的性质 (基于本文所讨论的简单trie树)

    1. 字符的种数决定每个节点的出度,即branch数组(空间换时间思想)

    2. branch数组的下标代表字符相对于a的相对位置

    3. 采用标记的方法确定是否为字符串。

    4. 插入、查找的复杂度均为O(len),len为字符串长度

    l Trie的示意图

    如图所示,该trie树存有abc、d、da、dda四个字符串,如果是字符串会在节点的尾部进行标记。没有后续字符的branch分支指向NULL





    l TrieTrie的优点举例

    已知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)。


    解释一下hash为什么不能将建立与查询同时执行,例如有串:911,911456输入,如果要同时执行建立与查询,过程就是查询911,没有,然后存入9、91、911,查询911456,没有然后存入9114、91145、911456,而程序没有记忆功能,并不知道911在输入数据中出现过。所以用hash必须先存入所有子串,然后for循环查询。

    而trie树便可以,存入911后,已经记录911为出现的字符串,在存入911456的过程中就能发现而输出答案;倒过来亦可以,先存入911456,在存入911时,当指针指向最后一个1时,程序会发现这个1已经存在,说明911必定是某个字符串的前缀,该思想是我在做pku上的3630中发现的,详见本文配套的“入门练习”。

     

     

    AekdyCoin大神在博客中提到Trie可以:

    1.求含有某个前(后)缀的单词个数,这个可以通过搜索这个前(后)缀得到cnt并返回,应该可行.
    2.查询一个单词是否出现过,如果出现过那么显然他出现的次数cnt>=1...这应该是Tries的最基本运用了..
    3.你想过数字也可以拿来这么Hash么?可以看成是一个字符集为10的Trie{'0','1',..'9'},显然<2^31的数字是只有最多10位,那么如此Hash真正做到了O(1)...不过在预处理的时候需要O(log10(N))来处理数字,一个方法是我们可以在输入的时候就用char[]来读-.-,以后再也不土土的写常数很大的Hash表了,就用Trie吧~
    4.如果3可以,那么下面就可以随便yy了,比如坐标的Hash...整数的自然是没问题,在<2^31的范围内,显然大不了20位(正负特判)
    同三维坐标...
    MS是想怎么Hash就怎么Hash...肯定比我以前写的土土的Hash表快多了..
    5.实数的Hash,对于给定上限小数点后位的实数来说,乘10^k就可以Hash了,注意可能溢出-_-
    6.yy结论,Trie太强大了,以后Hash就靠它了-_-
     
     
    经过实践证明...我还是继续用Hash表吧..字典树空间太大了..,看来我YY错了

     

    搞了几个小时,看来以后数字的Hash还是少用Trie为妙..

     

    l Trie的简单实现(插入、查询)

    模板

     

    /*
    字典树,又称单词查找树,Trie树,是一种树形结构,典型应用是用于统计,排序和保存大量的字符串,所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度的减少无谓的字符串比较,查询效率比哈希表高。
    
    字典树的应用:
    
    字符串的快速检索
    哈希
    最长公共前缀
    */
    
    //Trie树模板   by AbandonZHANG
    
    struct Trie_node
    {
        int Ch_Count;   //统计单词个数,如果为0表示没有该串。
        int hash;       //单词hash后的标识数
        Trie_node *next[26];//指向各个子树的指针,下标0-25代表26字符,如果是数字改成10就行了
        Trie_node()
        {
            Ch_Count=0;
            hash=-1;
            memset(next,NULL,sizeof(next));
        }
    };
    
    class Trie
    {
    public:
        Trie();
        void insert(char *word);                 //插入新单词
        int search(char * word);                 //返回单词个数,如果为0表示不存在该单词
    private:
        Trie_node* root;
    };
    
    Trie::Trie()
    {
        root = new Trie_node();
    }
    
    void Trie::insert(char * word)            //有时候可以插入查询一块儿做
    {
        Trie_node *p = root;
        int len=strlen(word);
        if(len==0)return ;
        for (int i=0;i<len;i++)
        {
            if(p->next[word[i]-'a'] == NULL)      //如果不存在的话,我们就建立一个新的节点
            {
                Trie_node *tmp = new Trie_node();
                p->next[word[i]-'a'] = tmp;
                p = p->next[word[i]-'a'];         //每插入一步,相当于有一个新串经过,指针要向下移动
            }
            else                                //如果这个节点之前就已经存在呃,我们只需要把统计次数加上1
                p=p->next[word[i]-'a'];
            //p->Ch_Count++;                      //这里是求所有前缀出现的次数,如果只求整个单词出现次数则用后一个
        }
        p->Ch_Count++;                        //求整个单词的出现次数
        /*
        if (p->hash<0)
            p->hash=words_num++;
        */
        return ;
    }
    
    int Trie::search(char * word)                //返回单词个数,如果为0表示不存在该单词
    {
        Trie_node *p = root;
        int len=strlen(word);
        if (len==0) return 0;
        for (int i=0;i<len;i++)
        {
            if (p->next[word[i]-'a']!=NULL)
                p=p->next[word[i]-'a'];
            else
                return 0;
        }
        return p->Ch_Count;
        //return p->hash;                       //返回单词的hash标识数,如果为0表示不存在该单词
    }
    
    int main()         //简单测试
    {
        Trie t;
        t.insert("a");
        t.insert("abandon");
        string c= "abandoned";
        t.insert(c);
        t.insert("abashed");
        if(t.search("abashed"))
            printf("abashed true\n");
        if(t.search("a"))
            printf("a true\n");
        if(t.search("abandoned"))
            printf("abandoned true\n");
        if(t.search("aba"))                     //看前缀统计不统计
            printf("aba true\n");
        return 0;
    }

     

    入门练习 :PKU POJ 3630解题报告

    举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
  • 相关阅读:
    Overloaded的方法是否可以改变返回值的类型
    parseXXX的用法
    java的类型转换问题。int a = 123456;short b = (short)a;System.out.println(b);为什么结果是-7616?
    UVA 10405 Longest Common Subsequence(简单DP)
    POJ 1001 Exponentiation(大数处理)
    POJ 2318 TOYS(计算几何)(二分)
    POJ 1265 Area (计算几何)(Pick定理)
    POJ 3371 Flesch Reading Ease (模拟题)
    POJ 3687 Labeling Balls(拓扑序列)
    POJ 1094 Sorting It All Out(拓扑序列)
  • 原文地址:https://www.cnblogs.com/AbandonZHANG/p/2598336.html
Copyright © 2011-2022 走看看