字典树
Published by powerfulyang on Apr 26, 2023
关于 Trie Tree 的介绍
Background
Trie树(又称前缀树、字典树)是一种高效的数据结构,用于处理字符串的查询、插入和删除操作。它主要用于实现词典、搜索引擎的自动补全、拼写检查等功能。Trie树的特点是每个节点表示一个字符,从根节点到任意一个叶子节点的路径表示一个字符串。
Trie树的基本组成如下:
- 节点:每个节点代表一个字符,通常包含一个指向子节点的指针数组,以及一个布尔值,表示该节点是否是一个完整字符串的结尾。
- 根节点:树的根节点,不包含任何字符,但可以指向其他节点。
- 子节点:一个节点可以有多个子节点,每个子节点代表一个字符。
Trie树的主要操作包括:
- 插入:将一个字符串插入Trie树中。从根节点开始,遍历字符串的每个字符,沿着对应的子节点路径向下搜索。如果路径不存在,创建新节点。当到达字符串的最后一个字符时,将节点的布尔值设为True,表示到此为止的路径构成一个完整的字符串。
- 查询:检查一个字符串是否存在于Trie树中。从根节点开始,遍历字符串的每个字符,沿着对应的子节点路径向下搜索。如果找不到对应的子节点,说明字符串不存在。如果遍历到最后一个字符,检查节点的布尔值。如果为True,表示该字符串存在。
- 删除:从Trie树中删除一个字符串。首先查询该字符串是否存在。如果存在,从最后一个字符的节点开始,将其布尔值设为False,然后逐级向上删除没有子节点的节点。
- 前缀查询:查询某个前缀的所有字符串。从根节点开始遍历前缀的每个字符,沿着对应的子节点路径向下搜索。到达前缀的最后一个字符时,遍历其所有子树,收集所有布尔值为True的节点,将它们的路径组合成完整的字符串。
Trie树的优点:
- 查询、插入和删除操作的时间复杂度为O(m),其中m是字符串的长度。与字符串的数量无关,因此在处理大量字符串时具有很高的性能。
- 前缀查询非常高效,适用于自动补全、拼写检查等场景。
Trie树的缺点:
- 空间复杂度较高,因为每个节点需要存储一个指向子节点的指针数组。在实际应用中,可以采用压缩或者优化的数据结构来减小空间开销,如压缩字典树(Radix Tree)、Ternary Search Tree等。
Implementation
1class TrieNode {
2 children: Map<string,TrieNode>;
3 endOfWord: boolean;
4
5 constructor() {
6 this.children = new Map();
7 this.endOfWord = false;
8 }
9}
10
11class Trie {
12 private root;
13
14 constructor() {
15 this.root = new TrieNode();
16 }
17
18 insert(word: string): void {
19 let current = this.root;
20 for (const char of word) {
21 const children = current.children;
22 if (!children.has(char)) {
23 children.set(char, new TrieNode());
24 }
25 current = children.get(char);
26 }
27 current.endOfWord = true;
28 }
29
30 search(word: string): boolean {
31 let current = this.root;
32 for (const char of word) {
33 const children = current.children;
34 if (children.has(char)) {
35 current = children.get(char);
36 } else {
37 return false;
38 }
39 }
40 return current.endOfWord;
41 }
42
43 startsWith(prefix: string): boolean {
44 let current = this.root;
45 for (const char of prefix) {
46 const children = current.children;
47 if (children.has(char)) {
48 current = children.get(char);
49 } else {
50 return false;
51 }
52 }
53 return true;
54 }
55}
Examples
Trie Tree(字典树)是一种树形数据结构,它用于高效地存储和查找字符串。Trie Tree 的每个节点表示一个字符,而从根节点到叶子节点所经过的所有字符组成一个字符串。Trie Tree 可以在 O(长度) 的时间内实现字符串的插入、删除和查询操作。下面列举了一些 Trie Tree 的应用:
- 自动补全:Trie Tree 常用于搜索引擎、文本编辑器和输入法的自动补全功能。当用户输入前缀时,Trie Tree 可以快速找到以该前缀开头的所有字符串。
- IP 路由查找:Trie Tree 可以用于查找网络中 IP 地址的最长匹配前缀。通过最长匹配前缀,路由器可以确定下一跳的地址,从而实现 IP 路由。
- 词频统计:Trie Tree 可用于统计文本中单词的出现次数。将文本中的单词插入到 Trie Tree 中,然后在每个单词的末尾节点记录单词出现的次数。
- 前缀匹配:Trie Tree 可以快速找到以某个前缀开头的所有字符串。这在文本分类、过滤和搜索中非常有用。
- 词典查找:Trie Tree 可用于实现词典查找功能,用户可以通过输入单词的前缀来快速查找到完整的单词。
- 拼写检查:Trie Tree 可用于实现拼写检查功能。将正确拼写的单词存储在 Trie Tree 中,然后通过比较输入的单词与 Trie Tree 中的单词来判断拼写是否正确。
- 字符串排序:Trie Tree 可以对字符串进行排序。将字符串插入到 Trie Tree 中,然后通过深度优先搜索(DFS)遍历 Trie Tree,输出排序后的字符串。
总之,Trie Tree 是一种非常实用的数据结构,可以应用于许多字符串处理场景。
find_maximum_xor
1use std::collections::HashSet;
2
3pub fn find_maximum_xor(nums: Vec<i32>) -> i32 {
4 let mut max_xor = 0;
5 let mut mask = 0;
6
7 for i in (0..=31).rev() {
8 mask |= 1 << i;
9 let prefixes: HashSet<i32> = nums.iter().map(|num| num & mask).collect();
10 let temp_max_xor = max_xor | (1 << i);
11 for &prefix in &prefixes {
12 // if a ^ b = c, then a ^ c = b and b ^ c = a
13 if prefixes.contains(&(temp_max_xor ^ prefix)) {
14 max_xor = temp_max_xor;
15 break;
16 }
17 }
18 }
19
20 max_xor
21}
这是一个Rust语言的函数,名为find_maximum_xor
,其目的是从给定的整数向量(nums: Vec<i32>
)中找出两个数,使得它们的异或(XOR)值最大。
在这个函数中,异或运算的特点被充分利用:如果a ^ b = c,那么a ^ c = b并且b ^ c = a。
让我详细解释一下这段代码的逻辑:
let mut max_xor = 0; let mut mask = 0;
:创建并初始化变量max_xor
(用来记录目前找到的最大异或值)和mask
(用于生成前缀)。for i in (0..=31).rev()
:这是一个循环,从31到0,覆盖了一个32位整数的所有位。mask |= 1 << i;
:将mask的第i位设为1(i从31到0),用于在下一步中生成所有可能的i位前缀。let prefixes: HashSet<i32> = nums.iter().map(|num| num & mask).collect();
:使用mask来获取数组nums中所有数字的前i位前缀,并将这些前缀存储到HashSet中。HashSet用于高效查找。let temp_max_xor = max_xor | (1 << i);
:尝试将max_xor
的第i位设为1,看看是否有两个数能使它们的异或结果为这个值。for &prefix in &prefixes { if prefixes.contains(&(temp_max_xor ^ prefix)) {...}
:对于每个前缀,我们检查temp_max_xor ^ prefix
是否也在前缀集合中。如果存在,那么说明我们可以在数组中找到两个数,它们的前i位异或结果为temp_max_xor
。max_xor = temp_max_xor; break;
:如果我们找到这样的两个数,我们就把temp_max_xor
赋值给max_xor
,并跳出当前的for循环。max_xor
:在遍历所有的位之后,我们返回max_xor
,这就是数组中两个数能得到的最大异或结果。
这个函数的主要思想是从最高位开始,试图让结果的每一位都尽可能地为1。通过使用贪心的策略,我们可以在O(n)的时间复杂度内求解这个问题,n为nums的长度。