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 }

浙公网安备 33010602011771号