数组形式

image.png

  • 高效的存储和查找字符串集合的数据结构。
  • 明白上面的存储结构,下面的代码其实有几个地方感觉很别扭,必P = son[p][u],实际检验过了,没什么错误,但是理解起来确实比较别扭。
  • image.png作为理解来讲,这三个已经非常的清楚表达son的含义,其中idx本质上就是结点的下标,其中cnt[x]本质上是以x作为结点的单词有多少。其实根据这个例子我感觉son[i][j],i本质上代表i的一个子结点。
  • trie树适合的题目主要是针对26个或者52英文字母的
  1. #include <iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10; // 没轮次输入的字符串的长度
  4. int n;
  5. char str[N];
  6. int idx; // 这个idx很难理解,其实是针对每一个加入的新的结点,基于一个新的编号。这个编号是一次递增的
  7. int son[N][26]; // 每个结点下面可能有26个英文字母的结点
  8. int cnt[N]; // 通过idx每一个结点都有一个编号,每一轮次都是一个字符串,字符串的肯定需要标记结尾
  9. //,cnt【p】++就是结尾轮次的表示。
  10. void insert(char str[]) {
  11. int p = 0; // 从root开始
  12. for (int i = 0; str[i]; i++)
  13. {
  14. int u = str[i] - 'a'; // 定位字符串是当前结点的第a b c d 。。。
  15. if (!son[p][u]) son[p][u] = ++idx; // 结点不存在的情况,不存在,给结点赋值为一次递增的序列号
  16. p = son[p][u]; // 跳到下一个结点
  17. }
  18. cnt[p]++;
  19. }
  20. int query(char str[])
  21. {
  22. int p = 0;
  23. for (int i = 0; str[i]; i++)
  24. {
  25. int u = str[i] - 'a';
  26. if (!son[p][u]) return 0;
  27. p = son[p][u];
  28. }
  29. return cnt[p];
  30. }
  31. int main() {
  32. ios::sync_with_stdio(false);
  33. cin >> n;
  34. while (n--)
  35. {
  36. char op[2];
  37. cin >> op;
  38. cin >> str;
  39. if (op[0] == 'I') insert(str);
  40. else cout << query(str) << endl;
  41. }
  42. return 0;
  43. }

链表形式

  1. class Trie {
  2. private:
  3. // 判断当前结点是不是一个单词的最后一个字母
  4. bool isEnd;
  5. // 要求小写的英文字母,一共26个
  6. Trie* next[26];
  7. public:
  8. Trie()
  9. {
  10. // 每个结点初始化的时候,都必须赋值非终点结点
  11. isEnd = false;
  12. // 指针全部初始化为空
  13. memset(next, 0, sizeof next);
  14. }
  15. void insert(string word)
  16. {
  17. Trie* node = this;
  18. for (int i = 0; i < word.size(); i++)
  19. {
  20. //插入碰到的空的时候,需要构造新的结点。
  21. if (node -> next[word[i] - 'a'] == NULL)
  22. {
  23. node -> next[word[i] - 'a'] = new Trie();
  24. }
  25. node = node -> next[word[i] - 'a'];
  26. }
  27. // 最后一个结点 必须标识为终点
  28. node -> isEnd = true;
  29. }
  30. bool search(string word)
  31. {
  32. Trie* node = this;
  33. for (int i = 0; i < word.size(); i++)
  34. {
  35. // 查找的时候只要找不到直接返回false
  36. if (node -> next[word[i] - 'a'] == NULL)
  37. return false;
  38. node = node -> next[word[i] - 'a'];
  39. }
  40. // 即使没有碰到空的情况也必须判断这个结点是不是终点
  41. return node -> isEnd;
  42. }
  43. // 判断prefix是不是已经存在字典树的前缀
  44. bool startsWith(string prefix)
  45. {
  46. Trie* node = this;
  47. for (int i = 0; i < prefix.size(); i++)
  48. {
  49. if (node -> next[prefix[i] - 'a'] == NULL)
  50. return false;
  51. node = node -> next[prefix[i] - 'a'];
  52. }
  53. //不需要判断是不是终点,这是判断前缀
  54. return true;
  55. }
  56. // 析构函数,删除当前结点指向的所有的结点
  57. ~Trie()
  58. {
  59. Trie* node = new Trie();
  60. for (int i = 0; i < 26; i++)
  61. {
  62. if (node -> next[i] != NULL)
  63. delete node -> next[i];
  64. }
  65. }
  66. };