字典树(Trie树)附例题(统计难题 HDU - 1251+Xor Sum HDU - 4825)

本文深入解析字典树(Trie树)的基础理论、基本性质、优点及定义,通过实例展示插入与查找操作,最后提供两道实战题目,帮助读者掌握字典树的应用技巧。

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

二、基本性质:

  • 根节点不包含字符,除根节点之外每个子节点都包含一个字符。
  • 从根节点开始到某一终止节点,路径上的字符连接起来,就是该终止节点对应的字符串。
  • 每个终止节点代表的字符串都是不相同的。

三、优点:

  • 快速查找检索,时间复杂度很低。
  • 最大限度地节约储存空间。
  • 利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

四、字典树定义

  • int tot;初始化为1,用于给节点编号。
  • int trie[maxn][26];字典树的储存,trie[ i ][ j ]表示第i个节点的第j个孩子。
  • bool isend[maxn];isend[ i ]用于判断第i个节点是否是某个字符串的结束字符,常用查询整个单词时。

五、基本操作:

  • 插入操作(Insert)
    Trie

    假设现在有个这样的Trie树(如图),tot=14(已有14个子节点);

    我们往树里再插入这两个单词(bcd、bca)(其实插入时就这两种类型);
    ①先看第一个bcd:第一个字符‘b’,根节点有这个孩子(‘b’),即trie[root][b]!=0(root=0),那么就不用再添加了,直接往下走,root= trie[root][b];
    第二个字符‘c’,巧了也有这个节点trie[root][c]!=0,还是直接往下走,root= trie[root][c];
    第三个字符‘d’,树上也有这个节点,trie[root][c]!=0,所以一个新节点也不需要加,那么这个’d’为“bcd”的终止节点,记isend[root]==true(此时root为‘b’的编号,下同),插入完成。

    ②在看第二个字符串“bca”,前两位与第一个字符相同,就不啰嗦了,到第三个节点时,发现这一路上没有‘c’这个节点,trie[root][c]==0(不要说旁边路上有‘c’节点啊,那是别人家的孩子!),那么我们就需要添加新节点‘c’,trie[root][c]现在等于0,我们给他一个编号(++tot),这是它的专属编号了,并且这个节点为终止节点,记isend[root]==true;插入完成。

    (其实看懂这些,查找操作也就懂了)
     
    void Insert(char *s,int root)
    {
        for(int i=0; s[i]; i++)
        {
            int x=s[i]-'a';
            if(trie[root][x]==0)//现在插入的字母在之前同一节点处未出现过
            {
                trie[root][x]=++tot;//字母插入一个新的位置,否则不做处理
            }
            root=trie[root][x];//为下个字母的插入做准备
        }
        //isend[root]=true;标志该单词末位字母的尾结点,在查询整个单词时用到
    }

     

 

  • 查找操作(Find)
    bool Find(char *s,int root)
    {
        for(int i=0; s[i]; i++)
        {
            int x=s[i]-'a';
            if(trie[root][x]==0)return false;//以root为头结点的x字母不存在,返回0
            root=trie[root][x];//为查询下个字母做准备
        }
        return true;
        //查询整个单词时,应该return isend[root]
    }

     

