字典树(Trie树)的经典模版及JS实现

248 阅读4分钟

什么是字典树?

我们先看一下百度百科的定义:

又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高

如果你看到这还不明白,那我们不妨利用它的另一个名字来解释:前缀树。

顾名思义,针对一些具有公共前缀的字符串我们可以将他们的公共前缀放在一起,从而减少查询时间,字典树核心思想就是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

接下来我们用实例来更加清晰的展示一下如何实现一个字典树。现在有一组字符串:['dog','food','foot','market','map','mather','meal'],我们如何设计一种存储方式来存放这字符串,可以在后续方便且快速的查询某个单词以及和他相似的单词呢(这里就类似于搜索推荐了)?

image.png

上图就是一个经典的字典树存放模式,可以看出,我们会将相同前缀的字符放在一起,这样当我们查找时,就可以很快的将相同前缀的单词一起查找出来,接下来我们讲一下如何通过算法来实现上图的字典树。

经典字典树模式

var Trie = function () {
    this.map = {}; //如果只有字母的话,也可以利用数组的下标作为26个字母;数组的值则为子节点
    this.isEnd = false; // 从root根节点至此是否是一个完整的单词(即这个节点是否是一个单词的结尾)
};

/** 
 * @param {string} word
 * @return {void}
 */
 //【向字典树插入单词word】
 //思路:按照word字符,从根节点开始,一直向下走如果遇到null,就new出新节点;
 //如果节点已经存在,cur顺着往下走就可以。
Trie.prototype.insert = function (word) {
    let cur = this.map;
    for (const c of word) {
        if (!cur[c]) {
            cur[c] = new Trie();
        }
        cur = cur[c];
    }
    cur.isEnd = true;// 一个单词插入完毕,此时cur指向的节点即为一个单词的结尾

};

/** 
 * @param {string} word
 * @return {boolean}
 */
//【判断一个单词word是否完整存在于字典树中】
// 思路:cur从根节点开始,按照word的字符一直尝试向下走:
// 如果走到了null,说明这个word不是前缀树的任何一条路径,返回false;
// 如果按照word顺利的走完,就要判断此时cur是否为单词尾端:如果是,返回true;如果不是,说明word仅仅是一个前缀,并不完整,返回false
Trie.prototype.search = function (word) {
    let cur = this.map;
    for (const c of word) {
        if (!cur[c]) return false;
        cur = cur[c];
    }
    return cur.isEnd;
};

/** 
 * @param {string} prefix
 * @return {boolean}
 */
//【判断一个单词word是否是字典树中的前缀】
// 思路:和sesrch方法一样,根据word从根节点开始一直尝试向下走:
//如果遇到null了,说明这个word不是前缀树的任何一条路径,返回false;
//如果安全走完了,直接返回true就行了———我们并不关心此事cur是不是末尾(isWord)
Trie.prototype.startsWith = function (prefix) {
    let cur = this.map;
    for (const c of prefix) {
        if (!cur[c]) return false;
        cur = cur[c];
    }
    return true;
};

/**
 * Your Trie object will be instantiated and called as such:
 * var obj = new Trie()
 * obj.insert(word)
 * var param_2 = obj.search(word)
 * var param_3 = obj.startsWith(prefix)
 */

image.png

要注意,图上所有红色节点就是代码中isEnd为true的节点,至此我们就完成了一个经典字典树的构造。

字典树的应用

一般来说,我们在生活中常见的字典树的应用有:输入法/搜索引擎的自动补全、文档的拼写检查与修复、以及各种网站中敏感词的检测等等。

image.png

甚至包括我们最常见的英语词典也是按照字典树的搜索机制来实现的,而这也是字典树名称的由来。

经典字典树的构造和详解到此结束,后续有时间的话我会讲一下经典字典树的其他变式和算法实现,下期见。