树状数组的原理及实现

树状数组 ,顾名思义就是像树一样的数组,当然这里说的是存储方式像树,就像哈希散列表根据哈希函数来储存。

具体怎么存呢。定义每一列的顶端结点C[]数组 

图中C[i]代表 子树的叶子结点的权值之和 //这里以维护区间和为例。

C[1]=A[1];

C[2]=A[1]+A[2];

C[3]=A[3];

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

C[6]=A[5]+A[6];

C[7]=A[7];

C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

这分别是C[i]代表的意义。我们其实就是把区间和存在一颗满二叉树上,为什么要这么存呢

这就要引入lowbit函数了。

lowbit 函数

int lowbit(int x) {return x&(-x);}
#define lowbit(x) x&(-x)  ///宏定义形式

lowbit函数可以说是树状数组的精髓和核心了吧。它的作用是“二进制数从低位向高位数,第一个数字1"作为运算结果。比较难理解,看下例子好了:lowbit(12) = 4,12的二进制为1100,从低位到高位,碰见第一个1时,截断,只取下面部分作为结果,即:100,十进制是4。 这就体现了,二进制和位运算的奇妙之处了。刚开始接触,可能很难接受和理解。但是慢慢就懂了。

现在知道了这个函数,再看看下面这些,就应该知道为什么用满二叉树来储存了吧

将C[]数组的结点序号转化为二进制

1=(001)      C[1]=A[1];

2=(010)      C[2]=A[1]+A[2];

3=(011)      C[3]=A[3];

4=(100)      C[4]=A[1]+A[2]+A[3]+A[4];

5=(101)      C[5]=A[5];

6=(110)      C[6]=A[5]+A[6];

7=(111)      C[7]=A[7];

8=(1000)    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

对照式子可以发现  C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;

 

主要应用

       区间查询:

由于我们的C[i]储存的[ i,i-lowbit(i)+1 ] 区间里每个数的和,为了方便求某段区间和,我们用一个数组sum [ x ]来存

区间 [ 1, x ]的和,由于[ x, y ]=[1, y] - [1 , x]  我们就能很方便得出任一区间的和了。

这里给出求Sum[ ] 的函数。

int sum(int c[],int i){
    int s=0;
    while(i>0){
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}

单点更新后的区间维护;

直接上代码吧,不难理解。


void updata(int i, int val)
{
	while (i <= n)
	{
		c[i] += val;
		i += lowbit(i);
	}
}

/**************************************/分隔

现在讨论一下区间最值查询和维护。区间最值和区间和虽然不一样,但思想上大致相同。

在求区间最值的算法中,用h[x]来储存[x,x-lowbit(x)+1]中每个数的最大值。

求区间最值的算法中还有一个a[i]数组,表示第i个数是多少。

 

区间最值的查询:没办法照搬区间和的方法,

假设query(x,y)为[ x , y ]上的最大值, 上面也可以知道h[ y] 代表[y,y-lowbit(y)+1]的最大值。

那么 

         若y-lowbit(y) > x ,则query(x,y) = max( h[y] , query(x, y-lowbit(y)) );

         若y-lowbit(y) <=x,则query(x,y) = max( a[y] , query(x, y-1);

用递归的思想来求解,代码中,我们常常用递推来求解。理解了上面这两个式子,基本上就没有问题了。

 

int query(int x, int y)
{
	int ans = 0;
	while (y >= x)
	{
		ans = max(a[y], ans);
		y --;
		for (; y-lowbit(y) >= x; y -= lowbit(y))
			ans = max(h[y], ans);
	}
	return ans;
}


更新后的区间维护

void updata(int x,int data)
{
	
	while (x <= n)
	{
		h[x] = data;//从左边开始维护
		for (int i=1; i<lowbit(x); i<<=1)
			h[x] = max(h[x], h[x-i]);
		x += lowbit(x);
	}		
}

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
树状数组是一种用于快速维护数组前缀和的数据结构,其时间复杂度为 $O(logn)$。树状数组实现基于二进制的思想,借助树状数组可以高效地进行单点更新和区间查询等操作。以下是树状数组实现原理: 1. 数组的每个元素代表原数组中某个位置的前缀和,如下所示: $$C_i = A_1 + A_2 + \cdots + A_i$$ 其中 $C_i$ 表示原数组 $A$ 的前 $i$ 项之和。 2. 将 $C$ 数组转换成树状数组 $T$,树状数组的每个节点表示其父节点到该节点的元素和。如下所示: $$T_i = C_{i-lowbit(i)+1} + C_{i-lowbit(i)+2} + \cdots + C_i$$ 其中 $lowbit(i)$ 表示 $i$ 的二进制表示中最低位的 $1$ 所代表的值。 3. 树状数组的单点更新操作只需要将 $i$ 位置的值加上 $k$,然后依次更新其祖先节点即可: ```cpp void update(int i, int k, int n, vector<int>& tree) { while (i <= n) { tree[i] += k; i += lowbit(i); } } ``` 其中 $n$ 表示原数组 $A$ 的大小,$tree$ 表示树状数组。 4. 树状数组区间查询操作只需要通过两次前缀和的差值计算出区间和即可: ```cpp int query(int i, vector<int>& tree) { int sum = 0; while (i > 0) { sum += tree[i]; i -= lowbit(i); } return sum; } int query(int i, int j, vector<int>& tree) { return query(j, tree) - query(i - 1, tree); } ``` 其中第一个查询函数用于计算 $A_1 + A_2 + \cdots + A_i$,第二个查询函数用于计算 $A_{i+1} + A_{i+2} + \cdots + A_j$,然后两者相减即可得到区间 $[i,j]$ 的和。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

°PJ想做前端攻城狮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值