字典树详细解剖

来源:互联网 发布:淘宝卖家退货退款流程 编辑:程序博客网 时间:2024/05/17 20:11
以下内容  为网上复制 及部分的个人理解
 
 
 
Trie树就是字典树,其核心思惟就是空间换时候。


举个简单的例子。



给你100000个长度不跨越10的单词。对于每一个单词,我们要断定他出没呈现过,若是呈现了,第一次呈现第几个地位。
这题当然可以用hash来,然则我要介绍的是trie树。在某些方面它的用处更大。比如说对于某一个单词,我要询问它的前缀是否呈现过。如许hash就不好搞了,而用trie还是很简单。
如今回到例子中,若是我们用最傻的办法,对于每一个单词,我们都要去查找它前面的单词中是否有它。那么这个算法的错杂度就是O(n^2)。显然对于100000的局限难以接管。如今我们换个思路想。假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开首的我显然不必推敲。而只要找以a开首的中是否存在abcd就可以了。同样的,在以a开首中的单词中,我们只要推敲以b作为第二个字母的……如许一个树的模型就垂垂清楚了……
假设有b,abc,abd,bcd,abcd,efg,hii这6个单词,我们构建的树就是如许的。

对于每一个节点,从根遍历到他的过程就是一个单词,若是这个节点被标识表记标帜为红色,就默示这个单词存在,不然不存在。
那么,对于一个单词,我只要顺着他从跟走到对应的节点,再看这个节点是否被标识表记标帜为红色就可以知道它是否呈现过了。把这个节点标识表记标帜为红色,就相当于插入了这个单词。
如许一来我们询问和插入可以一路完成,所用时候仅仅为单词长度,在这一个样例,便是10。
我们可以看到,trie树每一层的节点数是26^i级此外。所认为了节俭空间。我们用动态链表,或者用数组来模仿动态。空间的花费,不会跨越单词数×单词长度。


===========================================================================================


 http://www.cnblogs.com/tanky_woo/archive/2010/09/24/1833717.html ;Tanky Woo的法度人生


又称单词查找树Trie树,是一种树形布局,是一种哈希树的变种。典范应用是用于统计,排序和保存多量的字符串(但不仅限于字符串),所以经常被搜刮引擎体系用于文本词频统计。它的长处是:哄骗字符串的公共前缀来节俭存储空间,最大限度地削减无谓的字符串斗劲,查询效力比哈希表高。 

Trie的数据布局定义:



#define MAX 26
typedef 
struct Trie   
{   
    Trie 
*next[MAX];   //max的值要变 若是只是小写字母,则26即可,大小写字母,则是52,若加上数字,则是62,按照题意来断定
    
int v;  //v即建树的时候走过本节点几次 也就是说以从root到当前节点的前缀被用过几次  初始化为0
 };   
 
Trie 
*root;
next  其实代表的是下面层数的个数
 
结合实例 详细剖析字典树:
hdu1251

统计难题

Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)
Total Submission(s): 8549 Accepted Submission(s): 3411

Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).

Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.

注意:本题只有一组测试数据,处理到文件结束.

Output
对于每个提问,给出以该字符串为前缀的单词的数量.

Sample Input


banana


band


bee


absolute


acm

 

ba


b


band


abc

 

Sample Output


2


3


1


0

 

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
structnode
{
    intcount ;
    structnode *next[26];//定义26个分节点
};
structnode *root;
structnode *build()//建立节点
{
    structnode *p;
    p=(structnode *)malloc(sizeof(structnode));
    for(inti=0;i<26;i++)
    {
        p->next[i]=NULL;//令其指向0;
    }
        p->count=1;/*如果出现这个站点,那么它的次数肯定是大于等于1的。 题目要求 所以为1 因为本身也是前缀
        但是我们刚开始建立不必考虑后续问题*/
    returnp;//返回新建立的节点
}
voidsave(char*s)//储存字母
{  
    intlen=strlen(s);
    if(len==0)return;
    structnode *p;
    p=root;
    for(inti=0;i<len;i++)
        if(p->next[s[i]-'a']!=NULL)
            /*如果这个节点之前就已经存在呃,我们只需要把统计次数加上1.*/
        {  
            p=p->next[s[i]-'a'];
            p->count=p->count+1;
        }
        else//如果不存在的话,我们就建立一个新的节点 
        {
            p->next[s[i]-'a']=build();
            p=p->next[s[i]-'a'];
        }
}
intseach (char*s)//这个函数来查询数据
{
    structnode *p;
    intlen=strlen(s);
    if(len==0)return0;
    p=root;//此处小心
    for(inti=0;i<len ;i++)
    {
        if(p->next[s[i]-'a']!=NULL)//说明还有下个节点,继续查询
            p=p->next[s[i]-'a'];
        else 
            return0;
        /*如果没有指向下个节点,说明这个要查询的字符串根本不存在,直接返回0*/
    }
    returnp->count;
}
intmain()
{
    charstr[15];
    inti,ans;
    root=build();
    while(gets(str)&&str[0]!='\0')
        save(str);
    while(~scanf("%s",str))
    {
        ans=seach(str);
        printf("%d\n",ans);
    }
    return0;
}
 
 
剖析:
 

