树状数组的理解

目录

1.树状数组 

1.问题引入

1.问题

2.朴素算法:O(n^2)

3.树状数组:O(nlog(2)n)

2.树状数组引入分级管理制度

1.区间长度

2.前驱和后继

3.查询区间和

4.算法实现

5.树状数组的局限性

2.例题

 1.思路

2.代码

3. 总结



1.树状数组 

1.问题引入

1.问题

给出一个长度为n的数组,完成以下两种操作

1.将第x的数加上k

2.输出区间[x,y]内每个数的和

2.朴素算法:O(n^2)

1.单点修改O(1)

2.区间查询O(n^2)

3.树状数组:O(nlog(2)n)

1.单点修改:O(log(2)n)

2.区间查询:O(log(2)n)

2.树状数组引入分级管理制度

 树状数组又被称为二进制所引树,通过二进制分解划分区间

1.区间长度

若i的二进制表示末尾有k个连续的0,则c[i]存储的区间长度为2^k,从a[i]向前数2^k个元素,

即c[i] = a[i - 2^k +1] + a[i- 2^k+2]+...+a[i]

计算区间长度

2.前驱和后继

直接前驱: c [ i ]的直接前驱为 c [ i - lowbit ( i )],即 c [ i ]左侧紧邻的子树的根。

直接后继: c [ i ]的直接后继为 c [ i + lowbit ( i )],即 c [ i ]的父节点。 前驱: c [ i ]左侧所有子树的根。(计算前缀和) 后继: c [ i ]的所有祖先。(更新点)

树状数组不能从0开始,如果从0开始,进行lowbit的操作,会进入死循环

例如:如果我们要计算sum[9],那么只需要计算c[9] + c[8]

如果a[5]+10,那么只需要改变c[5],c[6]...

3.查询区间和

若要求值[i,j],则求解前j个元素的和值减去前i-1个元素的和即可

即sum[j]-s[i-1]

4.算法实现

#include<stdio.h>
int max = 10005;
//定义数组和树状数组 
int n, a[10005] = {0}, c[10005] = {0};
//c[i]的区间长度
int lowbit(int i) { 
	return (-i) & i;
} 
//对点进行更新 
void add(int i, int z) {
	//更新所有的后后继 
	for(; i <= n; i += lowbit(i)) {
		c[i] += z;
	}
} 
//前缀和 
int sum(int i) {
	int s = 0;
	//累加所有的前驱 
	for(; i > 0; i -= lowbit(i)){
		s += c[i];
	}
	return s;
}
//区间和 
int  sum(int i, int j) {
	return sum(j) - sum(i - 1); 
}
int main() 
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d", &a[i]);
		add(i, a[i]);//加入树状数组 
	}
	int x1, x2;
	scanf("%d", &x1);
	printf("%d\n",sum(x1));
	scanf("%d%d",&x1,&x2);
	printf("%d\n",sum(x1,x2));
	return 0; 
}

算法分析

点更新时,从叶子节点更新到树根,执行的次数不超过树的高度O(logn)。

前缀和查询时,从当前节点一直查询前驱,前驱的个数不超过O(logn)。

5.树状数组的局限性

2.例题

1626. 无矛盾的最佳球队 - 力扣(Leetcode)

 1.思路

我们需要一个没有矛盾的团队,那么就是需要对年龄进行排序,然后利用树状数组,进行区间求和,后面就可以解决问题。

2.代码

#define MAX(a, b) ((a) > (b) ? (a) : (b))//用于判断两个值的大小
//二维数组调用快排
static int cmp(const void *pa, const void *pb) {
     //如果分数相同就按照年龄排序
    if (((int *)pa)[0] == ((int *)pb)[0]) {
        return ((int *)pa)[1] - ((int *)pb)[1];
    }
    return ((int *)pa)[0] - ((int *)pb)[0];
}
//树状数组得区间长度
int lowbit(int x) {
    return x & (-x);
}
//更新后继
void update(int i, int val, int *t, int max_age) {
    for (; i <= max_age; i += lowbit(i)) {
        t[i] = MAX(t[i], val);
    }
}
//前缀和
int query(int i, const int *t) {
    int ret = 0;
    for (; i > 0; i -= lowbit(i)) {
        ret = MAX(ret, t[i]);
    }
    return ret;
}

int bestTeamScore(int* scores, int scoresSize, int* ages, int agesSize) {
    int max_age = 0;
    //求出年龄最大
    for (int i = 0; i < agesSize; i++) {
        max_age = MAX(max_age, ages[i]);
    }
    //定义树状数组
    int t[max_age + 1];
    memset(t, 0, sizeof(t));
    int res = 0;
    //建立二维数组
    int people[scoresSize][2];
    int peopleSize = scoresSize;
    for (int i = 0; i < scoresSize; ++i) {
        people[i][0] = scores[i];
        people[i][1] = ages[i];
    }
    //进行排序
    qsort(people, peopleSize, sizeof(people[0]), cmp);
    //求出最大的无矛盾的
    for (int i = 0; i < peopleSize; ++i) {
        int score = people[i][0], age = people[i][1], cur = score + query(age, t);
        update(age, cur, t, max_age);
        res = MAX(res, cur);
    }
    return res;
}

3. 总结

我们通过树状数组的分析,了解到对于前缀和,区间和,点更新这三种算法的时间复杂度更小的求解方式,但是他还是有一定的局限性的,所以如果我们要解决其他问题还是要使用线段树来进行求解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值