一文看懂字典树:数据结构中的“前缀大师”

148 阅读3分钟

简单介绍

定义

字典数又称单词查找树、Trie树,是一种树形结构,是一种哈希树的变种。

典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计

优点

利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

字典树的特点

1、字典树的根节点不存储字符,其他节点只存储单词的一个字符,

2、每个节点的所有子节点包含的字符都不相同

3、字典树中表示一个单词用的是一条链,从根节点到某一单词结束标志节点,路径上经过的字符连接起来是一个完整的单词

实现步骤

构建字典树

第一步:构建节点

思考下字典树的节点需要放什么信息呢?

  • 单词结束标志:isEndOfWord:Boolean
  • 子节点信息:children:Object
  • 单词出现频率(存储在单词结束标志节点里):frequency:number

思考问题:是否需要新增一个字段,存储单词的字符呢?

答案就是不需要,因为子节点信息是一个对象,那对象的key就可以用来存储单词的字符,也便于后续的查找

到这一步,我们就已经把节点构建出来了:

image.png

class TrieNode {
  constructor() {
    this.isEndOfWord = false;
    this.children = {};
    this.frequency = 0;
  }
}

第一步:构建字典树

我们先来模拟一下,假如插入一个单词【hello】的步骤是啥样的

image.png 可以看到插入完毕之后就是一条链表走到头了。接下来,在此基础上在插入一个单词【heal】

image.png

可以看到插入完毕前缀相同的地方节点是共用的。接下来,在此基础上在插入一个单词【world】

image.png

通过模拟构建字典树的插入,能够发现一个单词其实就是字典树下的一条分支,且多个单词的公共前缀能够共用。

接下来就开始实现构建字典树的代码:

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;
  }
}