树状数组

433 阅读2分钟

考虑一个数组,我们有两个需求:①区间更新;②区间和查询。

方案1

最朴素的想法,时间复杂度都是O(n)。

arr = [2,0,1,7,0,9]
for i in range(l, r):	# 区间更新
    arr[i] += value
for i in range(l, r):	# 区间和查询
    sum += arr[i]

方案2

高级点的想法,时间复杂度都是O(1)。这里用到了前缀数组和差分数组。

arr = [2,0,1,7,0,9]
prefix, diff = [2,2,3,10,10,19], [2,-2,1,6,-7,9]
diff[l] += value	# 区间更新
diff[r] -= value
sum = prefix[r] - prefix[l]	# 区间和查询

还有点小问题,区间和查询的O(1)是建立在arr不会更新的前提上,如果arr是动态更新的,那么arr每更新一次,就需要重建prefix,代价是O(n)的时间复杂度。

树状数组

下面代码给出了树状数组的初始化,arr和tree的下标从1开始。

arr  = [0,2,0,1,7,0,9]
tree = [0,0,0,0,0,0,0]

def add(i, value):
    while i < len(arr):
        tree[i] += value
        i += i&(-i)

for i in range(1, len(arr)):
    add(i, a[i])

print tree	# [0,2,2,1,10,0,9]

i=i&(-i)将保留i的最低位的1,其余所有位置0。

那么我们如何基于树状数组做区间和查询?

def ask(i):
    while i:
        sum += tree[i]
        i -= i&(-i)
    return sum

for i in range(1, len(arr)):
    print ask(i)	# 2 2 3 10 10 19

sum = ask(r) - ask(l)

add()时间复杂度为O(log(n)),ask()时间复杂度为O(log(n))。

若以add()做区间更新,承担O(nlog(n))的时间复杂度。

for i in range(l, r):
    add(i, value)

上面提到了区间更新可以使用差分数组优化时间,但是树状数组却是基于原数组建立的,如果我们基于原数组的差分数组建立树状数组,会是什么样子?

arr = [0,2,0,1,7,0,9]
diff, tree = [0,2,-2,1,6,-7,9], [0,0,0,0,0,0,0]

for i in range(1, len(arr)):
    add(i, diff[i])

print tree	# [0,2,0,1,7,-7,2]

for i in range(1, len(arr)):
    print ask(i)	# 2 0 1 7 0 9

可以看到基于差分数组建立树状数组,ask(i)的返回值为arr[i],因此该方法适用于区间更新,单点查询,而上面的方法适用于单点更新,区间查询