Trie树(字典树)从懵逼到入门

来源:互联网 发布:摄影灯品牌 知乎 编辑:程序博客网 时间:2024/06/17 02:55

//字典树是个好东西...也是个比较基础的东西,最近经常用到所以写个总结给自己看看...顺便分享一下求大佬指教


1.定义:Trie树

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

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

它有3个基本性质:

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

简单实现:单词列表为”apps”,”apply”,”apple”,”append”,”back”,”backen”以及”basic”对应的字母树可以是如下图所示。

这里写图片描述

例如,保存”apple”和 “apply”时,由于它们的前四个字母是相同的,所以希望它们共享这些字母,而只对剩下的部分进行分开存储。可以很明显地发现,字母树很好地利用了串的公共前缀,节约了存储空间。

查询操作也非常简单,例如需要查询apple那么只要沿着a-ap-app-appl-apple的路径就可以了


 2.应用

(1)串的快速检索

给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。 
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。

(2)“串”排序

给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。

(3)最长公共前缀

对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。

3.实现

字典树的插入(Insert)、删除( Delete)和查找(Find )都非常简单,用一个一重循环即可,即第 i 次循环找到前 i 个字母所对应的子树,然后进行相应的操作。实现这棵字典树,我们用最常见的数组保存即可,当然也可以开动态的指针类型。至于结点对儿子的指向,一般有三种方法: 
(1)对每个结点开一个字母集大小的数组,对应的下标是儿子所表示的字母,内容则是这个儿子对应在大数组上的位置,即标号; 
(2)对每个结点挂一个链表,按一定顺序记录每个儿子是谁; 
(3)使用左儿子右兄弟表示法记录这棵树。 
三种方法,各有千秋。第一种易实现,但实际的空间要求较大;第二种, 
较易实现,空间要求相对较小,但比较费时;第三种,空间要求最小,但相对费时且不易写。但总的来说,几种实现方式都是比较简单的,只要在做题时加以合理选择即可。

4.缺点

字典树的缺点很明显,用大量的空间换取时间,那么显然消耗的内存空间是非常巨大的。

比如小写字母的字典树,每个节点最多可能存在26个子节点,指数级增长的内存消耗


字典树的基本实现:

#include <iostream>   #include<cstdlib>   #define MAX 26   using namespace std;       typedef struct TrieNode                     //Trie结点声明    {       bool isStr;                            //标记该结点处是否构成单词        struct TrieNode *next[MAX];            //儿子分支    }Trie;       void insert(Trie *root,const char *s)     //将单词s插入到字典树中    {       if(root==NULL||*s=='\0')           return;       int i;       Trie *p=root;       while(*s!='\0')       {           if(p->next[*s-'a']==NULL)        //如果不存在,则建立结点            {               Trie *temp=(Trie *)malloc(sizeof(Trie));               for(i=0;i<MAX;i++)               {                   temp->next[i]=NULL;               }               temp->isStr=false;               p->next[*s-'a']=temp;               p=p->next[*s-'a'];              }              else          {               p=p->next[*s-'a'];           }           s++;       }       p->isStr=true;                       //单词结束的地方标记此处可以构成一个单词    }       int search(Trie *root,const char *s)  //查找某个单词是否已经存在    {       Trie *p=root;       while(p!=NULL&&*s!='\0')       {           p=p->next[*s-'a'];           s++;       }       return (p!=NULL&&p->isStr==true);      //在单词结束处的标记为true时,单词才存在    }       void del(Trie *root)                      //释放整个字典树占的堆区空间    {       int i;       for(i=0;i<MAX;i++)       {           if(root->next[i]!=NULL)           {               del(root->next[i]);           }       }       free(root);   }       int main(int argc, char *argv[])   {       int i;       int n,m;                              //n为建立Trie树输入的单词数,m为要查找的单词数        char s[100];       Trie *root= (Trie *)malloc(sizeof(Trie));       for(i=0;i<MAX;i++)       {           root->next[i]=NULL;       }       root->isStr=false;       scanf("%d",&n);       getchar();       for(i=0;i<n;i++)                 //先建立字典树        {           scanf("%s",s);           insert(root,s);       }       while(scanf("%d",&m)!=EOF)       {           for(i=0;i<m;i++)                 //查找            {               scanf("%s",s);               if(search(root,s)==1)                   printf("YES\n");               else                  printf("NO\n");           }           printf("\n");          }       del(root);                         //释放空间很重要        return 0;   }  



