字典树(Trie树)总结-hdu1251

40 篇文章 0 订阅

字典树(也称前缀树,Trie)

Trie树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

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

它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

Trie树也有它的缺点,Trie树的内存消耗非常大。

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

这里写图片描述

应用:
1. 字符串检索,词频统计,搜索引擎的热门查询
2. 字符串最长公共前缀
Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。
举例:给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少.
解决方案:
首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线 (Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
3. 排序:Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。

我以hdu1251为例,写一个Trie树的模板。

hdu1251

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251

题意:老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀)。

这题有个小技巧。
如何判断空行:用gets()读入。读入的回车符会自动转换为NULL。所以循环读入,每次检测读入进来的字符串的第一个字符是否为NULL即可。

没什么题解,这题可以说是Trie树的模板题,直接Trie树解决就行了。

代码有两种,一种用链表实现,一种用数组实现,数组做法在时间和空间上都优于链表。(推荐用数组,链表理解简单些)

先看链表代码,简单理解一下,再看数组代码就轻松些。

链表代码

//当前代码并不能过,编译器从g++改为c++,交g++的话会超内存。
//原因好像是g++会优化,导致出现一些问题。感兴趣的自行百度。
//c++编译器不支持#include <bits/stdc++.h>,所以再把该头文件改为其它即过
//#include <bits/stdc++.h>包含所有头文件,你把它删掉写具体的头文件出来就OK
#include <bits/stdc++.h>
using namespace std;

struct Trie{
    Trie *next[26];  //每个节点可以延伸26个子节点,即a-z
    int num;         //含有当前前缀的单词个数
    Trie()           //初始化
    {
        for(int i=0;i<26;i++) next[i]=NULL;
        num=0;
    }
}root;

int idx(char s)  //字母转数字
{
    return s-'a';
}

void Trie_insert(char word[])  //插入单词
{
    Trie *p=&root;
    for(int i=0;word[i];i++)
    {
        //如果不存在当前前缀就新建
        if(p->next[idx(word[i])]==NULL) p->next[idx(word[i])] = new Trie;
        p = p->next[idx(word[i])];  //节点移到当前前缀
        p->num++;  //含有当前前缀的单词+1
    }
}

int Trie_search(char word[])  //查询存在以word[]为前缀的单词个数
{
    Trie *p=&root;
    for(int i=0;word[i];i++)  //找到前缀为word[]的节点
    {
        if(p->next[idx(word[i])]==NULL) return 0;
        p = p->next[idx(word[i])];
    }
    return p->num;  
}

int main()
{
    char word[11];
    while(cin.getline(word,11))
    {
        if(strlen(word)==0) break;
        Trie_insert(word);
    }
    while(scanf("%s",word)!=EOF)
    {
        printf("%d\n",Trie_search(word));
    }
    return 0;
}

数组代码

//自行理解一下
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+5;
int trie[maxn][26],num[maxn],cnt=1;

void Trie_insert(char word[])
{
    int n=0;
    for(int i=0;word[i];i++)
    {
        if(trie[n][word[i]-'a']==0) trie[n][word[i]-'a']=cnt++;
        n = trie[n][word[i]-'a'];
        num[n]++;
    }
}

int Trie_search(char word[])
{
    int n=0;
    for(int i=0;word[i];i++)
    {
        if(trie[n][word[i]-'a']==0) return 0;
        n = trie[n][word[i]-'a'];
    }
    return num[n];
}

int main()
{
    char word[11];
    while(gets(word))
    {
        if(word[0]==NULL) break;
        Trie_insert(word);
    }
    while(scanf("%s",word)!=EOF)
    {
        printf("%d\n",Trie_search(word));
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值