Trie 字典树
1.1、定义
字典树,顾名思义,是关于“字典”的一棵树。即:它是对于字典的一种存储方式(所以是一种数据结构而不是算法)。这个词典中的每个“单词”就是从根节点出发一直到某一个目标节点的路径,路径中每条边的字母连起来就是一个单词。
1.2、字典树描述
其结点具有以下字段:。
最多 RR 个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。
本文中假定 RR 为 26,小写拉丁字母的数量。
布尔字段,以指定节点是对应键的结尾还是只是键前缀。
树中黄色节点表示目标节点,在此节点以上可组成一个单词
// 可获取如下字符串
a
abc
bac
bbc
ca
1.3、使用场景
字典树的本质是把很多字符串拆成单个字符的形式,以树的方式存储起来。所以我们说字典树维护的是”字典“。那么根据这个最基本的性质,我们可以由此延伸出字典树的很多妙用。简单总结起来大体如下:
- 1、维护字符串集合(即字典)。
- 2、向字符串集合中插入字符串(即建树)。
- 3、查询字符串集合中是否有某个字符串(即查询)。
- 4、统计字符串在集合中出现的个数(即统计)。
- 5、将字符串集合按字典序排序(即字典序排序)。
- 6、求集合内两个字符串的LCP(Longest Common Prefix,最长公共前缀)(即求最长公共前缀)。
自动补全
拼写检查
IP路由(最长前缀匹配)
打字预测
单词游戏
匹配过程
Java实现 Trie 字典树
2.1、数据结构
Java代码实现
public class TrieNode{
// 建立数组存储子字符串(建立Node)
private TrieNode[] links;
// 由于一个单词(小写),最多只有26个字母
private final int R = 26 ;
//判断当前节点是否为结束节点
private boolean isEnd;
// 构造函数
public TireNode(){
links = new TrieNode[R];
}
public boolean containsKey(char ch) {
return links[ch -'a'] != null;
}
public TrieNode get(char ch) {
return links[ch -'a'];
}
public void put(char ch, TrieNode node) {
links[ch -'a'] = node;
}
public void setEnd() {
isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
2.2、数据操作
对于树这种数据结构,很多时候我们都比较关注与两点:“查询” 与 “ 插入”数据
2.2.1、插入数据
我们通过搜索 Trie 树来插入一个键。我们从根开始搜索它对应于第一个键字符的链接。有两种情况:
补充
每个键在 trie 中表示为从根到内部节点或叶的路径。
1、链接存在。沿着链接移动到树的下一个子层。算法继续搜索下一个键字符。
2、链接不存在。创建一个新的节点,并将它与父节点的链接相连,该链接与当前的键字符相匹配。
重复以上步骤,直到到达键的最后一个字符,然后将当前节点标记为结束节点,算法完成。
如图
Java代码实现
public class Trie{
private TrieNode root ;
public Trie(){
root = new TrieNoe();
}
// 插入数据
public void insert(String word){
TrieNode node = root ;
for(int i=0;i<word.length() ;i++){
char currentChar = word.charAt(i);
if(!node.containKeys(currentChar)){
node.put(currentChar,new TrieNode());
}
node = node.get(currentChar);
}
node.isEnd();
}
}
2.2.2、查询数据
每个键在 trie 中表示为从根到内部节点或叶的路径。我们用第一个键字符从根开始,。检查当前节点中与键字符对应的链接。有两种情况:
1、存在链接。我们移动到该链接后面路径中的下一个节点,并继续搜索下一个键字符。
2、不存在链接。若已无键字符,且当前结点标记为 isEnd,则返回 true。否则有两种可能,均返回 false :
(1)、还有键字符剩余,但无法跟随 Trie 树的键路径,找不到键。
(2)、没有键字符剩余,但当前结点没有标记为 isEnd。也就是说,待查找键只是Trie树中另一个键的前缀。
如图
public class Trie{
private TrieNode root ;
public Trie(){
root = new TrieNoe();
}
// 查询数据,并返回结束时的Node点
public TrieNode searchPrefix(String word){
TrieNode node = root ;
for(inti = 0 ;i<word.length();i++){
char currentChar = word.charAt(i);
if(node.containKeys(currentChar)){
node = node.get(currentChar);
}else{
return null;
}
}
return node ;
}
// 判断查询结束时,返回node节点为目标节点且不为空
public boolean search(String word){
TireNode node = searchPrefix(word);
return node !=null &&node.isEnd();
}
}
扩展
查询Trie树的键前缀
该方法与在 Trie 树中搜索键时使用的方法非常相似。我们从根遍历 Trie 树,直到键前缀中没有字
符,或者无法用当前的键字符继续 Trie 中的路径。与上面提到的“搜索键”算法唯一的区别是,到
达键前缀的末尾时,总是返回 true。我们不需要考虑当前 Trie 节点是否用 “isend” 标记,因为我
们搜索的是键的前缀,而不是整个键。
如图
Java代码实现
public boolean startsWith(String prefix){
TrieNode node = searchPrefix(prefix);
return node!=null;
}