//hdu6059

Kanade's trio

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 255    Accepted Submission(s): 74


Problem Description
Give you an array A[1..n],you need to calculate how many tuples (i,j,k) satisfy that (i<j<k) and ((A[i] xor A[j])<(A[j] xor A[k]))

There are T test cases.

1T20

1n5105

0A[i]<230
 

Input
There is only one integer T on first line.

For each test case , the first line consists of one integer n ,and the second line consists of n integers which means the array A[1..n]
 

Output
For each test case , output an integer , which means the answer.
 

Sample Input
151 2 3 4 5
 

Sample Output
6
 
【分析】
字典树.....枚举a[j],考虑1~j-1维护一棵字典树,j+1~n维护一棵字典树,每次计算a[j]的每一位对应的两棵树的每一层满足后缀树^(a[j]^(1<<k))>前缀树^(a[j]^(1<<k))的方案数,也就是当前位置如果是0,就计算后缀树当前层中1的数量和前缀树当前层中0的数量
#include <cmath>  #include <cstdio>  #include <cstdlib>  #include <cstring>  #include <iostream>  #include <algorithm>  #define X 33  #define N 600000  #define M 20000000  using namespace std;  int n, a[N], cnt, f[M][3], s[X+3], trans[M];  long long ans_case, ans[X+3][3], g[M][3];    void update(int pos,int x,int modi,int t,int dep)  //当前节点pos,当前位01状态x,add/delete状态modi   //前缀树和后缀树区分t(0/1),层数dep   {      g[pos][t]+=modi;//正常更新当前节点数量       ans[dep][x]+=modi*g[trans[pos]][t^1];      //计算dep层中,后缀树中当前节点为x,前缀树中对应x^1的方案数   }  void add(int x,int t)  {      int j=0, ct=0;      while (x)      {          s[++ct]=x&1;          x>>=1;      }      for (int i=ct+1;i<=X;i++) s[i]=0;      for (int i=X;i;i--)      {          if (!f[j][s[i]])          {              f[j][s[i]]=++cnt;              if (f[j][s[i]^1])              {                  trans[cnt]=f[j][s[i]^1];                  //记录当前cnt节点在另一棵树中对应的节点编号,用于后面计算答案                   trans[f[j][s[i]^1]]=cnt;              }          }          j=f[j][s[i]];          update(j,s[i]^t,1,t,i);      }  }  void del(int x,int t)  {      int j=0, ct=0;      while (x)      {          s[++ct]=x&1;          x>>=1;      }      for (int i=ct+1;i<=X;i++) s[i]=0;      for (int i=X;i;i--)      {          j=f[j][s[i]];          update(j,s[i]^t,-1,t,i);      }  }  void solve(int x)  {      int j=0, ct=0;      while (x)      {          s[++ct]=x&1;          x>>=1;      }      for (int i=ct+1;i<=X;i++) s[i]=0;      for (int i=X;i;i--) ans_case+=ans[i][s[i]];  }  int main()  {      int T_T;scanf("%d",&T_T);      while (T_T--)      {          cnt=ans_case=0;          scanf("%d",&n);          for (int i=1;i<=n;i++) scanf("%d",&a[i]);                    if (n==1 || n==2){cout<<0<<endl;continue;}          //高中生的题目....总有些坑留在这           for (int i=1;i<=n;i++) add(a[i],1);          del(a[1],1);add(a[1],0);          for (int i=2;i<=n;i++)          {              del(a[i],1);              solve(a[i]);              add(a[i],0);          }          cout<<ans_case<<endl;          for (int i=1;i<=X;i++) ans[i][0]=ans[i][1]=0;          for (int i=0;i<=cnt;i++) f[i][0]=f[i][1]=g[i][0]=g[i][1]=trans[i]=0;      }      return 0;  }  


原创粉丝点击