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 是二进制位数(常数级别)。


求数组中两个数异或最大值的算法

基本思路

  1. 构造 Trie 树
    将数组中所有数字按照二进制形式插入 Trie 中。
  2. 查询最大异或
    对于数组中的每个数,从 Trie 树中查找能够使异或值最大的那个数。
    对于每一位,从最高位开始:
    • 如果当前位为 0,则我们希望查询到分支 1(如果存在);
    • 如果当前位为 1,则希望查询到分支 0; 如果找不到,则走已有分支。
  3. 记录最大异或值
    遍历每个数,更新全局最大异或值。

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)); // 输出最大异或值
    }
}

扩展:树上路径的异或最大值

问题描述

给定一棵带边权的树,求任意两点间路径的边权异或和的最大值。
例如,树中每条边有一个权值,任意两点之间存在唯一一条简单路径,我们希望计算路径上所有边权的异或和,并找出其中的最大值。

思路解析

  1. 树的前缀异或和
    从根节点开始,计算到每个节点的路径上所有边权的异或和,记为 xor[i]
    对于树中任意两个节点 uv,路径的异或和为:

    \[xorPath(u,v)=xor[u]⊕xor[v]\text{xorPath}(u, v) = xor[u] \oplus xor[v] \]

    因为异或满足交换律和结合律。

  2. 利用 01 Trie 树求最大异或
    把所有节点的 xor 值存入 Trie 中,再求任意两个 xor 值的异或最大值。
    这与前面求数组中两个数的异或最大值问题等价。

  3. 树的遍历
    使用深度优先搜索(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();
    }
}

代码讲解

  1. 树的表示与 DFS 遍历
    • 使用邻接表存储树,每个节点存储其相邻边和对应权值。
    • 利用 DFS 从根节点(假设为 0)开始,计算每个节点的异或前缀和 xorPrefix[v] = xorPrefix[u] ^ weight
  2. 构造 01 Trie 树
    • 将所有节点的 xorPrefix 插入 Trie。
    • 对于每个节点的前缀和,查询 Trie 得到与其异或最大的值,即路径异或值的最大值。
  3. 求解最大异或
    • 遍历所有节点前缀和,用 Trie 查询出与之异或最大值,并更新全局最大值。

总结

本文详细介绍了 Java 中 01 Trie 树的原理和应用。主要内容包括:

  • 基本原理
    利用二进制前缀树存储数值,通过贪心选择(高位优先选取相反位)实现高效查询。
  • 数组问题实现
    利用 Trie 树求数组中两个数的异或最大值,关键在于将数值按位插入和查询。
  • 扩展到树上路径
    对于树结构,通过 DFS 计算每个节点的异或前缀和,将问题转化为求两个前缀和的异或最大值,再利用 Trie 树求解。
  • 实战代码
    提供了详细的 Java 代码示例,从 Trie 的构造、查询到扩展问题的完整实现,使得该技术在实际竞赛和工程中都具有较高的应用价值。
posted @ 2025-03-05 21:01  咋还没来  阅读(56)  评论(0)    收藏  举报