数据结构-树状数组

数据结构-树状数组

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

参考

相关推荐
YGGP1 小时前
【Golang】Go语言编程思想(六):Channel,第四节,Select
golang
23级二本计科1 小时前
分治_归并_归并排序(逆序对)
数据结构·算法·排序算法
CHENWENFEIc1 小时前
基础排序算法详解:冒泡排序、选择排序与插入排序
c语言·数据结构·算法·排序算法·学习方法·冒泡排序
yangpipi-2 小时前
数据结构(C语言版)-4.树与二叉树
c语言·开发语言·数据结构
C++oj2 小时前
普及组集训--图论最短路径设分层图
数据结构·算法·图论·最短路径算法
ruleslol2 小时前
java基础概念49-数据结构2
java·数据结构
荒古前2 小时前
小发现,如何高级的顺序输出,逆序输出整数的每一位(栈,队列)
数据结构·c++·算法
Y编程小白2 小时前
Leetcode经典题7--跳跃游戏
算法·leetcode·游戏
Rossy Yan3 小时前
【数据结构——查找】顺序查找(头歌实践教学平台习题)【合集】
数据结构·c++·算法·查找
●VON3 小时前
go语言的成神之路-标准库篇-os标准库
linux·运维·服务器·开发语言·后端·学习·golang