Trie树

  Trie树是一种前缀树,一种高效存储和查询字符串的数据结构,用来处理字符串前缀的出现问题,或者异或对等问题。

例如我们有字符串:abc,ac,ad,ac,ba,bd,bad,cad,cb,da,dab。要在这些字符串中找出ac这个字符串的个数。第一个想法就是暴力搜索,用目标串去和每一个字符串比较,用一个ans存储答案。显然暴力的做法在字符串数量变多的情况下是会超时的。所以我们这时候想到用trie树。

  一般用到trie树的题目,要么都是小写字母,或者都是大写字母,或者都是数字,并且字符的数量不会太多。

一:求字符串出现次数

 

对于上面的这个题目,首先考虑建树的过程,我们是要找出在字符串集合里某个字符创出现的个数,用一个cnt数组来记录其出现的个数。在一个字符串的结束位置,将cnt数组加1。如下:

1 void insert(char s[]){
2     int p = 0;
3     for(int i = 0 ; s[i] ; i++){
4         int u = s[i]-'a';
5         if(!t[p][u])t[p][u] = ++idx;
6         p = t[p][u];
7     }
8     cnt[p]++;
9 }

由于我们用一个idx来作为每一个字符串的标记,所以不同的字符串的标记是唯一的。所以每个字符串结束时候的cnt就是该字符串出现的次数。

接着是查找过程。我们依旧从p = 0根节点开始,依次跳转到该字符串的下一个字符的位置上,如果下一个字符的位置不存在为0,那么说明这个字符串在所有字符串集合里面都不存在,反之,这个字符串存在,结束时候的cnt就是该字符串出现的次数。代码如下:

1 int query(char s[]){
2     int p = 0;
3     for(int i = 0; s[i]; i ++){
4         int u = s[i]-'a';
5         if(!t[p][u])return false;
6         else p = t[p][u];
7     }
8     return cnt[p];
9 }

 

 完整代码 :

 

 1 #include <iostream>
 2 #include <cstdio>
 3 
 4 using namespace std;
 5 
 6 const int N = 1e6+10;
 7 
 8 int t[N][26],idx,cnt[N];
 9 char str[N];
10 
11 void insert(char s[]){
12     int p = 0;
13     for(int i = 0 ; s[i] ; i++){
14         int u = s[i] - 'a';
15         if(!t[p][u])t[p][u] = ++ idx;//如果是idx++,会有0,会和初始化的0混在一起导致错误
16         p = t[p][u];
17     }
18     cnt[p]++;
19 }
20 
21 int query(char s[]){
22     int p = 0;
23     for(int i = 0 ; s[i] ; i ++){
24         int u = s[i]- 'a';
25         if(!t[p][u])return false;
26         else p = t[p][u];
27     }
28     return cnt[p];
29 }
30 int main(){
31     int m ;
32     cin >> m;
33     while(m--){
34         char c[2];
35         scanf("%s%s", c, str);
36         if(c[0] == 'I')insert(str);
37         else cout << query(str) << endl;
38     }
39     return 0;
40 }

 

 

 

 

 如果是求前缀出现的次数,只需要再每次转换节点的后面cnt数组加1就可。如下:

 1 void insert(char s[])
 2 {
 3     int p = 0;
 4     for(int i = 0 ; s[i] ; i ++)
 5     {
 6         int u = s[i] - 'a';
 7         if(!t[p][u])t[p][u] = ++ idx ;
 8         p = t[p][u];
 9         cnt[p] ++;
10     }
11 }

 

 二:求该字符集合内的字符串作为前缀在模式字符里面出现的次数

 

 建树并无差别,但是查询的时候需要在trie树上跳转,只要当前的模式字符串还没结束,就将沿途中的所有cnt数组的值加起来。

完整代码:

 

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 1e6+10;
 8 int t[N][26], cnt[N], idx;
 9 
10 void insert(char s[])
11 {
12     int p = 0;
13     for(int i = 0 ; s[i] ; i ++)
14     {
15         int u = s[i] - 'a';
16         if(!t[p][u])t[p][u] = ++ idx ;
17         p = t[p][u];
18     }
19     cnt[p] ++;
20 }
21 
22 int query(char s[])
23 {
24     int p = 0, ans = 0;
25     for(int i = 0 ; i < strlen(s) ; i ++)
26     {
27         int u = s[i] - 'a';
28         p = t[p][u];
29         if(p == 0)
30             return ans;
31         ans += cnt[p];
32     }
33     return ans;
34 
35 }
36 
37 
38 int main(){
39     int n, m;
40     cin >> n >> m;
41     while(n --)
42     {
43         char str[1000010];
44         cin >> str;
45         insert(str);
46     }
47 
48     while(m --)
49     {
50            char str[1000010];
51            cin >> str;
52            cout << query(str) << endl;
53     }
54     return 0;

 

 

三:求最大异或对

 

 求最大的异或对,想法是对于当前的一个数,我们将其拆分为二进制的数,如1就是1,2是10,因为最大的数是2^31,所以我们从最大的一位看起,每次都找和目标数异或为1的方向走,如果只有一条路,就只能沿这条路走。最终获得的数就是和当前数异或最大的数。

完整代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 31, M = 1e5+10;;
 7 
 8 int t[M * 31][2];
 9 int a[M];
10 int idx;
11 
12 void insert(int x)
13 {
14     int p = 0;
15     for(int i = 30 ; i >= 0 ; i --)
16     {
17         int u = x >> i & 1;
18         if(!t[p][u])t[p][u] = ++ idx;
19         p = t[p][u];
20     }
21 }
22 
23 int query(int x)
24 {
25     int p = 0, res = 0;
26     for(int i = 30 ; i >= 0 ; i --)//从最高位开始走
27     {
28         int u = x >> i & 1;
29         if(t[p][!u])
30         {
31             p = t[p][!u];
32             res = res * 2 + !u;//如果往这条路走,就将数*2在加上当前的数字
33         }
34         else
35         {
36             p = t[p][u];
37             res = res * 2 + u;
38         }
39     }
40     return res;
41 }
42 
43 int main(){
44     int n;
45     cin >> n;
46     for(int i = 0 ; i < n ; i ++)cin >> a[i];
47     
48     int ans = 0;
49     for(int i = 0 ; i < n ; i ++)
50     {
51         insert(a[i]);//先插入在查询,防止第一次是空树无法查询出错,并且和自己的异或为0,不影响结果
52         int t = query(a[i]);//t就是我们找到的配对
53         ans = max(ans, a[i] ^ t);
54     }
55     cout << ans << endl;
56     return 0;
57 }
posted @ 2019-11-27 23:00  dzcixy  阅读(227)  评论(0)    收藏  举报