【数据结构】数组区间更新-IndexTree(树状数组)

在前段时间,我们介绍过线段树,线段树是解决在数组区间上进行快速的增删改查操作。而今天我们讲得IndexTree也是为了达到这样类似的效果。
本期文章源码:GitHub

一、介绍

例题:给定一个数组arr,arr的长度是1000,现在问你如何快速的计算500 ~ 1000之间,所有的数的累加和??

可能你会说直接一个for循环,从500开始累加不就行了吗? 这样确实可以达到目的,但是还是不够快。

又或许,你会想到用前缀和数组,先计算每一个index,从0下标一直累加到index位置。这样一个前缀和数组,也是能达到相应的效果。如下:

image-20220218141543446

如上图所示,假设我们需要计算 3 ~7 范围上的累加和,我们只需要用 (1 ~ 7)累加和 减去(1 ~ 2)累加和,这样就能得到想要的答案。

但还是存在一个问题,计算前缀和当然很简单,假设原数组的数据经常改动,那么前缀和数组也需要跟着改动,由于这个问题,导致使用前缀和数组计算范围上的累加和存在一定的缺陷。所以最后就进行优化,搞出了现在听说的树状数组IndexTree

IndexTree原理

在上述的前缀和数组的基础之上,我们需要改一下前缀和的计算规则,如下所示:

image-20220218145330593

image-20220218145351514在这里插入图片描述

总结起来就是,在计算index位置的累加和时,先看左边是否有长度一样的累加和,有的话,二者就合并在一起,形成2倍长度。

然后你就会发现,在下标是(2的某次幂时),此时的位置是计算1index位置所有的累加和,例如4下标,就是管理14范围全部的累加和。

这样的设计,有何作用呢

例如,12下标的二进制是 0000 1100,将最靠后的1,放到最后一个位置去,如下:

image-20220218150540610

移动之后的值就是9,此时你会发现 下标12所管理的累计和就是 9 ~ 12范围。如图

image-20220218151414895

就是根据这样的规律,我们可以在改动原数组的数据时,能够很快的查询到,这次改动会影响到哪些位置的数据,这样就能很快的进行修改。

比如,我们现在更改9 ~ 12范围内的任意一个数,就假设改动下标10的数据吧,可以根据以下计算规则,就能计算到会影响12下标。

image-20220218152852831

线段树和IndexTree的区别???

可能你会说,你上面说的是区间更新操作,线段树也可以完成啊,为啥非要再搞一个IndexTree?闲的没事做吗?

显然不是的。确实区间更新操作,线段树能够做到,但是只对一个下标进行更新,使用线段树,就有点多余了。比如add(10, 10, -100),在L= 10,R=10的位置,插入-100,线段树这样做并不够高效,而IndexTree就是为了实现这样的,只改定一个位置的数据。

并且indexTree,能够很好的改成二维的效果,比如给你一个二维数组,计算某一块矩形的面积,这种就可以将IndexTree改为二维数组的形式,也是能够很快的解决问题。

简单来说,线段树确实能够实现区间更新,但是不好改成二维数组的形式;IndexTree,能够实现单点更新,还能改二维数组,也是一种比较好用的数据结构。

LeetCode矩形面积:https://leetcode-cn.com/problems/range-sum-query-2d-mutable/

二、IndexTree的实现

1、add方法

在知道原理以后,我们就可以很轻松的写出相应的代码了,关键就是记住 index += (-index & index)

public class IndexTree {
    private int[] nums; //原数组
    private int[] tree; //累加和数组
    private int length; //数组长度
    
    public IndexTree(int N) {
        this.length = N + 1; //要舍弃0下标的空间
        this.nums = new int[length];
        this.tree = new int[length];
    }
    
    //在index位置放入,val值  
    public void add(int index, int val) {
        if (index < 1 || index >= length) {
            return; //越界的情况
        }
        nums[index] += val; //改动原数组
        for (int i = index; i < length; i += (i & -i)) {
            tree[i] += val; //改累加和数组
        }
    }
}

2、update方法

更新操作,是将原数组的数据,改为另外一个数据。影响的范围还是从index位置向后的情况。与add方法类似

public void update(int index, int newVal) {
    if (index < 1 || index >= length) {
        return;//越界的情况
    }
    int value = newVal - nums[index]; //计算与之前的差值,最后累加到tree数组即可
    nums[index] = newVal; //改为新的数据
    for (int i = index; i < length; i += (i & -i)) {
        tree[i] += value; //累加差值即可
    } 
}

3、query方法

add方法和update方法,改动数据,是影响到index后面位置的数据。而query查询方法,是从1~index位置进行累加,所有从index位置开始计算,每次需要减去(index & -index)。

//查询1 ~ index位置的累加和
public int query(int index) {
    if (index < 1 || index >= length) {
        return -1; //越界的情况
    }
    int res = 0;
    for (int i = index; i > 0; i -= (i & -i)) {
        res += tree[i];
    }
    return res;
}

好啦,本期更新到此结束,我们下期见!!!

  • 27
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 26
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听雨7x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值