树状数组理论与实践

本文详细介绍了树状数组(FenwickTree)的概念、低位操作的应用以及C++中的实现方法,包括单点更新、前缀和查询和区间和计算。通过实例和模板展示了如何在实际问题中使用这种高效的数据结构。
摘要由CSDN通过智能技术生成

1 理论

1.1 介绍

树状数组(Fenwick Tree),也称为二叉索引树(Binary Indexed Tree, BIT),是一种用于高效计算数组前缀和的数据结构。它可以在 O ( l o g N ) O(logN) O(logN)的时间复杂度下完成单点更新、前缀和查询等操作。

树状数组的主要思想是利用二进制的特性来进行有效的更新和查询。它的核心思想是利用数组的索引来表示树状结构,从而实现高效的查询和更新操作。

具体来说,树状数组的结构如下:

  1. 数组C:这是一个额外的数组,用于存储原始数组的前缀和或其它需要维护的信息。
  2. 每个元素的索引i代表了原始数组中的某个位置。
  3. 每个元素的值C[i]存储了原始数组中某个位置到其父节点(通过某种规则计算)的信息。通常,C[i]的计算规则是将i的二进制表示中最低位的1所代表的区间累加到i上。

1.2 lowbit操作

lowbit(x)表示数x二进制表示中最后一位1的大小,用十进制表示。

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

例如输入6,返回2;输入7返回1;输入8返回8;输入9返回1。

对于数x,加上lowbit(x)的写法,

//写法1
x += lowbit(x);
//写法2
x += x & -x;

对于数x,减去lowbit(x)的写法,

//写法1
x -= lowbit(x);
//写法2
x -= x & -x;
//写法3
x &= x - 1;

1.3 C++模板

树状数组的C++模板如下,

//给定数组nums: 下标从0开始
//(1)构建tree数组
int n = nums.size();
vector<int> tree(n + 1, 0); //下标从1开始,故是n + 1。nums[i] <==> tree[i + 1]。
//tree[k]表示以k为区间右端点的那个区间下所有元素的和,也即[k - lowbit(k) + 1, k],该区间有lowbit(k)个元素。根据“tree数组从下标1开始,nums数组从下标0开始的设定”,转换成nums有:nums下标在[k - lowbit(k), k - 1]内的所有元素的和,该区间内有lowbit(k)个元素。

//(2.1)nums[i]增加操作
//nums[i] -> nums[i] + x
//nums[i]增加x,对相应的关键区间tree[k]的影响
void add(int i, int x) {
	nums[i] += x; //nums[i]增加x
	for (int k = i + 1; k < tree.size(); k += lowbit(k)) {
		tree[k] += x;
	}
	return;
}

//(2.2)nums[i]更新操作
//nums[i] -> val
//nums[i]更新为val,对相应的关键区间tree[k]的影响
void update(int i, int val) {
	int x = val - nums[i];
	add(i, x);
	return;
}

//(3)初始化
for (int i = 0; i < n; ++i) {
	add(i, nums[i]);
}

//(4.1)求nums数组中[0, r]区间内的元素和。即nums[0] + nums[1] + ... + nums[r]的和。
int get_s(int r) {
	int res = 0;
	for (int k = r + 1; k > 0; k -= lowbit(k)) {
		res + tree[k];
	}
	return res;
}

//(4.2)求nums数组中[l, r]区间内的元素和。即nums[l] + nums[l + 1] + ... + nums[r]的和。
int get_s(int l, int r) {
	return get_s(r) - get_s(l - 1);
}

重点
tree[k]以k为区间右端点的那个区间下所有元素的和,也即[k - lowbit(k) + 1, k],该区间有lowbit(k)个元素。根据“tree数组从下标1开始,nums数组从下标0开始的设定”,转换成nums有:nums下标在[k - lowbit(k), k - 1]内的所有元素的和,该区间内有lowbit(k)个元素

2 实践

模板题307区域和检索-数组可修改

C++代码如下,

class NumArray {
private:
    vector<int> nums;
    vector<int> tree;

public:
    inline int lowbit(int x) {
        return x & -x;
    }

    NumArray(vector<int>& nums) {
        int n = nums.size();
        this->nums.resize(n, 0);
        tree.resize(n + 1, 0);

        for (int i = 0; i < n; ++i) {
            update(i, nums[i]);
        }

    }
    
    void update(int index, int val) {
        int x = val - nums[index];

        for (int k = index + 1; k < tree.size(); k += lowbit(k)) {
            tree[k] += x;
        }

        nums[index] = val;//更新nums[index]

        return;
    }
    
    int sumRange(int right) {
        int res = 0;
        for (int k = right + 1; k > 0; k -= lowbit(k)) {
            res += tree[k];
        }
        return res;
    }

    int sumRange(int left, int right) {
        return sumRange(right) - sumRange(left - 1);
    }
};

题目13072. 将元素分配到两个数组中 II

C++代码如下,

class Fenwick {
    vector<int> tree;
public:
    Fenwick(int n) : tree(n) {} //下标从1开始

    //把下标为i的元素增加1
    void add(int i) {
        while (i < tree.size()) {
            tree[i]++;
            i += i & -i;
        }
    }

    //返回下标在[1,i]的元素之和,注意是闭区间
    int pre(int i) {
        int res = 0;
        while (i > 0) {
            res += tree[i];
            i &= i - 1;
        }
        return res;
    }
};

class Solution {
public:
    vector<int> resultArray(vector<int>& nums) {
        auto sorted = nums;
        ranges::sort(sorted);
        sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
        int m = sorted.size();

        vector<int> a{nums[0]}, b{nums[1]};
        Fenwick t1(m + 1), t2(m + 1); //t1,t2是树状数组
        t1.add(ranges::lower_bound(sorted, nums[0]) - sorted.begin() + 1); //+1是因为下标从1开始
        t2.add(ranges::lower_bound(sorted, nums[1]) - sorted.begin() + 1); //+1是因为下标从1开始
        for (int i = 2; i < nums.size(); ++i) {
            int x = nums[i];
            int v = ranges::lower_bound(sorted, x) - sorted.begin() + 1;
            int gc1 = a.size() - t1.pre(v);
            int gc2 = b.size() - t2.pre(v);
            if (gc1 > gc2 || gc1 == gc2 && a.size() <= b.size()) {
                a.push_back(x);
                t1.add(v);
            } else {
                b.push_back(x);
                t2.add(v);
            }
        }
        a.insert(a.end(), b.begin(), b.end());
        return a;
    }
};

题目2

3 参考

  1. 带你发明树状数组!附数学证明(Python/Java/C++/Go/JS/Rust)
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YMWM_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值