目录
1、树状数组
2、基本应用
2.1、前缀和:不修改,只查询
2.2、树状数组:动态修改,求区间和
3、实现数组树状
3.1、神奇的lowbit (x)操作
3.2、tree[ ]数组
3.3、基于tree[ ]的计算
3.4 sum[]的计算
3.5、tree[]的更新
4、代码演示
1、树状数组
1.1 简介
- 树状数组(Binary Indexed Tree, BIT),利用数的二进制特征进行检索的一种树状结构。
- 一种真正的高级数据结构:二分思想、二叉树、位运算、前缀和
树状数组顾名思义就是一个结构为树形结构的数组,于二叉树的结构类似但又不同,它是在二叉树的结构上删除了一些中间节点,如下图所示。
1.2解决什么问题?
可以解决大部分区间上面的修改以及查询的问题,例如1.单点修改,单点查询,2.区间修改,单点查询,3.区间查询,区间修改,换言之,线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决,因为线段树的扩展性比树状数组要强.
1.3 优缺点:
- 算法复杂度:O(logn),高效!
- 代码简洁!
- 缺点:扩展性弱,线段树能解决的问题,树状数组不一定能解决.
1.4、树状数组和线段树的区别在哪?
有人会问了既然线段树的问题能够用树状数组解决而且线段树还比树状数组扩展性强,那为什么不直接用线段树呢?问的很好,树状数组的作用就是为了简化线段树,举个例子:一个问题可以用线段树解决写代码半个小时,但是用树状数组只需要10分钟,那么你会选择哪一个算法呢?没错,基于某些简单的问题,我们没必要用到功能性强但实现复杂的线段树(杀鸡焉用宰牛刀).
2、基本应用
数列,操作:
1) 单点修改:修改元素add(k, x):把加上x。
2)求和:
区间和
2.1、前缀和:不修改,只查询
数列,求区间和:
数列是静态的,用前缀和计算区间和,特别高效。
- 前缀和: sum[i]=a+...+ a;
- 区间和:a;+...+a,= sum[j]-sum[i-1]
- 查询一次区间和,O(1)
代码演示:
a = [0,4,5,6,7,8,9,10,11,12,13] # a[0]不用,用0占位
sum = [0]*20
sum[1] = a[1]
for i in range(2,11): #计算前缀和
sum[i]=a[i]+sum[i-1]
print(sum)
for i in range(1,11): #用前缀和反推计算数组a[]:
print(sum[i] - sum[i-1], end=' ') # a[i] = sum[i] - sum[i-1]
print()
print("[5,8]=",sum[8]-sum[4])#查询区间和,例如查询[5,8]
# 输出
# [0, 4, 9, 15, 22, 30, 39, 49, 60, 72, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# 4 5 6 7 8 9 10 11 12 13
# [5,8]= 38
如果数列是动态的
- 修改元素add(k,x) :把加上x。复杂度O(1)
- 求区间和:前缀和全部改变,sum(j) - sum(i-1)。复杂度:O(n),效率低。
2.2、树状数组:动态修改,求区间和
数列是动态的
- 修改元素add(k, x) :把a,加上x。
- 求区间和:sum(j) - sum(i-1)
复杂度:O(logn)
3、实现数组树状
3.1、神奇的lowbit (x)操作
lowbit(x) = x &-x
功能:找到x的二进制数的最后一个1
& | 按位与 | a & b | 只有当a,b都是1时,a & b=1;否则a & b=0 |
以x = 5来举例,-5需要用补码表示
所以lowbit(5) = 5 &-5 = 1。
3.2、tree[ ]数组
- 从lowbit(x)推出tree[门]数组,所有的计算都基于tree[ ]
令m = lowbit(x)
定义tree[x]:把a,和它前面共m个数相加。
例:lowbit(6)=2,有tree[6] = 。
还可以用下面的方式表示:横线中的黑色表示tree[x],等于横线上元素相加的和。
3.3、基于tree[ ]的计算
(1)求和
利用tree[ ]数组求sum,例如:
- sum[8]= tree[8]
- sum[7]= tree[7]+ tree[6]+ tree[4]
- sum[9]=tree[9] + tree[8]
以上关系是如何得到的? 借助lowbit(x)
3.4 sum[]的计算
例: sum[7] = tree[7] + tree[6] + tree[4]
- 从7开始,加上tree[7];
- 7 - lowbit(7)=6,加上tree[6];
- 6 - lowbit(6)=4,加上tree[4];
- 4 - lowbit(4)=0,结束。
3.5、tree[]的更新
更改,和它相关的tree都会变化。
例如改变a,,那么tree[3]、tree[4]、tree[8]...都会改变。影响哪些tree[ ]? 仍然利用lowbit(x):
- 更改tree[3];
- 3+ lowbit(3)=4,更改tree[4];
- 4 + lowbit(4)=8,更改tree[8];
- 直到最后的tree[n]。
4、代码演示
def lowbit(x): # 找到x的二进制数的最后一个1
return x & -x
def add(x, d): # 修改x: x加上d
while x < n:
tree[x] += d
x += lowbit(x)
def sum(x): # 求a_1+.....+a_x
ans = 0
while x > O:
ans += tree[x]
x -= lowbit(x)
return ans
引用文章:
树状数组(详细分析+应用),看不懂打死我!