IndexTree作用
给你一个nums数组,实现查询区间和操作+单点更新nums数组操作。
可以使用IndexTree结构实现这两个操作。
IndexTree流程
1.
IndexTree的大小和nums数组大小相同。
2.
IndexTree下标必须从1开始,为了方便也将nums数组的下标一一对应。
3.
IndexTree[1]存储nums[1]。
IndexTree[2]存储nums[1]+nums[2]。
IndexTree[3]存储nums[3]。
IndexTree[4]存储nums[1]+nums[2]+nums[3]+nums[4]。
IndexTree[5]存储nums[5]。
IndexTree[6]存储nums[5]+nums[6]。
IndexTree[7]存储nums[7]。
IndexTree[8]存储nums[1]+nums[2]+nums[3]+nums[4]+nums[5]+nums[6]+nums[7]+nums[8]。
4.
一开始IndexTree[1]存储nums[1],线长度为1。
然后IndexTree[2]应该存储nums[2],如果存储nums[2]线长度为1,前面的线长度也是1,和我的相同,于是合并,线长度为2,表示存储nums[1]+nums[2]。
IndexTree[3]应该存储nums[3],线长度为1,前面线长度是2,不相同,不能合并。因此IndexTree[3]存储nums[3]。
IndexTree[4]应该存储nums[4],线长度为1,前面线长度也为1,相同合并,此时线长度为2,前面线长度为2,合并,此时线长度为4,表示nums[1]+nums[2]+nums[3]+nums[4],因此IndexTree[4]=nums[1]+nums[2]+nums[3]+nums[4]。
IndexTree[5]应该存储nums[5],线长度为1,前面线长度为4,不相同,因此IndexTree[5]=nums[5]。
IndexTree[6]应该存储nums[6],线长度为1,前面线长度为1,相同合并,此时线长度为2,前面线长度为4,不相同不能合并,因此IdnexTree[6]=nums[5]+nums[6]。
IndexTree[7]应该存储nums[7],线长度为1,前面线长度为2,不相同不能合并,因此IndexTree[7]=nums[7]。
IndexTree[8]应该存储nums[8],线长度为1,前面线长度为1,合并,此时线长度为2,前面线长度为2,合并此时线长度为4,前面线长度为4,合并此时线长度为8,表示nums[1]~nums[8],因此IndexTree[8]=nums[1]+nums[2]+nums[3]+nums[4]+nums[5]+nums[6]+nums[7]+nums[8]。
5.
如果我要查询nums数组 1~index区间的前缀和。
在IndexTree中,ret+=tree[index].这是第一个位置.
下一个位置是index-=lowbit(index).
ret+=tree[index].
下一个位置是index-=lowbit(index).
直到index越界为止.
6.
index-=lowbit(index)是什么意思?
首先lowbit(index) <==> index&(-index).
意思是在二进制中,保留最低位的1所代表的值.
index-=lowbit(index)意思是抹去最低位的1.
7.
如果我要单点更新nums数组index位置.加上c,IndexTree应该怎么变化?
首先IndexTree[index]+=c.
下一个位置index+=lowbit(index).
IndexTree[index]+=c.
下一个位置index+=lowbit(index).
直到index越界为止.
index+=lowbit(index)意思是加上最低位的1.
8.
如果要查询left~right区间和.
只需要前缀和right减去前缀和left-1.
9.
需要注意的是下标的映射关系.如果nums下标从0开始,IndexTree里面的下标对应的是1~n的下标.
可以想象IndexTree里面有个arr数组,和nums一样,唯一的区别是下标从1开始,IndexTree对应的是arr数组的下标.
IndexTree代码
class IndexTree {
public:int size; // 树的大小vector<int> tree; // 树的内部数据结构,用来存储部分和// 构造函数,初始化树的大小IndexTree(int _size) {size = _size + 1; // 实际大小比输入大小大1,通常是为了方便处理从1开始的下标tree.resize(size); // 调整内部数组的大小以匹配树的大小}// 计算低位的函数,用于确定区间的边界int lowbit(int i) {return i & -i; // 返回i的最低位的1所对应的值}// 计算从树的起始位置到给定索引的所有元素的总和int sum(int index) { // 对内的区间下标int ret = 0; // 用于存储和的变量while (index > 0) { // 从给定索引开始向树的根部移动ret += tree[index]; // 累加路径上的值index -= lowbit(index); // 移动到下一个区间}return ret; // 返回计算出的和}// 更新树的一个节点,增加一个值void add(int index, int d) { // 对内的区间下标while (index <= size) { // 从给定索引开始,向上更新所有相关的节点tree[index] += d; // 更新当前节点index += lowbit(index); // 移动到下一个需要更新的节点}}// 计算给定区间[l, r]的和int range(int l, int r) { // 对外封装的函数,l和r是外界的区间下标return sum(r + 1) - sum(l); // 通过差分计算区间和}
};int main() {vector<int> nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 初始数组IndexTree it(nums.size()); // 创建一个树状数组的实例for (int i = 0; i < nums.size(); i++) {it.add(i + 1, nums[i]); // 初始化树状数组,将nums中的元素逐个添加进去}// 打印从每个元素开始到数组结束的所有区间和for (int left = 0; left < nums.size(); left++) {for (int right = left; right < nums.size(); right++) {cout << it.range(left, right) << " "; // 输出每个区间的和}cout << endl; // 每个起始点后换行}return 0;
}
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!