1.建立:

这个是关键。这个地方用到了链表,如果不了解链表的同学还是请先百度一下链表,然后做一下链表的题目吧。无论哈希还是字典树,链表都是基础。好了,不废话了。注意看我代码中的注释。

定义:

structnode
{
    intcount ;
    structnode *next[26];//定义26个分节点
};

建立节点:


 
 
 
 
 
 
 
 
 
 
 
 
structnode *build()//建立节点
{
    structnode *p;
    p=(structnode *)malloc(sizeof(structnode));
    for(inti=0;i<26;i++)
    {
        p->next[i]=NULL;//令其指向0;
    }
        p->count=1;/*如果出现这个站点,那么它的次数肯定是大于等于1的。
        但是我们刚开始建立不必考虑后续问题*/
    returnp;//返回新建立的节点
}

补充说明一点,代码中的next[26]可以根据自己的需要修改,比如说我只是储存一个数字,那么我只要定义数组到10即可。其实这个代表的是下个节点的个数。

为什么要这么做呢?我们用字典树的目的是什么?我们要用一个树储存下一个单词,或者一个很大的数字。每个节点只放一个数字或者字母。这个节点都用链表把它链接起来。建立结束之后我们可以把它看作是一个树。

2.储存(插入)

好,上一步我们已经建立了一个简单的构建,但是我们并没有把这些节点链接起来,或者说我们并没有给他们安排好辈分问题。(谁是爹,谁是儿子的问题)

那么下面我们就开始明确他们之间的关系:


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
void save(char *s)//储存字母
{
    intlen=strlen(s);
    if(len==0)return;
    structnode *p;
    p=root;
    for(inti=0;i<len;i++)
        if(p->next[s[i]-'a']!=NULL)
            /*如果这个节点之前就已经存在呃,我们只需要把统计次数加上1.*/
        {
            p=p->next[s[i]-'a'];
            p->count=p->count+1;
        }
        else//如果不存在的话,我们就建立一个新的节点
        {
            p->next[s[i]-'a']=build();
            p=p->next[s[i]-'a'];
        }
}

如果你真的细心的看的话,其实我们开始的时候并不是建立一个完整的树(也就说我根本不知道他到底有多少层,到底有多少分支)。我上一步只是建立的节点,现在就是动态的建立节点。如果之前这个节点存在的话,我就不需要再建立新的节点了,如果没有的话,我才需要建立新的节点,新的分支。

3.查询(查找)

到现在为止,我们已经建立了一个字典树,我们也明确了他们的从属问题(不会因为让谁当爹而大打出手)。下面我们就需要完成对字典树的查找或者统计了。

请用最笨的方式复制代码,否则无法运行(我也不知道如何解决,请高人赐教)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
int seach (char *s)//这个函数来查询数据
{
    structnode *p;
    intlen=strlen(s);
    if(len==0)return0;
    p=root;//此处小心
    for(inti=0;i<len ;i++)
    {
        if(p->next[s[i]-'a']!=NULL)//说明还有下个节点,继续查询
            p=p->next[s[i]-'a'];
        else
            return0;
        /*如果没有指向下个节点,说明这个要查询的字符串根本不存在,直接返回0*/
    }
    returnp->count;
}

仔细看的话,你会发现我们判断是否已经到达底部的标准是他的下一个指向是否为空。

4.删除(点或者节点)

对于这个功能,我表示很尴尬,因为我之前没有用到过,也没有人教过我,不过百度的时候他们说用到的情况很少,所以““所以我就没怎么弄。好吧,如果我以后学会了,肯定会写上的,如果你会的话,还请你不吝指教。小弟万分感谢。

我会的也基本上都说完了,下面就是具体的应用了