01 Trie
Java 01 Trie
在很多算法问题中,求两个数的异或最大值问题非常常见。本文将以求任意两个不同数字的异或最大值为例,详细讲解如何利用 Java 实现 01 Trie 树(又称二进制前缀树),以及如何将其扩展到树上路径的异或最大值问题。我们将从基本原理开始,逐步深入到实战代码和优化细节。
问题描述
数组版问题
给定一个整数数组,求任意两个不同数字的异或结果中的最大值。例如,输入数组 [3, 10, 5, 25, 2, 8],其中最大的异或结果是 5 ^ 25 = 28(实际结果可能因具体数字而异)。
树上路径问题
给定一棵树,每条边都有一个权值。要求在树上找出任意两点间路径的异或和的最大值。路径的异或和定义为路径上所有边权的异或结果。
01 Trie 树原理
基本概念
01 Trie 树(或称二进制前缀树)是一种特殊的树形数据结构,用于存储二进制表示的数值。每个节点通常有两个子节点:
- 0 分支:表示当前二进制位为 0
- 1 分支:表示当前二进制位为 1
构造 Trie 树时,我们将每个整数按照其二进制形式(通常固定 32 位或 64 位)插入到树中。从根节点开始,每个层级表示一个二进制位,直至最低位。这样,同一前缀的数字会共享同一路径。
为什么用 01 Trie 树求异或最大值
异或操作的一个重要性质是:
对于一个数,我们希望另一数在高位上尽量相反,从而使得异或结果的该位为 1。
例如,对于一个位为 0 的位置,最佳选择是取位 1,而对于位为 1 的位置,则最佳选择是取位 0。
在 01 Trie 树中,我们可以按位进行贪心选择:
- 从最高位开始查找,选择与当前数二进制位相反的分支(如果存在),否则选择相同分支。
通过这种方式,可以快速找到与当前数异或结果最大的那个数。将所有数依次插入 Trie 中,再进行查询,复杂度一般为 O(n·L),其中 L 是二进制位数(常数级别)。
求数组中两个数异或最大值的算法
基本思路
- 构造 Trie 树
将数组中所有数字按照二进制形式插入 Trie 中。 - 查询最大异或
对于数组中的每个数,从 Trie 树中查找能够使异或值最大的那个数。
对于每一位,从最高位开始:- 如果当前位为 0,则我们希望查询到分支 1(如果存在);
- 如果当前位为 1,则希望查询到分支 0; 如果找不到,则走已有分支。
- 记录最大异或值
遍历每个数,更新全局最大异或值。
Java 实现代码
下面给出求数组中两个数异或最大值的完整代码:
public class MaxXOR {
// 定义 Trie 节点,只有 0 和 1 两个分支
static class TrieNode {
TrieNode[] children = new TrieNode[2];
}
// 构造 Trie 的根节点
private TrieNode root = new TrieNode();
// 将数字按照二进制形式插入 Trie 中(假设 32 位整数)
public void insert(int num) {
TrieNode node = root;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
if (node.children[bit] == null) {
node.children[bit] = new TrieNode();
}
node = node.children[bit];
}
}
// 查找与 num 异或值最大的数字
public int query(int num) {
TrieNode node = root;
int maxXor = 0;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
int desired = bit ^ 1; // 希望取相反的位
if (node.children[desired] != null) {
maxXor |= (1 << i);
node = node.children[desired];
} else {
node = node.children[bit];
}
}
return maxXor;
}
public static int findMaximumXOR(int[] nums) {
MaxXOR trie = new MaxXOR();
// 插入所有数字到 Trie 中
for (int num : nums) {
trie.insert(num);
}
int max = 0;
// 对每个数字查询最大异或值
for (int num : nums) {
max = Math.max(max, trie.query(num));
}
return max;
}
public static void main(String[] args) {
int[] nums = {3, 10, 5, 25, 2, 8};
System.out.println("Maximum XOR is: " + findMaximumXOR(nums)); // 输出最大异或值
}
}
扩展:树上路径的异或最大值
问题描述
给定一棵带边权的树,求任意两点间路径的边权异或和的最大值。
例如,树中每条边有一个权值,任意两点之间存在唯一一条简单路径,我们希望计算路径上所有边权的异或和,并找出其中的最大值。
思路解析
-
树的前缀异或和
从根节点开始,计算到每个节点的路径上所有边权的异或和,记为xor[i]。
对于树中任意两个节点u和v,路径的异或和为:\[xorPath(u,v)=xor[u]⊕xor[v]\text{xorPath}(u, v) = xor[u] \oplus xor[v] \]因为异或满足交换律和结合律。
-
利用 01 Trie 树求最大异或
把所有节点的xor值存入 Trie 中,再求任意两个xor值的异或最大值。
这与前面求数组中两个数的异或最大值问题等价。 -
树的遍历
使用深度优先搜索(DFS)遍历树,计算每个节点的xor值。
代码实现
import java.util.*;
public class TreeMaxXOR {
// 定义边
static class Edge {
int to, weight;
public Edge(int to, int weight) {
this.to = to;
this.weight = weight;
}
}
// 使用邻接表存储树
static List<List<Edge>> tree;
// 存储每个节点从根到该节点的异或前缀和
static int[] xorPrefix;
// DFS 遍历计算异或前缀和
static void dfs(int u, int parent) {
for (Edge edge : tree.get(u)) {
int v = edge.to;
if (v == parent) continue;
xorPrefix[v] = xorPrefix[u] ^ edge.weight;
dfs(v, u);
}
}
// 定义 Trie 节点(与之前的类似)
static class TrieNode {
TrieNode[] children = new TrieNode[2];
}
static class MaxXORTrie {
TrieNode root = new TrieNode();
// 插入一个数字到 Trie
public void insert(int num) {
TrieNode node = root;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
if (node.children[bit] == null) {
node.children[bit] = new TrieNode();
}
node = node.children[bit];
}
}
// 查询与 num 异或最大的值
public int query(int num) {
TrieNode node = root;
int maxXor = 0;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
int desired = bit ^ 1;
if (node.children[desired] != null) {
maxXor |= (1 << i);
node = node.children[desired];
} else {
node = node.children[bit];
}
}
return maxXor;
}
}
public static int findMaxXOROnTree(int n) {
// 计算所有节点的异或前缀和
xorPrefix = new int[n];
// 假设根节点为 0,前缀和为 0
dfs(0, -1);
// 构造 Trie 并插入所有 xor 前缀
MaxXORTrie trie = new MaxXORTrie();
for (int i = 0; i < n; i++) {
trie.insert(xorPrefix[i]);
}
int max = 0;
// 查询最大异或值
for (int i = 0; i < n; i++) {
max = Math.max(max, trie.query(xorPrefix[i]));
}
return max;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 节点数量
tree = new ArrayList<>();
for (int i = 0; i < n; i++) {
tree.add(new ArrayList<>());
}
// 输入树的边(n - 1 条边)
for (int i = 0; i < n - 1; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int weight = sc.nextInt();
// 假设节点编号从 0 开始
tree.get(u).add(new Edge(v, weight));
tree.get(v).add(new Edge(u, weight));
}
System.out.println("Tree Maximum XOR is: " + findMaxXOROnTree(n));
sc.close();
}
}
代码讲解
- 树的表示与 DFS 遍历
- 使用邻接表存储树,每个节点存储其相邻边和对应权值。
- 利用 DFS 从根节点(假设为 0)开始,计算每个节点的异或前缀和
xorPrefix[v] = xorPrefix[u] ^ weight。
- 构造 01 Trie 树
- 将所有节点的
xorPrefix插入 Trie。 - 对于每个节点的前缀和,查询 Trie 得到与其异或最大的值,即路径异或值的最大值。
- 将所有节点的
- 求解最大异或
- 遍历所有节点前缀和,用 Trie 查询出与之异或最大值,并更新全局最大值。
总结
本文详细介绍了 Java 中 01 Trie 树的原理和应用。主要内容包括:
- 基本原理:
利用二进制前缀树存储数值,通过贪心选择(高位优先选取相反位)实现高效查询。 - 数组问题实现:
利用 Trie 树求数组中两个数的异或最大值,关键在于将数值按位插入和查询。 - 扩展到树上路径:
对于树结构,通过 DFS 计算每个节点的异或前缀和,将问题转化为求两个前缀和的异或最大值,再利用 Trie 树求解。 - 实战代码:
提供了详细的 Java 代码示例,从 Trie 的构造、查询到扩展问题的完整实现,使得该技术在实际竞赛和工程中都具有较高的应用价值。

浙公网安备 33010602011771号