树状数组

来源:互联网 发布:淘宝视频服务订购 编辑:程序博客网 时间:2024/05/29 07:24

1. 定义

给定序列(数列)A,我们设一个数组C满足C[i] = A[i–2k+ 1] + … + A[i],其中,k为i在二进制下末尾0的个数,i从1开始算,则称C为树状数组(二进制索引树)。

对于给定的i,有三种求解方法:

(1) 2k= i&(i^(i-1))

(2) 2k= i^(i&(i-1))

(3) 2k= i & (-i)   (补码性质)

2. 用途

快速地获取连续几个数的和、最大值以及最小值,动态地修改一些位置上的数字,求解一个序列的逆序数。该文中以求和为例。

注:如果我们用一个数组B[i]记录A中前i项和,查询的时间代价是O(1),但是在更新每一个元素时时间代价为O(n),而采用树状数组时间代价O(log2n)

3. 操作

3.1 求树状数组C

方法一:根据公式C[i] = A[i–2k+ 1] + … + A[i]可以轻松的计算出数组C中的每一个元素。

方法二:初始化数组A与数组C为0,然后更新A中的每个元素为目标元素,即我们要用于查询部分和的数据。当更新A[i]时,采用自底向上的方式更新每一个包含A[i]的树状数组C的值。易得直接包含A[i]的为C[i],直接包含C[i]的为C[i+2k],其中k为i的二进制表示中0的个数,求解方法见定义。

方法一与方法二的时间复杂度分别为O(n2)与O(nlog2n),其中n为原始数据个数。

3.2 查询

我们将查询分为两部分:从起点开始的连续i个数的和与连续区间[k,m]之和。用sum[i]代表从起点开始连续i个数之和。

查询一:从起点开始的连续i个数

sum[i]包含了i项,而C[i]包含了从i开始向前数共2k项,因此我们有sum[i] = C[i] + sum[i-2k](sum[0] = 0)。而k代表了i的二进制表示中0的个数,i-2k表示将i中最后一个1置0,因此该递归式递归次数为i中1的个数,即log2i。可得查询操作的时间复杂度为log2n,其中n为原始数据个数。

注:i-2k =i&(i-1)

查询二:连续区间[k, m]数字之和S

因为S = sum[m] – sum[k-1],因此第二种查询可以转化为第一种查询。该计算方式进行了两次查询一,因此时间复杂度也为log2n。

3.3 修改元素的值

对元素的修改包括值的增加、减少与改变,而减少一个值可以理解为增加一个负值,改变也可以转变为增加一个值(或正或负),因此这里指描述增加即可。

3.3.1 增加

设对A[i]元素增加k:

(1) C[i] = C[i]+ k

(2) i = i+ 2k,如果i<n,执行(1);反之,运算结束。

该操作的时间复杂度为O(log2n)。

3.4 增加元素个数

相比于线段树来说,块状数组可以增加元素的个数,而不需要从新计算已经求出来的数据。

4. 代码实现

#include <iostream>#include <assert.h>using namespace std;#define MAXNUM 10template<class T>class CTreeArry{public:CTreeArry(int num);~CTreeArry();private:int dataNum;public:T *sourceData;T *treeArry;void Init(const T arry[], int n);void Increase(const int i, const T value);void Reduce(const int i, const T value);void Change(const int i, const T value);T Query(const int end);T Query(const int start, const int end);void AddNumOfElem(const T arry[], int addNum);void upData(const int i, const T value);};template<class T>CTreeArry<T>::CTreeArry(int num):dataNum(num){sourceData = new T[num+1];treeArry = new T[num+1];memset(sourceData, 0, sizeof(T)*(dataNum+1));memset(treeArry, 0, sizeof(T)*(dataNum+1));}template<class T>CTreeArry<T>::~CTreeArry(){delete []sourceData;delete []treeArry;}template<class T>void CTreeArry<T>::Init(const T arry[], int n){for (int i=1; i<=n; ++i){Increase(i, arry[i]);}}template<class T>void CTreeArry<T>::Increase(const int i, const T value){assert(i>=1 && i<=dataNum);int temp = i;sourceData[temp] += value;for (; temp<=dataNum; temp += (temp&(-temp))){treeArry[temp] += value;}}template<class T>void CTreeArry<T>::Reduce(const int i, const T value){assert(i>=1 && i<=dataNum);Increase(i, -value);}template<class T>void CTreeArry<T>::Change(const int i, const T value){assert(i>=1 && i<=dataNum);Increase(i, value-sourceData[i]);}template<class T>T CTreeArry<T>::Query(const int end){assert(end>=1 && end<=dataNum);T sum = 0;//下面两个等价//for (int i=end; i>0; i-=(i&(-i)))for (int i=end; i>0; i=i&(i-1)){sum += treeArry[i];}return sum;}template<class T>T CTreeArry<T>::Query(const int start, const int end){assert(start>=1 && start<=end && end<=dataNum);return Query(end) - Query(start-1);}template<class T>void CTreeArry<T>::upData(const int i, const T value){assert(i>=1 && i<=dataNum);for (int j=i; j<=dataNum; j+=(j&(-j))){treeArry[j] += value;}}template<class T>void CTreeArry<T>::AddNumOfElem(const T arry[], int addNum){int tempDataNum = dataNum;dataNum = dataNum + addNum;T *tempSource = sourceData;T *tempTreeArry = treeArry;sourceData = new T[dataNum + 1];treeArry = new T[dataNum + 1];memset(sourceData, 0, sizeof(T)*(dataNum + 1));memset(treeArry, 0, sizeof(T)*(dataNum + 1));for (int i=1; i<=tempDataNum; ++i){sourceData[i] = tempSource[i];if (i+(i&(-i))<=tempDataNum){treeArry[i] = tempTreeArry[i];}else{upData(i, tempTreeArry[i]);}}delete tempSource;delete tempTreeArry;for (int i=tempDataNum+1; i<=dataNum; ++i){Increase(i, arry[i-tempDataNum-1]);}}


0 0
原创粉丝点击