六、例题

  1.  统计难题 HDU - 1251(题目链接戳这里)板子题(裸)
    题意:Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). 
    #include <set>
    #include <map>
    #include <cmath>
    #include <stack>
    #include <queue>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstring>
    #include <sstream>
    #include <iomanip>
    #include <iostream>
    #include <algorithm>
    #define clr(str,x) memset(str,x,sizeof(str))
    #define FRER() freopen("in.txt","r",stdin);
    #define FREW() freopen("out.txt","w",stdout);
    #define INF 0x3f3f3f3f
    #define maxn 400000
    
    typedef long long int ll;
    using namespace std;
    int trie[maxn][26];
    int tot=1;
    int sum[maxn];
    void Insert(char *s)
    {
        int n=strlen(s);
        int root=0;
        for(int i=0; i<n; i++)
        {
            int x=s[i]-'a';
            if(trie[root][x]==0)
                trie[root][x]=tot++;
            sum[trie[root][x]]++;
            root=trie[root][x];
        }
    }
    int Find(char *ss)
    {
        int n=strlen(ss);
        int root=0;
        for(int i=0; i<n; i++)
        {
            int x = ss[i] - 'a';
            if(trie[root][x]==0)
                return 0;
            root=trie[root][x];
        }
        return sum[root];
    }
    int main()
    {
        //FRER()
        //FREW()
        char s[12];
        while(cin.getline(s,sizeof(s)))
        {
            if(strlen(s)==0)
                break;
            Insert(s);
        }
        while(cin>>s)
            cout<<Find(s)<<endl;
        return 0;
    }
    

     

  2.  Xor Sum HDU - 4825(题目链接戳这里)
    题意:Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么? 
    题解:建树,把数字转化为二进制,二进制不足35位(大于32位即可)的在前面填充上‘0’,存入树中;
               求解时,把要求的数字转化为二进制(同上),开始查找(详细可看代码注释)
    #include <set>
    #include <map>
    #include <cmath>
    #include <stack>
    #include <queue>
    #include <vector>
    #include <string>
    #include <cstdio>
    #include <cstring>
    #include <sstream>
    #include <iomanip>
    #include <iostream>
    #include <algorithm>
    #define clr(str,x) memset(str,x,sizeof(str))
    #define FRER() freopen("in.txt","r",stdin);
    #define FREW() freopen("out.txt","w",stdout);
    #define INF 0x3f3f3f3f
    #define maxn 3000055
    
    typedef long long int ll;
    using namespace std;
    char s[40],t[40];
    ll trie[3000055][5];
    ll len,tot=1;
    map<int,int> sum;
    void Insert(ll temp)
    {
        ll root=0;
        for(int i=0; i<35; i++)
        {
            int x=s[i]-'0';
            if(trie[root][x]==0)
                trie[root][x]=tot++;
            root=trie[root][x];
        }
        sum[root]=temp;
    }
    ll Get(char *s)
    {
        ll root=0;
        for(int i=0; i<35; i++)
        {
            int x=s[i]-'0';
            if(trie[root][x]==0)
            {
                //若这一节点不存在,则取反去找;(为了保证异或最大)
                root=trie[root][!x];
            }
            else
            {
                //若这一节点存在
                if(trie[root][!x])//若取反的节点存在,就去取反的那个节点(为了保证异或最大)
                    root=trie[root][!x];
                else//不存在,就继续往下走吧
                    root=trie[root][x];
            }
        }
        return sum[root];
    }
    void change(ll x)
    {
        memset(s,0,sizeof(s));
        memset(t,0,sizeof(t));
        for(int i=0; x; i++)
        {
            if(x%2==0)
                s[i]='0';
            else
                s[i]='1';
            x/=2;
        }
        int n=strlen(s);
        for(int i=n; i<35; i++)
            s[i]='0';
        for(int i=0,j=34; i<35; j--, i++)
            t[j]=s[i];
        strcpy(s,t);
    }
    int main()
    {
        int T,n1,m1;
        ll x;
        int Case=1;
        scanf("%d",&T);
        while(T--)
        {
            tot=1;
            memset(trie,0,sizeof(trie));
            sum.clear();
            scanf("%d%d",&n1,&m1);
            for(int i=0; i<n1; i++)
            {
                scanf("%lld",&x);
                change(x);
                Insert(x);
            }
            printf("Case #%d:\n",Case++);
            for(int i=0; i<m1; i++)
            {
                scanf("%lld",&x);
                change(x);
                printf("%lld\n",Get(s));
            }
        }
    
        return 0;
    }
    

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值