数据结构-树状数组

数据结构-树状数组

摘要 :树状数组是一种高效的数据结构,常用来解决区间和问题。本文将对这种数据结构进行介绍,并且给出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运行截图如下:

参考

相关推荐
Ai 编码助手1 小时前
在 Go 语言中如何高效地处理集合
开发语言·后端·golang
轩辕烨瑾3 小时前
C#语言的区块链
开发语言·后端·golang
苦 涩3 小时前
考研408笔记之数据结构(七)——排序
数据结构
银河梦想家3 小时前
【Day23 LeetCode】贪心算法题
leetcode·贪心算法
sz66cm4 小时前
LeetCode刷题 -- 45.跳跃游戏 II
算法·leetcode
Victoria.a4 小时前
顺序表和链表(详解)
数据结构·链表
Bran_Liu5 小时前
【LeetCode 刷题】字符串-字符串匹配(KMP)
python·算法·leetcode
笔耕不辍cj6 小时前
两两交换链表中的节点
数据结构·windows·链表
萧若岚6 小时前
Elixir语言的Web开发
开发语言·后端·golang
csj506 小时前
数据结构基础之《(16)—链表题目》
数据结构