介绍 Trie (发音为 “try”) 或前缀树是一种树数据结构,用于检索字符串数据集中的键。这一高效的数据结构有多种应用:
自动补全
谷歌搜索建议
拼写检查
文字处理软件中的拼写检查
IP 路由 (最长前缀匹配)
使用Trie树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径
T9 (九宫格) 打字预测
T9(九宫格输入),在 20 世纪 90 年代常用于手机输入。
单词游戏
Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏
还有其他的数据结构,如平衡树和哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 Trie 树呢?尽管哈希表可以在 O (1) 时间内寻找键值,却无法高效的完成以下操作:
找到具有同一前缀的全部键值。
按词典序枚举字符串的数据集。
Trie 树优于哈希表的另一个理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 O (n ),其中 n 是插入的键的数量。与哈希表相比,Trie 树在存储多个具有相同前缀的键时可以使用较少的空间。此时 Trie 树只需要 O (m ) 的时间复杂度,其中 m 为键长。而在平衡树中查找键值需要O (m logn ) 时间复杂度。
数据结构图
Problem Description 实现一个 Trie (前缀树),包含 insert
, search
, 和 startsWith
这三个操作。
note
你可以假设所有的输入都是由小写字母 a-z
构成的。
保证所有输入均为非空字符串。
e.g. 1 2 3 4 5 6 7 8 Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); // 返回 true trie.search("app"); // 返回 false trie.startsWith("app"); // 返回 true trie.insert("app"); trie.search("app"); // 返回 true
Solution 利用前缀树就能很好的解决这一题 #208 实现 Trie (前缀树) 。
首先构建一个前缀树的数据结构模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static class TireTree { private boolean isEnd; private final TireTree[] data; private Character ele; private final static int CAPACITY = 26 ; public TireTree (char ele) { this .ele = ele; this .data = new TireTree [CAPACITY]; this .isEnd = false ; } @Override public String toString () { return "TireTree{" + "isEnd=" + isEnd + ", data=" + Arrays.toString(data) + ", ele=" + ele + '}' ; } }
当然还可以扩展出set
、get
方法以供方便调用。
继续完善解决该题,难点在于insert
方法,当插入某串字符串的子串时,需要将末尾节点的isEnd
标记为置true
;如果出现新的字符,需要插入一个新的节点。search
方法只要递归判断末尾节点标记位是否为true
即可,而startsWith
只要递归判断prefix
能否在该树下走完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class ImplementTriePrefixTreeII { static class TireTree { private boolean isEnd; private final TireTree[] data; private Character ele; private final int CAPACITY = 26 ; public TireTree (char ele) { this .ele = ele; data = new TireTree [CAPACITY]; isEnd = false ; } @Override public String toString () { return "TireTree{" + "isEnd=" + isEnd + ", data=" + Arrays.toString(data) + ", ele=" + ele + '}' ; } } TireTree tireTree; public ImplementTriePrefixTreeII () { tireTree = new TireTree ('$' ); } private void insert (TireTree tireTree, String target, int index) { if (index >= target.length()) { return ; } char curr = target.charAt(index); TireTree currTree = tireTree.data[curr - 'a' ]; if (currTree == null ) { tireTree.data[curr - 'a' ] = new TireTree (curr); currTree = tireTree.data[curr - 'a' ]; currTree.ele = curr; } if (currTree.ele == curr && index == target.length() - 1 ) { currTree.isEnd = true ; return ; } insert(currTree, target, index + 1 ); } private boolean search (TireTree tireTree, String target, int index, boolean isPrefix) { char curr = target.charAt(index); TireTree currTree = tireTree.data[curr - 'a' ]; if (currTree == null ) { return false ; } else { if (currTree.ele == curr) { if (index == target.length() - 1 ) { return isPrefix || currTree.isEnd; } else { return search(currTree, target, index + 1 , isPrefix); } } else { return false ; } } } public void insert (String word) { TireTree tmp = tireTree; insert(tmp, word, 0 ); } public boolean search (String word) { TireTree tmp = tireTree; return search(tmp, word, 0 , false ); } public boolean startsWith (String prefix) { TireTree tmp = tireTree; return search(tmp, prefix, 0 , true ); } }