Calmsym学习数据结构与算法之 树状数组(2)


前言

在前一部分的学习中,我们了解了什么是树状数组,它的原理是什么,我们对其做了一定的知识储备
今天我们进一步了解,树状数组如何实现,他又是如何维护数组,等一系列操作的

树状数组的性质

我们用数组C【】来进行维护,数组C可以看作如图所示的树形结构

在这里插入图片描述
图中最下面一行是N个叶节点(N=16),代表数值a【1 ~ N】该结构满足以下性质:
1.每个内部节点 c[x] 保存以它为根的子树的所有叶节点的和
2.每个节点内部 c[x] 的子节点个数等于 lowbit(x)的位数
3.除树根外,每个根内部节点c[x]的父节点是 c[ x+lowbit(x) ]
4.树的深度为O(logN)

维护操作

1.查询前缀和

根据上述性质,求节点x的前缀和的代码实现如下

inline int lowbit(int x){
	return (x) & (-x);
}
//define lowbit(x) ((x) & (-x))

inline int ask(int x){
	int tot=0;
	for(; x; x-=lowbit(x)){
		tot+=c[x];
	}
	return tot;
}

根据前缀和性质,当我们要查询区间 [ L , R ] 时
只需要求 ask ( R ) - ask ( L-1 )

inline int query(int l,int r){
	return ask(r)-ask(l-1);
}

2.单点修改

当给序列 a 中的一个数增加 num 时,我们需要同时完成对点的修改与对前缀和的维护

我们知道当给 a [ x ] 增加一个数num时,只有其本身以及其所有祖先节点保存的区间和需要增加,而任意一个节点的祖先至多只有 logN 个 我们只需逐一更新即可,实现如下:

inline void update(int x,int num){
	for(; x<=n; x+=lowbit(x)){
		c[x]+=num;
	}
}

上述代码的时间复杂度为O( logN )

3.初始化树状数组

在执行所有操作之前,我们需要对树状数组初始化,即建立一个树状数组对原数组进行维护;

一般的初始化方法为,直接建立一个空数组 c 然后依次更新每个位置的前缀和值,代码如下:

inline void build(){
	for(int i=1;i<=n;i++){
		update(i,a[i]);
	}
}

更高效的方法是: 从小到大依次考虑每个节点 x ,借助 lowbit 运算扫描它的子节点并求和

采用这种方法时,其树的每条边只会被遍历一次,时间复杂度为

O ( ∑ k = 1 l o g N k ∗ N / 2 k ) = O ( N ) O( \sum_{k=1}^{logN} { k* N/2^k})= O(N) Ok=1logNkN/2k=O(N)

总结与思考

关于树状数组的基础学习到这里就结束了,对于这一数据结构在处理逆序对等问题上效果优异。在关于数据结构的学习之后一定要多加刷题来巩固知识。

下一部分我们将继续学习一种更加通用的数据结构—线段树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值