在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。(摘自 https://zh.wikipedia.org/wiki/Trie)
视频参考:LeetCode 或 B站 https://www.bilibili.com/video/av35452288/?p=70
Trie 树的典型应用场景:
摘自原文:https://my.oschina.net/yangjiannr/blog/1528534
第一:词频统计;(一般使用 Trie 统计词频,然后用堆排序取前 n 个,词频统计常见的方法还有各种 Map 如 HashMap、TreeMap,以及MapReduce)
第二: 前缀匹配;
第三:去重
适用范围:数据量大,重复多,但是数据种类小可以放入内存
基本原理及要点:实现方式,节点孩子的表示方式
扩展:压缩实现。
面试题有:
有10个文件,每个文件1G,每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。要你按照query的频度排序。
1000万字符串,其中有些是相同的(重复),需要把重复的全部去掉,保留没有重复的字符串。请问怎么设计和实现?
寻找热门查询:查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个,每个不超过255字节。
一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
用trie树统计每个词出现的次数,时间复杂度是O(n*le)(le表示单词的平准长度),然后是找出出现最频繁的前10个词,可以用堆来实现。
有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
每个结点增加个count变量,查询、插入时维护count变量进行词频统计,用一个最小堆进行维护最高频的100个词的频率
寻找热门查询:
搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,这些查询串的重复读比较高,虽然总数是1千万,但是如果去除重复和,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串,要求使用的内存不能超过1G。
(1) 请描述你解决这个问题的思路;
(2) 请给出主要的处理流程,算法,以及算法的复杂度。
Trie 树的介绍以及多种实现方式:https://segmentfault.com/a/1190000008877595
Trie 树最大的好处就是查询字符串的时间复杂度稳定,即字符串的本身长度 m = string.length
; 不随单词个数 n
的变化而变化
缺点就是空间换时间。 至于用哪种方式实现,可以参考上面提到的文章。
package trie;
import java.util.HashMap;
import java.util.Stack;
public class Trie {
// 节点类
private class Node {
public Boolean isWord;
public HashMap<Character, Node> next;
public Node(Boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
public Node() {
this(false);
}
}
private Node root;
private int size;
public Trie() {
root = new Node();
size = 0;
}
// 获取Trie中储存的单词个数
public int size() {
return size;
}
// 向Trie中插入一个单词
public void add(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
size ++;
}
}
// 从 trie 中删除某个单词
public void delete(String word) {
if (!contains(word)) return;
Stack<Node> stack = new Stack<>();
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
cur = cur.next.get(c);
stack.push(cur);
}
cur.isWord = false;
int index = word.length() - 1;
while (!stack.empty() && !stack.peek().isWord && stack.peek().next.isEmpty()) {
stack.pop();
stack.peek().next.remove(word.charAt(index--));
}
size --;
}
// 查询
public boolean contains(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return cur.isWord;
}
// 判断是否有单词是以某个单词为前缀
public boolean isPrefix(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
// 模式匹配 通配符 ‘.’
public boolean match(String pattern) {
return match(root, pattern, 0);
}
private boolean match(Node node, String pattern, int index) {
if (index == pattern.length()) {
return node.isWord;
}
char c = pattern.charAt(index);
if (c != '.') {
if (node.next.get(c) == null) return false;
return match(node.next.get(c), pattern, index + 1);
}
for (char nextChar : node.next.keySet()) {
if (match(node.next.get(nextChar), pattern, index + 1)) return true;
}
return false;
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.add("ab");
trie.add("ab");
trie.add("ab666");
trie.add("ab777");
trie.add("ab77777");
System.out.println(trie.size());
trie.delete("ab77777");
System.out.println(trie.size());
System.out.println(trie.contains("ab777"));
System.out.println(trie.contains("ab77777"));
trie.add("ab77777");
System.out.println(trie.size());
trie.delete("ab777");
System.out.println(trie.size());
System.out.println(trie.contains("ab777"));
System.out.println(trie.contains("ab77777"));
System.out.println(trie.contains("ab"));
System.out.println(trie.contains("ab666"));
}
}
package trie;
import java.util.HashMap;
public class wordCountTrie {
private class Node {
public int count;
public HashMap<Character, Node> next;
public Node() {
this.count = 0;
next = new HashMap<>();
}
}
private Node root;
private int size;
public wordCountTrie() {
root = new Node();
size = 0;
}
// 获取Trie中储存的单词个数
public int size() {
return size;
}
// 向Trie中插入一个单词
public void add(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (cur.count == 0) {
size ++;
}
cur.count ++;
}
// 向Trie中插入一个单词
public int count(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return 0;
}
cur = cur.next.get(c);
}
return cur.count;
}
}