数据结构之树状数组

     树状数组(Binary Indexed Tree BIT)是一种特殊的数据结构,这种数据结构专门用来解决两种问题:给定一个数组array[ ]={a1,a2,a3...aN}(1)给定i,计算前i个数之和。(2)给定i和num,计算array[i]+num之后,对整个树状数组进行改变。这两种操作的时间复杂度在树状数组内均为(log(n))。

    省去过多的长篇大论的理论介绍,通过一个简单的例子来对树状数组进行一个初步的了解和认识。假设有一个数组a[]={1,2,3,4},现将其以两个一组作为子节点,求得二者之和作为其父节点,重复该操作,直到最终只剩下一个父节点,可以得到以下该树:

                               数据结构之树状数组_第1张图片

那么现在看着所构造出来的这棵树来想一个问题,怎样求数组中下标从i到j的和?其实我们可以换一个角度来思考求i到j的和可以转化为((1到j的和)-(1到i-1的和))。仔细观察上面的树,不难发现在求解1到x的和过程中灰色部分的节点均不需要使用,因为不论在求和或修改树状数组两种操作中要使用这些右节点,那么其兄弟节点一定会被用到,这样的话直接使用其父节点即可(例如,求1到3的和。只需要将自上而下第二层的值为3的节点与第三层值为3的节点相加即可)。那么我们将这些冗余的节点删去,并且加以编号就可以得到一个树状数组,如下图所示:

                              数据结构之树状数组_第2张图片

给各节点编号也有一种特殊的规律:节点编号二进制数为1结尾的对应原数组a中的区间长度为1(例如1(0001)和3(0011)对应的就是a中a[1]和a[3]的值),节点编号二进制数结尾只有连续个0的对应原数组a中的区间长度为2(例如2(0010)对应a中a[1]和a[2]的和),节点编号二进制数结尾连续个0的对应原数组a中的区间长度为4(例如4(0100)对应a中a[1]到a[4]的和)。

    在对树状数组进行了一定的了解之后,接下来就正式的去解决问题。

    (1)求前i项的和

     将树状数组下标i的值加到结果中,并且将i的二进制数的最小位的1减去(用i&(-i)完成该操作),重复此操作直到i变成0为止。

     以上述a数组举例,求前3项的和,先将树状数组下标为3的值加入结果中,此时结果为3,之后将3的二进制数0011的最后一个1减去,i变为2(0010),重复上面操作,此时结果为6,再将2的二进制数0010的最后一个1减去,此时i变为0,而结果也为最终结果6.实例代码如下:

//用于找出num的二进制数的最后一个1
private int dealBit(int num)
{
	return num&(-num);
}
//用于求前n个数的和
private int sum(int n)
{
	int sum = 0;
	while(n>0)
	{
		sum += bit[n];
		n -= dealBit(n);
	}
	return sum;
}

     (2)树状数组的更新

      将第i项的值加上val,并且将i中最小位的1加到i上,直到i大于树状数组的长度。

      例如,将树状数组下标为3的值加1,此时树状数组下标为3的值为4,之后将3(0011)中最小位的1加到3上得到4(0100),再将下标为4的值加1,此时下标为4的值为11.示例代码如下:

//当nums某一位加上一个数时,用于改变整个树状数组.
private void add(int index,int num)
{
	while(index<=bit.length-1)
	{
		bit[index] += num;
		index += dealBit(index);
	}
}





你可能感兴趣的:(算法设计技巧)