自学内容网 自学内容网

非线性数据结构之树

Fenwick 树(树状数组)

Fenwick 树(或称树状数组)是一种支持高效前缀和(Prefix Sum)查询和更新的数据结构。Fenwick 树的应用较广泛,特别是在处理频繁变动的区间数据时,能在 O(log n) 时间内完成查询和更新操作。


一、定义

Fenwick 树是一种使用数组结构来实现的树结构,每个元素 i 存储的值等于该位置索引的二进制表示中“最后一个 1”位所代表的区间和。例如,若 i = 6(110),则树状数组在位置 6 处存储了从索引 5 到 6 的区间和。


二、特点
  1. 空间复杂度低:只需要一个与原数组相同大小的树状数组来记录前缀和。
  2. 支持动态更新:更新某一元素后,只需更新相关节点而不影响其他数据,使其在实时更新数据的场景下高效。
  3. 前缀和查询高效:每次查询通过递减“最后一个 1”位可以跳过部分区间,缩小查询范围。
三、操作
  • 更新操作:修改数组的一个值并更新对应的树状数组,时间复杂度为 O(log n)。
    • 更新某个元素时,递增索引的“最后一个 1”位,更新树状数组。
  • 前缀和查询:查询从起始位置到某位置的和,时间复杂度为 O(log n)。
    • 查询时不断去除索引的“最后一个 1”位,累加对应树状数组的值。
四、优缺点
  • 优点:支持快速前缀和查询和单点更新,结构简单,易于实现,特别适用于频繁更新的场景。
  • 缺点:仅支持单点更新,对于复杂区间操作支持有限。
五、应用
  • 统计频率和排名:如在竞赛计分板中快速更新和查询分数排名。
  • 区间和的实时更新:如金融数据或实时统计中需要快速响应的区间查询。
六、示例代码(Java 实现)
class FenwickTree {
    private int[] tree;

    public FenwickTree(int size) {
        tree = new int[size + 1];
    }

    // 单点更新
    public void update(int index, int delta) {
        while (index < tree.length) {
            tree[index] += delta;
            index += index & -index;
        }
    }

    // 查询前缀和
    public int query(int index) {
        int sum = 0;
        while (index > 0) {
            sum += tree[index];
            index -= index & -index;
        }
        return sum;
    }

    // 查询区间和
    public int rangeQuery(int left, int right) {
        return query(right) - query(left - 1);
    }
}

归并树(Merge Tree)

归并树是一种用于解决特定区间查询的树结构,通常用于静态数据集的查询优化,如处理区间最值查询、频数查询等。


一、定义

归并树是将数据分块并排序构建的一种树结构。每个节点存储一段区间,并按值排序,以方便二分查找等操作。构建过程中,归并树不断将左右子区间归并成有序的区间数组,逐层合并,形成一棵完全二叉树。

二、特点
  1. 多种区间查询:支持区间求和、区间最值等操作。
  2. 二分查找优化:每个节点存储有序数据,因此区间查询时可以利用二分查找优化查询速度。
  3. 适用于静态数据:归并树的构建开销较大,适合于处理固定不变的静态数据集。
三、操作
  • 构建树:自底向上,先将叶节点数据排序,再合并生成上层节点的有序区间数据,时间复杂度为 O(n log n)。
  • 查询:递归查询左右子节点的区间内数据,复杂度 O(log n)。
  • 动态更新:不支持动态更新,若数据变动需重新构建归并树。
四、优缺点
  • 优点:支持高效的静态区间查询,利用二分查找和分治思想,提升区间最值和频数查询效率。
  • 缺点:不支持动态更新,适用于不变的数据集;构建复杂度较高。
五、应用
  • 区间频数统计:如需要统计区间内某元素出现的次数。
  • 区间最值/特定范围查询:适合静态数据的区间最大值、最小值、排名等查询操作。
六、示例代码(Java 实现)
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class MergeTree {
    private List<Integer>[] tree;
    private int size;

    public MergeTree(int[] data) {
        size = data.length;
        tree = new ArrayList[4 * size];
        buildTree(data, 0, 0, size - 1);
    }

    // 构建归并树
    private void buildTree(int[] data, int node, int start, int end) {
        tree[node] = new ArrayList<>();
        if (start == end) {
            tree[node].add(data[start]);
        } else {
            int mid = (start + end) / 2;
            buildTree(data, 2 * node + 1, start, mid);
            buildTree(data, 2 * node + 2, mid + 1, end);
            merge(tree[2 * node + 1], tree[2 * node + 2], tree[node]);
        }
    }

    // 合并子数组
    private void merge(List<Integer> left, List<Integer> right, List<Integer> merged) {
        int i = 0, j = 0;
        while (i < left.size() && j < right.size()) {
            if (left.get(i) < right.get(j)) {
                merged.add(left.get(i++));
            } else {
                merged.add(right.get(j++));
            }
        }
        while (i < left.size()) merged.add(left.get(i++));
        while (j < right.size()) merged.add(right.get(j++));
    }

    // 查询区间内小于给定值的元素个数
    public int query(int left, int right, int k) {
        return queryRecursive(0, 0, size - 1, left, right, k);
    }

    private int queryRecursive(int node, int start, int end, int left, int right, int k) {
        if (right < start || end < left) {
            return 0;
        }
        if (left <= start && end <= right) {
            return countLessThan(tree[node], k);
        }
        int mid = (start + end) / 2;
        return queryRecursive(2 * node + 1, start, mid, left, right, k) +
               queryRecursive(2 * node + 2, mid + 1, end, left, right, k);
    }

    private int countLessThan(List<Integer> list, int k) {
        int l = 0, r = list.size() - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (list.get(mid) < k) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return l;
    }
}

总结比较

数据结构特点优点缺点应用场景
Fenwick 树使用数组实现的树结构,支持前缀和动态更新和查询效率高不支持复杂区间查询区间和、统计数据
归并树每个节点存储有序区间,支持二分查找高效区间查询和频数统计不支持动态更新,构建耗时静态数据集的区间查询和统计

Fenwick 树与归并树都在区间查询中发挥着重要作用,但其适用场景不同。Fenwick 树适用于动态更新场景,而归并树适用于静态数据集的复杂查询。


原文地址:https://blog.csdn.net/m0_61840987/article/details/143396134

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!