zoukankan      html  css  js  c++  java
  • Trie树(字典树)

    参考博客

    一、引入

    字典是干啥的?查找字的。

    字典树自然也是起查找作用的。查找的是啥?单词。

    看以下几个题:

    1、给出n个单词和m个询问,每次询问一个单词,回答这个单词是否在单词表中出现过。

    答:简单!map,短小精悍。

    好。下一个

    2、给出n个单词和m个询问,每次询问一个前缀,回答询问是多少个单词的前缀。

    答:map,把每个单词拆开。

    judge:n<=200000,TLE!

    这就需要一种高级数据结构——Trie树(字典树)

    二、原理

    在本篇文章中,假设所有单词都只由小写字母构成

    对cat,cash,app,apple,aply,ok 建一颗字典树,建成之后如下图所示

     

    由此可以看出:

    1、字典树用边表示字母

    2、有相同前缀的单词公用前缀节点,那我们可以的得出每个节点最多有26个子节点(在单词只包含小写字母的情况下)

    3、整棵树的根节点是空的。为什么呢?便于插入和查找,这将会在后面解释。

    4、每个单词结束的时候用一个特殊字符表示,图中用的‘′,那么从根节点到任意一个‘′,那么从根节点到任意一个‘’所经过的边的所有字母表示一个单词。

    三、基本操作

    A、insert,插入一个单词

    1.思路

      从图中可以直观看出,从左到右扫这个单词,如果字母在相应根节点下没有出现过,就插入这个字母;否则沿着字典树往下走,看单词的下一个字母。

      这就产生一个问题:往哪儿插?计算机不会自己选择位置插,我们需要给它指定一个位置,那就需要给每个字母编号。

      我们设数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。

     什么意思呢?

     这里有2种编号,一种是i,k表示节点的位置编号,这是相对整棵树而言的;另一种是j,表示节点i的第j的孩子,这是相对节点i而言的。

     不理解?看图

     还是单词cat,cash,app,apple,aply,ok 

     我们就按输入顺序对其编第一种号,红色表示编号结果。因为先输入的cat,所以c,a,t分别是1,2,3,然后输入的是cash,因为c,a是公共前缀,所以从s开始编,s是4,以此类推。

    注意这里相同字母的编号可能不同

    第二种编号,相对节点的编号,紫色表示编号结果。

    因为每个节点最多有26个子节点,我们可以按他们的字典序从0——25编号,也就是他们的ASCLL码-a的ASCLL码。

    注意这里相同字母的编号相同

     实际上每个节点的子节点都应该从0编到——25,但这样会发现许多事根本用不到的。比如上图的根节点应该分出26个叉。节约空间,用到哪个分哪个。

     这样编号有什么用呢?

    回到数组trie[i][j]=k。 数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。

    那么第二种编号即为j,第一种编号即为i,k

    2、代码

     1 inline void insert(){
     2     len=strlen(s);
     3     root=0;
     4     for(int i=0;i<len;i++){
     5         int id=s[i]-'a';
     6         if(!trie[root][id])
     7                     trie[root][id]=++tot;
     8         root=trie[root][id];
     9     }
    10 }

    B、search,查找

    查找有很多种,可以查找某一个前缀,也可以查找整个单词。

    再次我们以查找一个前缀是否出现过为例讲解

    1、思路

      从左往右以此扫描每个字母,顺着字典树往下找,能找到这个字母,往下走,否则结束查找,即没有这个前缀;前缀扫完了,表示有这个前缀。

    2、代码

     1 bool find(){
     2     len=strlen(s);
     3     root=0;
     4     for(int i=0;s[i];i++){
     5         int x=s[i]-'a';
     6         if(trie[root][x]==0)return false;
     7         root=trie[root][x];
     8     }
     9     return true;
    10 }

    3、如果是查询某个单词的话,我们用bool变量 v[i]表示节点i是否是单词结束的标志。

        那么最后return的是v[root],所以在插入操作中插入完每个单词是,要对单词最后一个字母的v[i]置为true,其他的都是false

    4、如果是查询前缀出现的次数的话,那就在开一个sum[],表示位置i被访问过的次数,

       那么最后return的是sum[root],插入操作中每访问一个节点,都要让他的sum++

       这里前缀的次数是标记在前缀的最后一个字母所在位置的后一个位置上。

      比如:前缀abc出现的次数标记在c所在位置的后一个位置上,

    四、完整代码

    1、查询是否出现

     1 #include<bits/stdc++.h>
     2 #define maxn 2000010
     3 using namespace std;
     4 int tot=1,n;
     5 int trie[maxn][26];
     6 //bool isw[maxn];查询整个单词用
     7 void insert(char *s,int rt){
     8     for(int i=0;s[i];i++){
     9         int x=s[i]-'a';
    10         if(trie[rt][x]==0){
    11             trie[rt][x]=++tot;
    12         }
    13         rt=trie[rt][x]; 
    14     }
    15 }
    16 bool find(char *s,int rt){
    17     for(int i=0;s[i];i++){
    18         int x=s[i]-'a';
    19         if(trie[rt][x]==0)return false;
    20         rt=trie[rt][x];
    21     }
    22     return true;
    23 }
    24 char s[22];
    25 int main(){
    26     tot=0;
    27     int rt=1;
    28     scanf("%d",&n);
    29     for(int i=1;i<=n;i++){
    30         cin>>s;
    31         insert(s,rt);
    32     }
    33     scanf("%d",&n);
    34     for(int i=1;i<=n;i++){
    35         cin>>s;
    36         if(find(s,rt))printf("YES
    ");
    37         else printf("NO
    ");
    38     }
    39     return 0;
    40 }

    2、查询前缀出现次数

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int trie[400001][26],len,root,tot,sum[400001];
     4 bool p;
     5 int n,m; 
     6 char s[11];
     7 void insert(){
     8     len=strlen(s);
     9     root=0;
    10     for(int i=0;i<len;i++){
    11         int id=s[i]-'a';
    12         if(!trie[root][id]) trie[root][id]=++tot;
    13         sum[trie[root][id]]++;
    14         root=trie[root][id];
    15     }
    16 }
    17 int search(){
    18     root=0;
    19     len=strlen(s);
    20     for(int i=0;i<len;i++){
    21         int id=s[i]-'a';
    22         if(!trie[root][id]) return 0;
    23         root=trie[root][id];
    24     }
    25     return sum[root];
    26 }
    27 int main(){
    28     scanf("%d",&n);
    29     for(int i=1;i<=n;i++){
    30         cin>>s;
    31         insert();
    32     }
    33     scanf("%d",&m);
    34     for(int i=1;i<=m;i++){
    35         cin>s;
    36         printf("%d
    ",search());
    37     }
    38 }

    完整代码

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 char s[11];
     4 int n,m;
     5 bool p;
     6 struct node{
     7     int count;
     8     node * next[26];
     9 }*root;
    10 node * build(){
    11     node * k=new(node);
    12     k->count=0;
    13     memset(k->next,0,sizeof(k->next));
    14     return k;
    15 }
    16 void insert(){
    17     node * r=root;
    18     char * word=s;
    19      while(*word){
    20         int id=*word-'a';
    21         if(r->next[id]==NULL) r->next[id]=build();
    22         r=r->next[id];
    23         r->count++;
    24         word++;
    25     }
    26 }
    27 int search(){
    28     node * r=root;
    29     char * word=s;
    30     while(*word){
    31         int id=*word-'a';
    32         r=r->next[id];
    33         if(r==NULL) return 0;
    34         word++;
    35     }
    36     return r->count;
    37 }
    38 int main(){
    39     root=build();
    40     scanf("%d",&n);
    41     for(int i=1;i<=n;i++){
    42             cin>>s;
    43             insert();
    44     }
    45     scanf("%d",&m);
    46     for(int i=1;i<=m;i++){
    47         cin>>s;
    48         printf("%d
    ",search());
    49     }
    50 }
  • 相关阅读:
    WCF 订单服务(2)
    移动应用接口的授权和安全
    数据库服务器死锁的解决方法 (转)
    WCF 订单服务(3)
    sqlservice 表分区方法
    基于.NET解决方案的架构和框架
    IIS7架构原理
    多线程的同步和通信
    【原创】关于wince OS开发面试问题的总结系列之OAL
    【原创】关于noot的学习笔记
  • 原文地址:https://www.cnblogs.com/wangyifan124/p/10325387.html
Copyright © 2011-2022 走看看