当前位置:   article > 正文

字典树的姿势_字典树拍扁

字典树拍扁

姿势1:静态数组形式

  1. struct Trie
  2. {
  3. int ch[maxnode][sigema_size],val[maxnode],sz;
  4. Trie() {sz=1;memset(ch[0],0,sizeof(ch[0]));memset(val,-1,sizeof(val));}
  5. void insert(bign s,int v)
  6. {
  7. int u=0;
  8. for(int i=s.len-1;i>=max(s.len-41,0);--i)
  9. {
  10. int c=s.d[i];
  11. if(!ch[u][c])
  12. {
  13. memset(ch[sz],0,sizeof(ch[sz]));
  14. if(val[sz]==-1) val[sz]=v;
  15. ch[u][c]=sz++;
  16. }
  17. u=ch[u][c];
  18. }
  19. }
  20. int find(char *s)
  21. {
  22. int u=0;
  23. for(int i=0;s[i];++i)
  24. {
  25. int c=s[i]-'0';
  26. if(!ch[u][c]) return -1;
  27. u=ch[u][c];
  28. }
  29. return val[u];
  30. }
  31. };

优点:访问速度非常快。

缺点:需要预先估计整个字典树的节点个数,否则会RE。同时需要非常大的内存空间,也容易MLE。和姿势2一样,不适合字符集非常大的字典树。


姿势2:动态指针形式

  1. struct node{
  2. int sum;
  3. node* next[26];
  4. node(){
  5. sum = 0;
  6. memset(next,0,sizeof(next));
  7. }
  8. };
  9. struct Trie{
  10. static const int BASE = 'a';
  11. node * root;
  12. Trie(){root = new node;}
  13. ~Trie(){destroy(root);}
  14. void destroy(node * cur){
  15. for(int i = 0 ; i < 26; ++i)
  16. if(cur->next[i] != NULL)
  17. destroy(cur->next[i]);
  18. delete cur;
  19. }
  20. void insert(char * str){
  21. node* cur = root;
  22. for(char *p = str; *p; ++p){
  23. int c = *p - BASE;
  24. if(cur->next[c] == NULL)
  25. cur->next[c] = new node;
  26. cur = cur->next[c];
  27. cur->sum++;
  28. }
  29. }
  30. int query(char * str){
  31. node* cur = root;
  32. for(char *p = str; cur != NULL && *p != 0;++p)
  33. cur = cur->next[*p - BASE];
  34. if(cur == NULL)
  35. return 0;
  36. else
  37. return cur->sum;
  38. }
  39. };

优点:动态申请空间,不用考虑字典树的节点的个数;

缺点:访问速度比第一种慢。因为要保存指针数组,不适合非常大的字符集。



姿势3:左儿子右兄弟形式

  1. // 字母表为全体小写字母的Trie
  2. struct Trie {
  3. int head[maxnode]; // head[i]为第i个结点的左儿子编号
  4. int next[maxnode]; // next[i]为第i个结点的右兄弟编号
  5. char ch[maxnode]; // ch[i]为第i个结点上的字符
  6. int tot[maxnode]; // tot[i]为第i个结点为根的子树包含的叶结点总数
  7. int sz; // 结点总数
  8. long long ans; // 答案
  9. void clear() { sz = 1; tot[0] = head[0] = next[0] = 0; } // 初始时只有一个根结点
  10. // 插入字符串s(包括最后的'\0'),沿途更新tot
  11. void insert(const char *s) {
  12. int u = 0, v, n = strlen(s);
  13. tot[0]++;
  14. for(int i = 0; i <= n; i++) {
  15. // 找字符a[i]
  16. bool found = false;
  17. for(v = head[u]; v != 0; v = next[v])
  18. if(ch[v] == s[i]) { // 找到了
  19. found = true;
  20. break;
  21. }
  22. if(!found) {
  23. v = sz++; // 新建结点
  24. tot[v] = 0;
  25. ch[v] = s[i];
  26. next[v] = head[u];
  27. head[u] = v; // 插入到链表的首部
  28. head[v] = 0;
  29. }
  30. u = v;
  31. tot[u]++;
  32. }
  33. }
  34. // 统计LCP=u的所有单词两两的比较次数之和
  35. void dfs(int depth, int u) {
  36. if(head[u] == 0) // 叶结点
  37. ans += tot[u] * (tot[u] - 1) * depth;
  38. else {
  39. int sum = 0;
  40. for(int v = head[u]; v != 0; v = next[v])
  41. sum += tot[v] * (tot[u] - tot[v]); // 子树v中选一个串,其他子树中再选一个
  42. ans += sum / 2 * (2 * depth + 1); // 除以2是每种选法统计了两次
  43. for(int v = head[u]; v != 0; v = next[v])
  44. dfs(depth+1, v);
  45. }
  46. }
  47. // 统计
  48. long long count() {
  49. ans = 0;
  50. dfs(0, 0);
  51. return ans;
  52. }
  53. };
这个思路是将整个字典树用链表来存。对于每个节点,用head保存它所有儿子的链表的头,即左儿子。在利用这个头,去访问他的所有其他的儿子,即右兄弟。 


姿势4:map

  1. struct Trie{
  2. map<char,Trie> next;
  3. map<char,int> times;
  4. void insert(char *s){
  5. if(*s == 0)
  6. return;
  7. else {
  8. times[*s]++;
  9. next[*s].insert(s+1);
  10. }
  11. }
  12. int find(char *s){
  13. if(next.count(*s) == 0)
  14. return 0;
  15. else if(s[1] == 0)
  16. return times[*s];
  17. else return next[*s].find(s+1);
  18. }
  19. };

 优点:好写,在满足时间要求的情况下,代码量少。动态分配内存,不容易MLE

缺点:速度慢,找到每个元素会比数组多花费时间。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/994711
推荐阅读
相关标签
  

闽ICP备14008679号