简单介绍
定义
字典数又称单词查找树、Trie树,是一种树形结构,是一种哈希树的变种。
典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
优点
利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
字典树的特点
1、字典树的根节点不存储字符,其他节点只存储单词的一个字符,
2、每个节点的所有子节点包含的字符都不相同
3、字典树中表示一个单词用的是一条链,从根节点到某一单词结束标志节点,路径上经过的字符连接起来是一个完整的单词
实现步骤
构建字典树
第一步:构建节点
思考下字典树的节点需要放什么信息呢?
- 单词结束标志:isEndOfWord:Boolean
- 子节点信息:children:Object
- 单词出现频率(存储在单词结束标志节点里):frequency:number
思考问题:是否需要新增一个字段,存储单词的字符呢?
答案就是不需要,因为子节点信息是一个对象,那对象的key就可以用来存储单词的字符,也便于后续的查找
到这一步,我们就已经把节点构建出来了:
class TrieNode {
constructor() {
this.isEndOfWord = false;
this.children = {};
this.frequency = 0;
}
}
第一步:构建字典树
我们先来模拟一下,假如插入一个单词【hello】的步骤是啥样的
可以看到插入完毕之后就是一条链表走到头了。接下来,在此基础上在插入一个单词【heal】
可以看到插入完毕前缀相同的地方节点是共用的。接下来,在此基础上在插入一个单词【world】
通过模拟构建字典树的插入,能够发现一个单词其实就是字典树下的一条分支,且多个单词的公共前缀能够共用。
接下来就开始实现构建字典树的代码:
class TrieNode {
constructor() {
this.isEndOfWord = false;
this.children = {};
this.frequency = 0;
}
}
class Trie{
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (let i = 0; i < word.length; i++) {
const char = word[i]; // 拿到单个字符
// 没有这个子节点就创建
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];// 接着往后插入
}
// 单词字符遍历完毕
node.isEndOfWord = true; // 一个单词出现
node.frequency = node.frequency+1
}
}
查找单词存在的频率
查找就比较简单了,直接用树的遍历一步步往下找就行。
class TrieNode {
constructor() {
this.isEndOfWord = false;
this.children = {};
this.frequency = 0;
}
}
class Trie{
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (let i = 0; i < word.length; i++) {
const char = word[i]; // 拿到单个字符
// 没有这个子节点就创建
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];// 接着往后插入
}
// 单词字符遍历完毕
node.isEndOfWord = true; // 一个单词出现
node.frequency = node.frequency+1
}
// 查找
search(word) {
let node = this.root;
for (let i = 0; i < word.length; i++) {
node = node.children[word[i]];
if (!node) {
return 0;
}
}
// 最后一个单词字符查找结束,直接返回出现频率
// 结束标识是false,frequency一点是0,所以这里就不用判断是否有结束标识,直接返回frequency
return node.frequency;
}
}