区间信息操作之树状数组(Fenwick Tree)原理

        树状数组(Fenwick Tree)是一种高效处理前缀和单点更新的数据结构,时间复杂度为 O(log n),适用于动态维护数组的区间统计信息。本文将详细讲解树状数组的核心原理,并通过Java代码实现其核心功能。


目录

一、树状数组的核心思想

1. 什么是树状数组?

2. 核心原理:二进制索引与Lowbit操作

二、树状数组的Java实现

1. 树状数组结构

2. 单点更新

3. 前缀和查询

4. 区间和查询

三、应用示例

1. 动态维护数组的区间和

2. 逆序对统计

四、树状数组的优缺点

优点:

缺点:

五、总结


一、树状数组的核心思想

1. 什么是树状数组?

树状数组是一种基于二进制索引的树形结构,支持以下操作:

  • 单点更新(Update):在 O(log n) 时间内更新某个元素的值。

  • 前缀和查询(Query):在 O(log n) 时间内查询前 i 个元素的和。

  • 区间和查询(Range Query):通过两次前缀和查询得到区间 [L, R] 的和。

2. 核心原理:二进制索引与Lowbit操作

树状数组利用二进制索引的特性,将数组的索引分解为二进制位的组合。

关键操作lowbit(x) = x & (-x),用于快速定位节点的父节点或子节点。 

示例

索引 6 的二进制为 110,其 lowbit(6) = 2,对应树状数组中的层级关系。


二、树状数组的Java实现

1. 树状数组结构

public class FenwickTree {
    private int[] tree;  // 树状数组
    private int n;       // 数组长度

    // 初始化树状数组
    public FenwickTree(int size) {
        n = size;
        tree = new int[n + 1];  // 索引从1开始
    }

    // 计算lowbit值
    private int lowbit(int x) {
        return x & (-x);
    }
}

2. 单点更新

向索引 i 处的元素增加 delta(支持正负值)。

public void update(int i, int delta) {
    while (i <= n) {
        tree[i] += delta;
        i += lowbit(i);  // 向上更新父节点
    }
}

3. 前缀和查询

查询前 i 个元素的和。

public int query(int i) {
    int sum = 0;
    while (i > 0) {
        sum += tree[i];
        i -= lowbit(i);  // 向前查找前驱节点
    }
    return sum;
}

4. 区间和查询

查询区间 [L, R] 的和。

public int rangeQuery(int L, int R) {
    return query(R) - query(L - 1);
}
 
  

三、应用示例

1. 动态维护数组的区间和

问题描述:给定数组 arr,支持动态更新元素值并查询区间和。

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        FenwickTree ft = new FenwickTree(arr.length);
        
        // 初始化树状数组
        for (int i = 0; i < arr.length; i++) {
            ft.update(i + 1, arr[i]);  // 树状数组索引从1开始
        }
        
        // 查询区间[2,4]的和
        System.out.println(ft.rangeQuery(2, 4));  // 输出:3+5+7=15
        
        // 更新元素arr[1](原值为3)增加2
        ft.update(2, 2);
        System.out.println(ft.rangeQuery(2, 4));  // 输出:5+5+7=17
    }
}

2. 逆序对统计

问题描述:统计数组中逆序对的数量(利用树状数组离散化处理)。

public int countInversions(int[] nums) {
    // 离散化数组(假设nums已去重并排序)
    Map rank = new HashMap<>();
    int[] sorted = Arrays.copyOf(nums, nums.length);
    Arrays.sort(sorted);
    for (int i = 0; i < sorted.length; i++) {
        rank.put(sorted[i], i + 1);  // 排名从1开始
    }
    
    FenwickTree ft = new FenwickTree(nums.length);
    int count = 0;
    for (int i = nums.length - 1; i >= 0; i--) {
        int r = rank.get(nums[i]);
        count += ft.query(r - 1);  // 查询比当前数小的元素数量
        ft.update(r, 1);           // 插入当前数
    }
    return count;
}
 
  

四、树状数组的优缺点

优点:

  • 高效:单次操作时间复杂度为 O(log n)

  • 空间优化:仅需 O(n) 的额外空间。

  • 实现简单:代码量少,逻辑清晰。

缺点:

  • 功能有限:仅支持前缀和类操作,无法处理区间最值等复杂问题。

  • 索引从1开始:需要处理原始数组的索引偏移。


五、总结

        树状数组是处理动态区间和问题的利器,尤其适合需要频繁更新和查询的场景(如实时数据统计)。对于更复杂的区间操作(如区间修改、区间最值),可考虑线段树(Segment Tree)结构。

你可能感兴趣的:(数据结构和算法,#,高级数据结构,算法,数据结构,java)