彻底理解树状数组
2016-08-24 11:33
169 查看
最近又学习了树状数组,趁热打铁,顺便总结一下对树状数组的理解
树状数组功能:
1.可用于快速求解数组a
的前i项和
2.将a[i]+c,并保证修改后依然能快速求解前i项和
树状数组实现方法:
先来看一下树状数组是一个什么样的数据结构,这样有助于后面我们对算法实现的理解:
定义bit[i]=a[i-2^k+1]+…a[i],其中k等于将i写成二进制后末尾含0的个数,例如对于二进制数1000来说,k=3;现在我们看一下如何求解bit[i],为了方便说明,将i表示成二进制,并用bit[1000]来举例说明,
bit[1000]=a[0001]+a[0010]+a[0011]+a[0100]+a[0101]+a[0110]+a[0111]+a[1000];
现在,再来观察一下bit[0100],bit[0110],bit[0111];
bit[0100]=a[0001]+a[0010]+a[0011]+a[0100];
bit[0110]=a[0101]+a[0110];
bit[0111]=a[0111];
不难发现,bit[1000]=bit[0100]+bit[0110]+bit[0111]+a[1000];
现在来看一下这幅图加深一下对树状数组的理解
除叶子节点之外,每个节点由俩部分组成,一部分是儿子节点(红色部分),另一部分就是原来数组(灰色部分),对于灰色部分没什么好说的,现在可以看一下红色部分,给定一个节点bit[i],那么他的儿子节点一定有k(其中k等于将i写成二进制后末尾含0的个数)个,且每个儿子节点也可直接由i求出来,直观的来看,每个儿子节点就是将i写成二进制后,再将i的第k+1位写成0,之后把第k,k-1,k-2…1这些位置依次的0依次改为1,每修改一次便得到一个儿子节点,例如1000,此时k=3,将k+1位改成0变成0000,然后依次将3,2,1位修改为1得到0100,0110,0111,这就是他的三个儿子节点;有了以上对树状数组的直观理解后我们再来看一下树状数组俩个功能的具体实现方法:
1.求取前x项和的方法;
bit[x]=a[x-2^k+1]+…+a[x];
现在有了后2^k项的和,还留下a[1]…a[x-2^k]没有求,那么不妨另x1=x-2^k,那么再加上bit[x1],就又获得了2^k1项和,如此一直迭代下去,知道xi=0时就求出了前x项和,这里由机器补码的知识可以很容易的计算出x-2^k=x-x&(-x);所以求和的代码如下
2.修改某一项a[x]的值
首先我们来看一下修改了a[x]后bit中哪些值会受影响,由上面bit[x]的组成部分可以知道,a[x]一定含bitx[x]中,且bit[x]的子孙节点均不受影响,再观察一下那副图,会发现bit[x]的父节点也包含a[x],也就是说
在修改了a[x]的值后,
bit[x]的父节点都会受影响,根据上面父节点与子节点的关系,这里有一个很方便的方法求解x的父节点fx,fx=x+x&(-x);现给出修改某一项值得代码
参考博客 树状数组 详解
树状数组功能:
1.可用于快速求解数组a
的前i项和
2.将a[i]+c,并保证修改后依然能快速求解前i项和
树状数组实现方法:
先来看一下树状数组是一个什么样的数据结构,这样有助于后面我们对算法实现的理解:
定义bit[i]=a[i-2^k+1]+…a[i],其中k等于将i写成二进制后末尾含0的个数,例如对于二进制数1000来说,k=3;现在我们看一下如何求解bit[i],为了方便说明,将i表示成二进制,并用bit[1000]来举例说明,
bit[1000]=a[0001]+a[0010]+a[0011]+a[0100]+a[0101]+a[0110]+a[0111]+a[1000];
现在,再来观察一下bit[0100],bit[0110],bit[0111];
bit[0100]=a[0001]+a[0010]+a[0011]+a[0100];
bit[0110]=a[0101]+a[0110];
bit[0111]=a[0111];
不难发现,bit[1000]=bit[0100]+bit[0110]+bit[0111]+a[1000];
现在来看一下这幅图加深一下对树状数组的理解
除叶子节点之外,每个节点由俩部分组成,一部分是儿子节点(红色部分),另一部分就是原来数组(灰色部分),对于灰色部分没什么好说的,现在可以看一下红色部分,给定一个节点bit[i],那么他的儿子节点一定有k(其中k等于将i写成二进制后末尾含0的个数)个,且每个儿子节点也可直接由i求出来,直观的来看,每个儿子节点就是将i写成二进制后,再将i的第k+1位写成0,之后把第k,k-1,k-2…1这些位置依次的0依次改为1,每修改一次便得到一个儿子节点,例如1000,此时k=3,将k+1位改成0变成0000,然后依次将3,2,1位修改为1得到0100,0110,0111,这就是他的三个儿子节点;有了以上对树状数组的直观理解后我们再来看一下树状数组俩个功能的具体实现方法:
1.求取前x项和的方法;
bit[x]=a[x-2^k+1]+…+a[x];
现在有了后2^k项的和,还留下a[1]…a[x-2^k]没有求,那么不妨另x1=x-2^k,那么再加上bit[x1],就又获得了2^k1项和,如此一直迭代下去,知道xi=0时就求出了前x项和,这里由机器补码的知识可以很容易的计算出x-2^k=x-x&(-x);所以求和的代码如下
int sum(int x) { int s=0; for (;x;x-=x&-x) s+=bit[x]; return s; }
2.修改某一项a[x]的值
首先我们来看一下修改了a[x]后bit中哪些值会受影响,由上面bit[x]的组成部分可以知道,a[x]一定含bitx[x]中,且bit[x]的子孙节点均不受影响,再观察一下那副图,会发现bit[x]的父节点也包含a[x],也就是说
在修改了a[x]的值后,
bit[x]的父节点都会受影响,根据上面父节点与子节点的关系,这里有一个很方便的方法求解x的父节点fx,fx=x+x&(-x);现给出修改某一项值得代码
void add(int x,int c) { for (;x<=N;x+=x&(-x)) bit[x]+=c; }
参考博客 树状数组 详解
相关文章推荐
- 二维树状数组学习之一:彻底理解
- 无数被转彻底理解树状数组
- 【学习】彻底理解树状数组
- 彻底理解ThreadLocal
- 带你彻底理解RSA算法原理
- 彻底理解position与anchorPoint
- this指针指向的彻底理解
- 彻底理解Java的Future模式
- [学习笔记] CDQ分治 从感性理解到彻底晕菜
- [数据库事务与锁]详解一: 彻底理解数据库事务
- 这次彻底理解了Object这个属性
- Android AsyncTask完全解析,带你从源码的角度彻底理解
- 彻底理解Toast原理和解决小米MIUI系统上没法弹Toast的问题
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- JavaScript中原型对象的彻底理解
- 彻底理解js中this的指向,不必硬背
- 彻底理解ThreadLocal
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- 彻底理解position与anchorPoint