题目描述(中等难度)

208. Implement Trie(Prefix Tree) - 图1

实现 Trie 数,即前缀树。trie的发明者 Edward Fredkin 把它读作 /ˈtriː/ "tree"。但是,其他作者把它读作/traɪ/"try"

解法一

算作一个高级的数据结构,实现方式可以通过 26 叉树。每个节点存一个字母,根节点不存字母。

  1. "app" "as" "cat" "yes" "year" "you"
  2. root
  3. / | \
  4. a c y
  5. / \ | / \
  6. p s a e o
  7. / | / \ \
  8. p t s a u
  9. |
  10. r
  11. 上图中省略了 null 节点,对于第一层画完整了其实是下边的样子, 图示中用 0 代表 null
  12. root
  13. / | | | | | | | | | | | | | | | | | | | | | | | | \
  14. a 0 c 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 y 0
  15. 其它层同理

代码的话,我们定义一个节点,每个节点包含一个节点数组,代表 26 个孩子。此外,还需要一个 flag 变量来标记当前节点是否是某个单词的结束。

  1. class TrieNode {
  2. TrieNode[] children;
  3. boolean flag;
  4. public TrieNode() {
  5. children = new TrieNode[26];
  6. flag = false;
  7. //节点初始化为 null
  8. for (int i = 0; i < 26; i++) {
  9. children[i] = null;
  10. }
  11. }
  12. }

然后只需要实现题目中所需要的三个函数即可。其中 children[0]achildren[1]bchildren[2]c… 依次类推。所以存的时候我们用当前字符减去 a ,从而得到相应的 children 下标。

  1. class Trie {
  2. class TrieNode {
  3. TrieNode[] children;
  4. boolean flag;
  5. public TrieNode() {
  6. children = new TrieNode[26];
  7. flag = false;
  8. for (int i = 0; i < 26; i++) {
  9. children[i] = null;
  10. }
  11. }
  12. }
  13. TrieNode root;
  14. /** Initialize your data structure here. */
  15. public Trie() {
  16. root = new TrieNode();
  17. }
  18. /** Inserts a word into the trie. */
  19. public void insert(String word) {
  20. char[] array = word.toCharArray();
  21. TrieNode cur = root;
  22. for (int i = 0; i < array.length; i++) {
  23. //当前孩子是否存在
  24. if (cur.children[array[i] - 'a'] == null) {
  25. cur.children[array[i] - 'a'] = new TrieNode();
  26. }
  27. cur = cur.children[array[i] - 'a'];
  28. }
  29. //当前节点代表结束
  30. cur.flag = true;
  31. }
  32. /** Returns if the word is in the trie. */
  33. public boolean search(String word) {
  34. char[] array = word.toCharArray();
  35. TrieNode cur = root;
  36. for (int i = 0; i < array.length; i++) {
  37. //不含有当前节点
  38. if (cur.children[array[i] - 'a'] == null) {
  39. return false;
  40. }
  41. cur = cur.children[array[i] - 'a'];
  42. }
  43. //当前节点是否为是某个单词的结束
  44. return cur.flag;
  45. }
  46. /**
  47. * Returns if there is any word in the trie that starts with the given
  48. * prefix.
  49. */
  50. public boolean startsWith(String prefix) {
  51. char[] array = prefix.toCharArray();
  52. TrieNode cur = root;
  53. for (int i = 0; i < array.length; i++) {
  54. if (cur.children[array[i] - 'a'] == null) {
  55. return false;
  56. }
  57. cur = cur.children[array[i] - 'a'];
  58. }
  59. return true;
  60. }
  61. };

只要知道每个节点存字母,路径代表单词,代码就很好写出来了。

前缀树适用于两个场景。

  • 统计每个单词出现的次数,代码的话只需要将上边的 flag 改成 int 类型,然后每次插入的时候计数即可。

    当然,我们用 HashMap 也可以做到,key 是单词,value 存这个单词出现的次数即可。但缺点是,当单词很多很多的时候,受到 Hash 函数的影响,hash 值会经常出现冲突,算法可能退化为 O(n)nkey 的总数。

    而对于前缀树,我们查找一个单词出现的次数,始终是 O(m)m 为单词的长度。

  • 查找某个前缀的单词,最常见的比如搜索引擎的提示、拼写检查、ip 路由等。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情