字典树(Trie树)

本文深入讲解Trie树(字典树)的概念、构建方法及查询原理。Trie树通过优化字符串匹配过程,实现高效的文本词频统计。文章还提供了详细的代码实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       Trie树,即字典树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

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

它有3个基本性质:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 每个节点的所有子节点包含的字符都不相同。

1. 字典树的构建

       题目:给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置。
分析:这题当然可以用hash来解决,但是本文重点介绍的是trie树,因为在某些方面它的用途更大。比如说对于某一个单词,我们要询问它的前缀是否出现过。这样hash就不好搞了,而用trie还是很简单。
假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开头的我显然不必考虑。而只要找以a开头的中是否存在abcd就可以了。同样的,在以a开头中的单词中,我们只要考虑以b作为第二个字母的,一次次缩小范围和提高针对性,这样一个树的模型就渐渐清晰了。
好比假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:

如上图所示,对于每一个节点,从根遍历到他的过程就是一个单词,如果这个节点被标记为红色,就表示这个单词存在,否则不存在。
那么,对于一个单词,我只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。把这个节点标记为红色,就相当于插入了这个单词。

#基本的操作#

定义(即定义结点):

    struct node{    
        int cnt;    
        struct node *next[26];    
        node(){    
            cnt=0;    
            memset(next,0,sizeof(next));    
        }    
    }; 
    node *root = NULL;  

next是表示每层有多少种类的数,如果只是小写字母,则26即可,若改为大小写字母,则是52,若再加上数字,则是62了,这里根

据题意来确定

cnt可以表示一个字典树到此有多少相同前缀的数目,这里根据需要应当学会自由变化

插入(即建树过程):

构建Trie树的基本算法也很简单,无非是逐一把每则单词的每个字母插入Trie。插入前先看前缀是否存在。如果存在,就共享,否则

创建对应的节点和边。比如要插入单词add(已经插入了单词“ad”),就有下面几步:

    考察前缀"a",发现边a已经存在。于是顺着边a走到节点a

    考察剩下的字符串"dd"的前缀"d",发现从节点a出发,已经有边d存在。于是顺着边d走到节点ad

    考察最后一个字符"d",这下从节点ad出发没有边d了,于是创建节点ad的子节点add,并把边ad->add标记为d

    void buildtrie(char *s){    
        node *p = root;    
        node *tmp = NULL;    
        int l = strlen(s);    
        for(int i = 0; i < l; ++i){    
            if(p->next[s[i]-'a'] == NULL){    
                tmp = new node;    
                p->next[s[i]-'a'] = tmp;    
            }    
            p = p->next[s[i]-'a'];    
            p->cnt++;    
        }    
    }  

2. 查询

(1)每次从根结点开始进行搜索;

(2)取要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;

(3)在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索; 

(4)迭代刚才过程。。。

(5)直到在某个结点处:

——关键词的所有字母都被取出,则读取附在该结点上的信息,即完成查找。

——该结点没有任何信息,则输出该关键词不在此字典树里。

void _findtrie(char *s)
{
    node *p = root;
    int l = strlen(s);
    for(int i = 0; i < l; ++i)
    {
        if(p->next[s[i]-'a'] == NULL)
        {
            printf("0\n");
            return;
        }
        p = p->next[s[i]-'a'];
    }
    printf("%d\n",p->cnt);
}

bool findtrie(char *s)
{
    node *p = root;
    int l = strlen(s);
    for(int i = 0; i < l; ++i)
    {
        if(p->next[s[i]-'a'] == NULL)
        {
            return false;
        }
        p = p->next[s[i]-'a'];
    }
    return p->cnt > 0;
}

3.释放内存

有些题目,数据比较大,需要查询完之后释放内存(比如:hdu1671 Phone List)

递归释放内存:

    void del(node *root){  
        for(int i = 0; i < 10; ++i)  
            if(root->next[i])  
                del(root->next[i]);  
        delete(root);  
    }  

4.main()函数:

int main(){  //hdu1251
    char str[11];  
    root = new node;  
    while(gets(str)){  
        if(strcmp(str,"") == 0)  
            break;  
        buildtrie(str);  
    }  
    while(scanf("%s",str) != EOF){  
        findtrie(str);  
    }  
    return 0;  
}  

注意事项:

1.用G++交会出现Memory Limit Exceeded(就算释放了内存,还是Memory Limit Exceeded)

2.根结点要初始化root=new node;  


相关链接:

http://blog.youkuaiyun.com/sunnyyoona/article/details/43900425










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值