【面试常见算法整理】Binary Indexed Tree(Fenwick Tree,树状数组)详解

问题:求一个数组中连续n项的和。

首先想到的肯定是做一个循环,把这个连续的n项加起来,时间复杂度为O(n)。复杂度为n,看起来还不错,再说了求n个数的和,怎么也要加n次吧,所以说这应该就是最优解了,但是一提交结果是Time Limit Exceeded,顿时傻眼了,难道还有复杂度更低的方法?

会不会有O(logn)的解法?

O(n)的那个算法,如果只操作一次还是可以接受的,但是如果需要大量的求和操作,比如第一次求下标(1,1234)的和第二次求下标(2,1024)的和,很容易发现在第一次计算的过程中(2,1024)的和是计算过的,只是没有保存下来,导致第二次求和的时候还要再算一遍。你有没有想过,如果事先把一部分的和先计算并保存起来,这样会不会更快一些呢?

Binary Indexed Tree(BIT)

其实树状数组(Binary Indexed Tree(BIT), Fenwick Tree)就是这样做的,他是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值。

核心思想:

  • 树状数组中的每个元素是原数组中一个或者多个连续元素的和。
  • 在进行连续求和操作a[1]+…+a[n]时,只需要将树状数组中某几个元素的和即可。时间复杂度为O(lgn)

下面是一个示意图

示意图

a[]: 保存原始数据的数组
e[]: 树状数组,其中的任意一个元素e[i]可能是一个或者多个a数组中元素的和。如e[2]=a[1]+a[2]; e[3]=a[3],e[4]=a[1]+a[2]+a[3]+a[4]。
e[i]中的元素:如果数字 i 的二进制表示中末尾有k个连续的0,则e[i]是a数组中2^k个元素的和,则e[i]=a[i-2^k+1]+a[i-2^k+2]+…+a[i-1]+a[i]。也就是说,e[i]中每一个元素管理着a[]中若干个元素的和,并且各个元素管理的区间没有重叠。

    如:4=100(2)  e[4]=a[1]+a[2]+a[3]+a[4];
      6=110(2)  e[6]=a[5]+a[6]
      7=111(2)  e[7]=a[7]
      
计算2^k的两个方法

  • 2^k = (i & (-i)); (利用机器补码特性)
  • 2^k = (i & (i^(i-1));

父节点,子节点

父节点

是离它最近的,且编号末位连续0比它多的就是它的父亲,如e[2]是e[1]的儿子;e[4]是e[2]的儿子。
e[4] = e[2]+e[3]+a[4] = a[1]+a[2]+a[3]+a[4] ,e[2]、e[3]的后继就是e[4]。

计算方法

lowbit(i) = ( (i-1) ^ i) & i ; //或者(i & (-i))
节点e[i]的父节点为 e[ i - lowbit(i) ]

子节点

最近的,编号即为比自己小的,最末连续0比自己多的节点。如e[7]的子节点是e[6],e[6]的子节点是e[4]

计算方法

lowbit(i) = ( (i-1) ^ i) & i ; //或者(i & (-i))
节点e[i]的子节点为 e[ i + lowbit(i) ]

实现代码

public class NumArray {

    private int[] tree; //Binary Indexed Tree
    private int[] nums; //原始数组

    public NumArray(int[] nums) {
        this.nums = nums;
        int sum = 0;
        int lowbit;
        tree = new int[nums.length + 1];
        for (int i = 1; i < tree.length; i++) {
            sum = 0;
            lowbit = i & ((i - 1) ^ i);
            for (int j = i; j > i - lowbit; j--) {
                sum = sum + nums[j - 1];
            }
            tree[i] = sum;
        }
    }

    //更新
    void update(int i, int val) {
        int tem = val - nums[i];
        nums[i] = val;
        i++;
        for (; i < tree.length; i = i + (i & ((i - 1) ^ i))) {
            tree[i] += tem;
        }
    }

    public int sumRange(int i, int j) {
        return getSum(j) - getSum(i - 1);
    }

    //求和
    public int getSum(int i) {
        int sum = 0;
        i++;
        while (i > 0) {
            sum = sum + tree[i];
            i = i - (i & ((i - 1) ^ i));
        }
        return sum;
    }
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Fenwick Tree,又称为树状数组Binary Indexed Tree),是一种基于数组实现的数据结构,用于高效地动态维护前缀和。它可以在O(logn)的时间内完成以下操作:更新某个元素的值,查询某个区间的和。Fenwick Tree的实现原理是将数组分解为一系列的区间和,每个区间和保存在树状数组的相应位置上。通过使用二进制的技巧,可以高效地计算每个区间和。: ``` public class FenWickTree { private int[] values; private int[] bit; public FenWickTree(int length) { values = new int[length]; bit = new int[length + 1]; } public void setValues(int index, int value) { values[index = value; index += 1; while (index < bit.length) { bit[index += value; index += index & -index; } } public int getSum(int index) { int sum = 0; while (index > 0) { sum += bit[index]; index -= index & -index; } return sum; } } ``` 以上代码展示了如何使用Fenwick Tree实现动态维护前缀和的功能。其中setValues()方法用于更新某个元素的值,getSum()方法用于查询某个区间的和。 另外,Fenwick Tree也可以用来解决区间修改的问题。对于元素的修改,我们可以视为区间查询的逆过程,通过从叶节点开始向上更新父节点,依次对每个父节点进行相同的修改操作。具体的实现可以参考下面的示例代码: ``` class FenwickTree { private int[] tree; public FenwickTree(int n) { tree = new int[n + 1]; } public int lowbit(int x) { return x & (-x); } public void add(int i, int val) { while (i < tree.length) { tree[i += val; i += lowbit(i); } } public int query(int i) { int res = 0; while (i > 0) { res += tree[i]; i -= lowbit(i); } return res; } } ``` 这段代码展示了如何使用Fenwick Tree解决区间修改的问题。add()方法用于修改某个元素,query()方法用于查询某个区间的和。 综上所述,Fenwick Tree是一种用于高效地动态维护前缀和的数据结构,可以在O(logn)的时间内完成更新和查询操作。同时,它也可以应用于区间修改的问题。 #### 引用[.reference_title] - *1* *3* [【数据结构与算法】树状数组](https://blog.csdn.net/zzy_NIC/article/details/130616434)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [6.10 Fenwick树](https://blog.csdn.net/m0_66201040/article/details/122923027)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值