算法学习06: 前缀树(字典树)
前缀树(Trie树)
前缀树(Trie树)每个节点存储一个字符,从根节点向下对应一个字符串的前缀.
前缀树的用途
- 查询以某字符串开头的字符串: 可以实现,直接从根节点向下撸
- 查询是否存在"be"这个字符串? 原始的前缀树不能判断,“bef"会覆盖"be”,
因此给节点加一个数据项,表示有多少字符串以此字符结尾,这样就可以查加入了几次"be" - 再扩充一个功能? 给你一个字符串,问有多少字符串以它为前缀?
再加一个数据项: 表示每一个节点被滑过多少次.
前缀树的特点
加入
和查询
的操作与数据量无关,只与被操作字符串长度有关.
前缀树的实现leetcode 208
前缀树节点结构: 前缀树的节点为26字母之一,因此不将其字母写在节点上,而是将子节点写进一个长度为26的数组,若数组对应index
为空则说明无节点,否则存在该节点.
class TrieNode {
public int path;
public int end;
public TrieNode[] nexts;
public TrieNode() {
path = 0;
end = 0;
nexts = new TrieNode[26];
}
}
在实现前缀树时,我们并没有将字母值放在节点上,而是将其放在边上(数组或map),这样方便遍历的同时也节约了一个成员变量
public class TrieTree {
private TrieNode root; // 存储字典树根
public TrieTree() {
root = new TrieNode();
}
// 向前缀树中添加一个字符串
public void insert(String word);
// 在前缀树中查询一个字符串的出现次数
public int search(String word);
// 从前缀树中删除一个字符串
public void delete(String word);
// 查找以某字符串开头的长字符串数
public int prefixNumber(String pre);
}
下面是具体方法的实现:
-
insert(String word)
方法: 沿着要加入的字符串一路查找下去,若子节点存在就滑过去,若不存在就建立对应索引的节点. 中途经过的节点的path++,遇到末尾节点,则end++.// 向前缀树中添加一个字符串 public void insert(String word) { if (word == null) { return; } // 把字符串转化为字符数组 char[] chs = word.toCharArray(); TrieNode node = root; // 从根节点沿着字符数组向下滑 for (char ch : chs) { int index = ch - 'a'; // 若对应索引的节点不存在,则创建该节点 if (node.nexts[index] == null) { node.nexts[index] = new TrieNode(); } // 若对应索引的节点存在,则滑过该节点 // 先滑到下一个节点,然后再path++,下边两句话不能反了 node = node.nexts[index]; node.path++; } node.end++; }
-
search(String word)
: 查询字符串出现次数,遍历方式与添加相同. 中途若有节点找不到则返回0,否则找到末尾节点并返回其end
成员变量.// 在前缀树中查询一个字符串的出现次数 public int search(String word) { if (word == null) { return 0; } char[] chs = word.toCharArray(); TrieNode node = root; // 查询与添加一样,沿着字符数组一路滑下去 for (char ch : chs) { int index = ch - 'a'; if (node.nexts[index] == null) { // 若某中途节点不存在,则后边节点一定不存在,字符串出现次数为0 return 0; } node = node.nexts[index]; } // 返回以末尾节点结束的字符串的个数 return node.end; }
-
delete(String word)
方法: 先查询该字符串是否存在,若不存在,则不用删.若字符串存在,则将沿途节点出现次数减一,若某节点被删除,其子节点必然被删除,因此可以直接返回,让jvm回收其子节点.// 从前缀树中删除一个字符串 public void delete(String word) { // 先查询字符串是否存在,若不存在则不用删除了 if (search(word) != 0) { char[] chs = word.toCharArray(); TrieNode node = root; // 遍历节点,将沿途节点出现次数减一 for (char ch : chs) { int index = ch - 'a'; // 若中途某节点出现次数被减到0次,则其后节点也将不存在,直接返回即可 if (--node.nexts[index].path == 0) { node.nexts[index] = null; return; } node = node.nexts[index]; } node.end--; } }
-
prefixNumber(String pre)
方法:找到前缀的最后一个节点,返回其path
成员变量值// 查找以某字符串开头的长字符串数 public int prefixNumber(String pre) { if (pre == null) { return 0; } char[] chs = pre.toCharArray(); TrieNode node = root; // 遍历找到前缀的最后一个节点 for (char ch : chs) { int index = ch - 'a'; if (node.nexts[index] == null) { return 0; } node = node.nexts[index]; } // 返回前缀最后一个节点的path变量 return node.path; }
leetcode上的前缀树习题: leetcode 745,leetcode 14,字典树专题