likes
comments
collection
share

漫画:什么是前缀树(Trie树)?

作者站长头像
站长
· 阅读数 28

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

————— 第二天 —————

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

如上图所示,我们在百度输入框输入ap两个字母,下拉菜单就会自动列举出包含该前缀的所有单词,比如api、app、apple等等。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

小灰的想法,是要建立一个很大的哈希表,哈希表中的key,是所有单词包含的前缀。

举个例子,有两个单词app和apple,它们的前缀包括a、ap、app、appl、apple,把这些前缀都作为key存储到哈希表中,每一个key对应的value,就是具有这个前缀的单词:

漫画:什么是前缀树(Trie树)?

比如app和apple都具有前缀a,那么a所对应的value就是app,apple;再比如appl是apple的前缀,那么appl这个key对应的value就是apple。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

————————————

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

我们利用apple,app,api,banana,bus这5个单词来举例子,构建出一颗前缀树,这颗前缀树就像下图这样:

漫画:什么是前缀树(Trie树)?

在这棵树中,每一个节点最多可以拥有26个孩子节点,对应着从a到z这26个英文字母。

在我们给的例子中,apple,app,api这三个单词是字母a开头,banana,bus这两个单词是字母b开头,所以根节点拥有两个孩子,分别对应着以字母a开头的单词和以字母b开头的单词。

apple,app,api这三个单词,第二个字母都是p,所以a孩子节点只拥有一个孩子节点,对应着第二个字母是p的单词。

banana,bus这两个单词,第二个字母分别是a和u,所以b孩子节点拥有两个孩子节点,分别对应着第二个字母是a的单词(banana),以及第二个字母是u的单词(bus)。

以此类推,所有单词的所有字母,共同构成了这个前缀树的所有节点。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

假如我们输入查询关键字“ap”,进行前缀查询,前缀树将会如何工作呢?

首先,前缀树会根据关键字中的第一个字母“a”,检查根节点是否有a对应的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

接下来,根据关键字中的第二个字母“p”,检查a孩子节点是否拥有对应字母p的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

这样一来,前缀树就判断出当前字典中存在以“ap”为前缀的单词。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

假如我们输入查询关键字“bus”,进行精确查询,前缀树将会如何工作呢?

首先,前缀树会根据关键字中的第一个字母“b”,检查根节点是否有b对应的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

接下来,根据关键字中的第二个字母“u”,检查b孩子节点是否拥有对应字母u的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

左后,根据关键字中的第三个字母“s”,检查u孩子节点是否拥有对应字母s的孩子节点,发现存在该孩子节点,并且该节点的结束标志位为真

漫画:什么是前缀树(Trie树)?

这样一来,前缀树就判断出当前字典中存在精确匹配“bus”的单词。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

假如我们要插入一个新单词“buy”,前缀树如何工作呢?

首先,前缀树会根据关键字中的第一个字母“b”,检查根节点是否有b对应的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

接下来,根据关键字中的第二个字母“u”,检查b孩子节点是否拥有对应字母u的孩子节点,发现存在该孩子节点:

漫画:什么是前缀树(Trie树)?

然后,根据关键字中的第三个字母“y”,检查u孩子节点是否拥有对应字母y的孩子节点,发现并没有这个孩子节点:

漫画:什么是前缀树(Trie树)?

最后,创建字母y对应的新孩子节点。由于字母y是单词buy当中的最后一个字母,所以该节点的结束标志位置为“真”:

漫画:什么是前缀树(Trie树)?

这样一来,前缀树成功插入了“buy”这个新单词。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

class TrieNode { // 存储当前节点的子节点 private TrieNode[] children; // 标记该节点是否是一个单词的结束 private boolean isEndOfWord; public TrieNode() { // 初始化子节点数组,通常为26个字母 children = new TrieNode[26]; isEndOfWord = false; } // 添加一个单词到Trie树中 public void insert(String word) { TrieNode current = this; for (int i = 0; i < word.length(); i++) { char ch = word.charAt(i); int index = ch - 'a'; if (current.children[index] == null) { current.children[index] = new TrieNode(); } current = current.children[index]; } current.isEndOfWord = true; } // 从Trie树删除一个单词 public boolean delete(String word) { return delete(this, word, 0); } private boolean delete(TrieNode current, String word, int index) { if (index == word.length()) { if (!current.isEndOfWord) { return false; // 单词不存在于树中 } current.isEndOfWord = false; return canDelete(current); // 返回是否可以删除该节点 } char ch = word.charAt(index); int childIndex = ch - 'a'; TrieNode child = current.children[childIndex]; if (child == null) { return false; // 单词不存在于树中 } boolean canDeleteChild = delete(child, word, index + 1); if (canDeleteChild) { current.children[childIndex] = null; return canDelete(current); } return false; } // 判断节点是否能被删除 private boolean canDelete(TrieNode node) { if(node.isEndOfWord){ return false; } for (TrieNode child : node.children) { if (child != null) { return false; } } return true; } // 检查一个单词是否存在于Trie树中 public boolean search(String word) { TrieNode current = this; for (int i = 0; i < word.length(); i++) { char ch = word.charAt(i); int index = ch - 'a'; if (current.children[index] == null) { return false; } current = current.children[index]; } return current.isEndOfWord; } // 检查是否有以给定前缀开头的单词存在 public boolean startsWith(String prefix) { TrieNode current = this; for (int i = 0; i < prefix.length(); i++) { char ch = prefix.charAt(i); int index = ch - 'a'; if (current.children[index] == null) { return false; } current = current.children[index]; } return true; } } public class Trie { private TrieNode root; public Trie() { root = new TrieNode(); } // 插入一个单词到Trie树中 public void insert(String word) { root.insert(word); } // 从Trie树删除一个单词 public boolean delete(String word) { return root.delete(word); } // 检查一个单词是否存在于Trie树中 public boolean search(String word) { return root.search(word); } // 检查是否有以给定前缀开头的单词存在 public boolean startsWith(String prefix) { return root.startsWith(prefix); } public static void main(String[] args) { Trie trie = new Trie(); trie.insert("apple"); System.out.println(trie.search("apple")); // 输出 true System.out.println(trie.search("app")); // 输出 false System.out.println(trie.startsWith("app")); // 输出 true trie.insert("app"); System.out.println(trie.search("app")); // 输出 true trie.delete("apple"); System.out.println(trie.search("apple")); // 输出 false } }

在该代码中,TrieNode为前缀树的节点类,Trie类负责前缀树的整体操作。

至于前缀树的删除操作,比插入和查找操作要复杂一些,涉及到递归,大家可以阅读代码中的delete方法进行理解。

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?

漫画:什么是前缀树(Trie树)?