C++的数据结构(十一):树状数组

树状数组(Binary Indexed Tree),又称Fenwick树,是一种用于高效处理前缀和的数据结构。它的核心思想是通过一系列的位运算来优化求和过程,从而在O(log n)的时间复杂度内完成单点更新和区间查询等操作。

树状数组的基本思想是将原始数组A1...n映射到一个新的数组C1...n,使得对于任意的前缀和Si = A1 + A2 + ... + Ai,都可以通过C数组的某些元素的加减运算得到。具体地,对于A数组中的每一个元素Ai,我们将其贡献拆分成多个部分,并分别加到C数组的对应位置上。

树状数组的基本操作:

  1. 单点更新操作是指在A数组的某个位置i上增加或减少一个值val,并更新C数组以保证前缀和的正确性。这个操作的时间复杂度为O(log n)。示例代码如下。
cpp 复制代码
void update(int i, int val) {
    while (i <= n) {
        C[i] += val;
        i += i & (-i);
    }
}
  1. 前缀和查询操作是指求A数组中前i个元素的和Si。由于C数组是经过特殊构造的,我们可以通过对C数组中的某些元素进行加减运算来快速得到Si。这个操作的时间复杂度也为O(log n)。示例代码如下。
cpp 复制代码
int query(int i) {
    int sum = 0;
    while (i > 0) {
        sum += C[i];
        i -= i & (-i);
    }
    return sum;
}

下面是一个使用C++编写的树状数组实例,它演示了如何更新序列中某个位置的值及查询序列中某个位置的前缀和(即该位置及之前所有位置的值之和)。代码如下。

cpp 复制代码
#include <vector>
using namespace std;

class FenwickTree {
private:
    vector<int> bit;

    int lowbit(int x) {
        return x & (-x);
    }

public:
    FenwickTree(int n) : bit(n + 1, 0) {}

    void update(int index, int val) {
        while (index < bit.size()) {
            bit[index] += val;
            index += lowbit(index);
        }
    }

    int query(int index) {
        int sum = 0;
        while (index > 0) {
            sum += bit[index];
            index -= lowbit(index);
        }
        return sum;
    }
};
int main() {
    int n, m;
    cin >> n >> m; // 输入序列长度和操作次数

    FenwickTree ft(n);
    vector<int> a(n + 1);

    // 初始化序列和树状数组
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        ft.update(i, a[i]);
    }

    // 执行m次操作
    for (int i = 0; i < m; ++i) {
        int op, p, v;
        cin >> op;
        if (op == 1) { // 更新操作
            cin >> p >> v;
            ft.update(p, v - a[p]); // 更新差值,避免重复加值
            a[p] = v; // 更新原序列中的值
        } else if (op == 2) { // 查询前缀和操作
            cin >> p;
            cout << ft.query(p) << endl;
        }
    }

    return 0;
}

现在,我们来测试一下这个程序。假设输入如下:

5 2

1 2 3 4 5

1 2 10

2 5

输出结果如下。

通过这个示例,我们可以看到树状数组在解决实际问题中的应用。它通过高效的前缀和计算,使得在更新和查询操作中都能保持较低的时间复杂度。当然,树状数组并不是解决所有问题的银弹,但在某些特定场景下,它可以提供非常优秀的性能。

相关推荐
.Hypocritical.17 分钟前
数据结构笔记——链表成环/反转 + 有序二叉树(BST)构建、遍历、删除
java·数据结构
CSharp精选营5 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假8 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠9 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦16 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠17 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾17 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82117 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q17 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒17 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode