数据结构-树状数组

数据结构-树状数组

摘要 :树状数组是一种高效的数据结构,常用来解决区间和问题。本文将对这种数据结构进行介绍,并且给出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,比如对于B6,6的二进制为110, 最低位的1则是10,所以B6在树中的父节点的下标为110+10=8。而求取最低位的1开始使用以下公式计算:
a = b & − b a=b \& -b a=b&−b

这样就保证了更新一个数字的时间复杂度控制在 O ( l o g n ) O(logn) O(logn)。同时,在求取区间和时,由于区间和可以转换为对应的两个前缀和的差。比如要求B9位置的前缀和,如图,可以按照一定的顺序将部分节点的值加起来即可,具体就是减去最低位的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运行截图如下:

参考

相关推荐
linx2954 分钟前
第七章 · 标准库容器、算法与 ranges
c语言·开发语言·数据结构·c++·算法
Logic10121 分钟前
C语言/数据结构位运算题解:异或XOR找出独特数字的索引位置——成对数字在两侧
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
橘子汽水16822 分钟前
Leetcode 763,45 划分字母区间 跳跃游戏II
数据结构·算法·leetcode
kiracrimson34 分钟前
建堆的两种算法:O(N) 和 O(N log N) 差在哪里
数据结构
报错小能手1 小时前
GO语言接口 泛型 错误处理
golang
天天喝旺仔1 小时前
Git 内部原理深度解析:从 blob/tree/commit 对象到 packfile 与垃圾回收
数据结构·数据库·git·算法·哈希
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 228. 汇总区间 | C++ 锚点游标与断点检测法
c++·算法·leetcode
Edward The Bunny1 小时前
Leetcode Hot 100
数据结构·算法
xiangyun6113 小时前
【408数据结构 08】队列:循环队列判空判满,408年年考,一次讲清
c语言·开发语言·数据结构·c++·算法
xiangyun6114 小时前
【408数据结构 06】双链表、循环链表、静态链表
c语言·开发语言·数据结构·c++·算法