[go]树状数组的实现

这个up主讲的非常细,我这里只是简单实现和使用

package treenums

type TreeNum struct {
	nums []int
}

// NewTreeNum 初始化一个存储n个元素的树状数组
func NewTreeNum(n int) *TreeNum {
	return &TreeNum{nums: make([]int, n+1)}
}

//计算x二进制的最低位1及之后的数。101000 -> 001000
func (t *TreeNum) lowbit(x int) int {
	return x & (-x)
}

// Add x点值+d;x = [0,n-1]
func (t *TreeNum) Add(x, d int) {
	x++
	for ; x < len(t.nums); x += t.lowbit(x) { //因为动态维护一个前缀和数组,所以依次都要+d
		t.nums[x] += d
	}
}

// Ask 查询x下标处的前缀和;x = [0,n-1]
func (t *TreeNum) Ask(x int) (ret int) {
	x++
	for ; x >= 1 && x < len(t.nums); x -= t.lowbit(x) {
		ret += t.nums[x]
	}
	return
}

/*
单点修改,区间查询
	[x]+d == add(x,d)
	[l,r] == ask(r)-ask(l-1)
区间修改,单点查询(使用树状数组维护一个查分数组b,记录查分数组的前缀和)
	[l,r]+d == add(l,d);add(r+1,-d) //[l]+d使得后面的都+d,[r+1]-d 保证区间外的值不受影响
	a[x] == ask(x)+a[x] // 查分数组记录了区间的值得变化情况
*/

树状数组用来维护前缀和非常好用

  1. 307. 区域和检索 - 数组可修改
type NumArray struct {
	*TreeNum
	nums []int
}

func Constructor(nums []int) NumArray {
	treeNum := NewTreeNum(len(nums))
	for i := range nums {
		treeNum.add(i, nums[i])
	}
	return NumArray{
		TreeNum: treeNum,
		nums:    nums,
	}
}
//更新某一点的值
func (this *NumArray) Update(index int, val int) {
	a := this.nums[index]
	this.add(index, val-a)
	this.nums[index] = val
}
//计算区间和
func (this *NumArray) SumRange(left int, right int) int {
	return this.ask(right) - this.ask(left-1)
}

type TreeNum struct {
	nums []int
}

func NewTreeNum(n int) *TreeNum {
	return &TreeNum{make([]int, n+1)} //下标从1开始
}

//计算x二进制的最低位1及之后的数。101000 -> 001000
func (t *TreeNum) lowbit(x int) int {
	return x & (-x)
}

//x点值+d
func (t *TreeNum) add(x, d int) {
	x++
	for ; x < len(t.nums); x += t.lowbit(x) { //因为动态维护一个前缀和数组,所以依次都要+d
		t.nums[x] += d
	}
}

//查询x下标处的前缀和
func (t *TreeNum) ask(x int) (ret int) {
	x++
	for ; x >= 1 && x < len(t.nums); x -= t.lowbit(x) {
		ret += t.nums[x]
	}
	return
}
  1. 面试题 10.10. 数字流的秩
type StreamRank struct {
	*TreeNum
}

func Constructor() StreamRank {
	max := 50001
	return StreamRank{
		TreeNum: NewTreeNum(max),
	}
}
//修改值
func (this *StreamRank) Track(x int) {
	this.add(x, 1)
}
//获取点
func (this *StreamRank) GetRankOfNumber(x int) int {
	return this.ask(x)
}

type TreeNum struct {
	nums []int
}

func NewTreeNum(n int) *TreeNum {
	return &TreeNum{make([]int, n+1)} //下标从1开始
}

//计算x二进制的最低位1及之后的数。101000 -> 001000
func (t *TreeNum) lowbit(x int) int {
	return x & (-x)
}

//x点值+d
func (t *TreeNum) add(x, d int) {
	x++
	for ; x < len(t.nums); x += t.lowbit(x) { //因为动态维护一个前缀和数组,所以依次都要+d
		t.nums[x] += d
	}
}

//查询x下标处的前缀和
func (t *TreeNum) ask(x int) (ret int) {
	x++
	for ; x >= 1 && x < len(t.nums); x -= t.lowbit(x) {
		ret += t.nums[x]
	}
	return
}
树状数组是一种用于快速维护数组前缀和的数据结构,其时间复杂度为 $O(logn)$。树状数组实现基于二进制的思想,借助树状数组可以高效地进行单点更新和区间查询等操作。以下是树状数组实现原理: 1. 数组的每个元素代表原数组某个位置的前缀和,如下所示: $$C_i = A_1 + A_2 + \cdots + A_i$$ 其 $C_i$ 表示原数组 $A$ 的前 $i$ 项之和。 2. 将 $C$ 数组转换成树状数组 $T$,树状数组的每个节点表示其父节点到该节点的元素和。如下所示: $$T_i = C_{i-lowbit(i)+1} + C_{i-lowbit(i)+2} + \cdots + C_i$$ 其 $lowbit(i)$ 表示 $i$ 的二进制表示最低位的 $1$ 所代表的值。 3. 树状数组的单点更新操作只需要将 $i$ 位置的值加上 $k$,然后依次更新其祖先节点即可: ```cpp void update(int i, int k, int n, vector<int>& tree) { while (i <= n) { tree[i] += k; i += lowbit(i); } } ``` 其 $n$ 表示原数组 $A$ 的大小,$tree$ 表示树状数组。 4. 树状数组的区间查询操作只需要通过两次前缀和的差值计算出区间和即可: ```cpp int query(int i, vector<int>& tree) { int sum = 0; while (i > 0) { sum += tree[i]; i -= lowbit(i); } return sum; } int query(int i, int j, vector<int>& tree) { return query(j, tree) - query(i - 1, tree); } ``` 其第一个查询函数用于计算 $A_1 + A_2 + \cdots + A_i$,第二个查询函数用于计算 $A_{i+1} + A_{i+2} + \cdots + A_j$,然后两者相减即可得到区间 $[i,j]$ 的和。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值