数据结构-树状数组
摘要 :树状数组是一种高效的数据结构,常用来解决区间和问题。本文将对这种数据结构进行介绍,并且给出golang语言的实现。最后以LeetCode 307来介绍如何使用树状数组解决问题。
关键词 :数据结构,树状数组,golang,LeetCode
背景
给定一个数组,在这个数组上经常性地需要重新区间[i, j]的数字和,很容易想到的一种思路就是前缀和,利用前缀和可以以 O ( 1 ) O(1) O(1)的时间复杂度查询出所有的区间和。但是如果需要经常性地修改数组中的某个元素,然后再进行区间和的查询,此时前缀和修改的时间复杂度就达到了 O ( n ) O(n) O(n),尽管查询的时间复杂度还是 O ( 1 ) O(1) O(1),但是这种方法在大数据量情况下运行时间是线性增加的,这显然是低效的。那么有没有什么数据结构可以使的修改查询区间和的时间复杂度都低于 O ( n ) O(n) O(n)?要回答这个问题就需要用到树状数组,其修改和查询的时间复杂度都是 O ( l o g n ) O(logn) O(logn),是一种解决此类问题的高效数据结构。
树状数组
树状数组顾名思义就是一棵树,只不过这棵树上面的节点是一个区间,并且父节点的区间由所有子节点的区间合成,节点上的值就是这个区间的和,并且父节点的值等于所有子节点的值之和。因此,在更新某个点的值时,只需要沿着树往树根去更新,在查询区间和时,也可以按照某种规则从树的底层往根节点去处理。如下图所示:
可以看到,对于树状数组B来说,其父节点的下标(这里为了便于说明,下标从1开始)等于其下标加上该下标最低位的1,比如对于B[6],6的二进制为110, 最低位的1则是10,所以B[6]在树中的父节点的下标为110+10=8。而求取最低位的1开始使用以下公式计算:
a = b & − b a=b \& -b a=b&−b
这样就保证了更新一个数字的时间复杂度控制在 O ( l o g n ) O(logn) O(logn)。同时,在求取区间和时,由于区间和可以转换为对应的两个前缀和的差。比如要求B[9]位置的前缀和,如图,可以按照一定的顺序将部分节点的值加起来即可,具体就是减去最低位的1,直到为零,这同样可以将区间和的查询时间复杂度控制在 O ( l o g n ) O(logn) O(logn)。
以下是golang语言的实现:
go
type BinaryIndexTree struct {
elems []int
}
func NewBinaryIndexTree(nums []int) *BinaryIndexTree {
n := len(nums)
tree := &BinaryIndexTree{make([]int, n + 1)}
for i, num := range nums {
tree.Add(i + 1, num)
}
return tree
}
// 求取前i个数的和
func (tree *BinaryIndexTree) Sum(i int) int {
sum := 0
for i > 0 {
sum += tree.elems[i]
i -= (i & -i)
}
return sum
}
// 第i个数加上delta
func (tree *BinaryIndexTree) Add(i, delta int) {
for i < len(tree.elems) {
tree.elems[i] += delta
i += ( i & -i)
}
}
应用
以LeetCode307为例来说明这一个问题,这道题目就是上文描述的典型场景,实现代码如下:
go
type NumArray struct {
tree *BinaryIndexTree
nums []int
}
func Constructor(nums []int) NumArray {
tree := NewBinaryIndexTree(nums)
return NumArray{tree: tree, nums: nums}
}
func (this *NumArray) Update(index int, val int) {
this.tree.Add(index + 1, val - this.nums[index])
this.nums[index] = val
}
func (this *NumArray) SumRange(left int, right int) int {
return this.tree.Sum(right + 1) - this.tree.Sum(left)
}
type BinaryIndexTree struct {
elems []int
}
func NewBinaryIndexTree(nums []int) *BinaryIndexTree {
n := len(nums)
tree := &BinaryIndexTree{make([]int, n + 1)}
for i, num := range nums {
tree.Add(i + 1, num)
}
return tree
}
// 求取前i个数的和
func (tree *BinaryIndexTree) Sum(i int) int {
sum := 0
for i > 0 {
sum += tree.elems[i]
i -= (i & -i)
}
return sum
}
// 第i个数加上delta
func (tree *BinaryIndexTree) Add(i, delta int) {
for i < len(tree.elems) {
tree.elems[i] += delta
i += ( i & -i)
}
}
LeetCode运行截图如下: