算法:树状数组

树状数组解决的两个问题:

  1. 快速求前缀和 O(log2n)
  2. 修改某一个数 O(log2n)

上面两个操作分别用可以实现相应的做法

  1. 前缀和 O(n) O(1)
  2. 数组 O(n) O(1)

树状数组折中

算法原理基于二进制算法:

  • 每一个数可以被分为最多log(x)个部分
    在这里插入图片描述
    可以使得我们在log(n)的时间复杂度之内,求出前n项的前缀和,分析一个上面每个区间分别由几个数(绿字):
    在这里插入图片描述
    再观察每个区间右端点和区间长度的关系:
    在这里插入图片描述
    假设一个左开右闭区间(L , R],其区间长度一定是R的二进制表示的最后一位1,所对应的次幂:即lowbit(x),换成闭区间要加1:
    在这里插入图片描述
    我们用C[R]来表示:
    在这里插入图片描述
    用C[x]来表示:该区间所有树的和
    在这里插入图片描述

画图表示分别以x = 1 ~ 16结尾的长度是lowbit(x)的区间
在这里插入图片描述
C[16] = A[16] + C[15] + C[14] + C[12] + C[8];
用后面的Tr[i]表示
就是Tr[i] = A[i] + Tr[i - 1] + Tr[i - 1 - lowbit(i - 1)] + Tr[i - 1 - lowbit(i - 1) - lowbit(i - 1 - lowbit(i - 1))] + …
这里
i = 16 , i - 1 = 15, i - 1 - lowbit(i - 1) = 14, 对应14 - lowbit(14) = 12; 12 -lowbit(12) = 8;
在这里插入图片描述
以此类推所由不同C的关系
在这里插入图片描述
代码验证:假设一个数组_nums[8] = {1,2,3,4,5,6,7,8};
每次开始时先写以下三个函数:
在这里插入图片描述

初始化:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
得到tr[]数组的过程以及最终tr为:

i = 1, tr[1] = 1
i = 2, tr[2] = 1
i = 4, tr[4] = 1
i = 8, tr[8] = 1

i = 2, tr[2] = 3
i = 4, tr[4] = 3
i = 8, tr[8] = 3

i = 3, tr[3] = 3
i = 4, tr[4] = 6
i = 8, tr[8] = 6

i = 4, tr[4] = 10
i = 8, tr[8] = 10

i = 5, tr[5] = 5
i = 6, tr[6] = 5
i = 8, tr[8] = 15

i = 6, tr[6] = 11
i = 8, tr[8] = 21

i = 7, tr[7] = 7
i = 8, tr[8] = 28

i = 8, tr[8] = 36

tr[1] = 1 tr[2] = 3 tr[3] = 3 tr[4] = 10 tr[5] = 5 tr[6] = 11 tr[7] = 7 tr[8] = 36

和上图对照看,求区间和时的公式
在这里插入图片描述
在这里插入图片描述
注意这里query传给tr数组,其下标为[1,n],sumRange传的是原数组num的小标i,j,范围是[0,n-1],所以这里假设要求nums [0,7]的区间和,就是求前缀和(下标加一)sum[8] - sum[0], 而sum[8]就是根据tr[8] + tr[8 - lowbit(8)] + … 这里正好8 - lowbit(8)就是0,所以sum[8]就是tr[8],但是这只是一个巧合,假设是求nums下标为[2,10]的区间和,就是sum[11]- sum[2],这里的sum[11],sum[11] = tr[11] + tr[10] + tr[8]…

回过来对于 nums[0,7] 就是query(8) - query(0), query(8)只会加一个 tr[8] = 36,因为减去lowbit(8)就变成0,

假设求nums[2, 6], 就是求原区间下标[2,6]的和 对应了下标加一的sum 为 sum[7] - sum[2], 即query(7) - query(2), 在query(7) 里求sum[7]的过程为

  • res = tr[7] + tr[6] + tr[4]

验证入下:
在这里插入图片描述

x为:7
i = 7, tr[7] = 7, res = 7
i = 6, tr[6] = 11, res = 18
i = 4, tr[4] = 10, res = 28

7 (111)2 被分为三个区间 ,每个区间长度为lowbit®

(110, 111] 十进制(6,7]
(100,110] 十进制(4,6]
(000,100] 十进制 (0,4]

至此验证完毕,对于单点更新或者查询和来说树状数组可以在logn的时间内完成,对Q个数插入add的时间复杂度为O(Qlogn),初始化时若用定义初始化tr数组可以控制时间复杂度为O(n) 总时间复杂度为 O(n + Qlogn)
在这里插入图片描述
以下为初始化时间复杂度为O(nlogn)以及O(n)的两种写法

//O(nlog(n))插入
        // for (int i = 0; i < n; i++) add(i + 1, _nums[i]);
        //O(n)插入,用到定义, 第二层循环会在常数个时间内解决
        for (int i = 1; i <= n; i ++ ) {
            tr[i] = nums[i - 1]; //  Tr[i] = A[i] + Tr[i - 1] + Tr[i - 1 - lowbit(i - 1) ... ] ,外部的Tr[i]是从1开始的所以比nums[]多加了一个
            for (int j = i - 1; j > i - lowbit(i); j -= lowbit(j))
                tr[i] += tr[j];
        }
————————————————
版权声明:本文为CSDN博主「肖源杰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Heck_Jacke/article/details/123654590

假设一个x:
在这里插入图片描述
C[x]就等于 自己这个数A[x],加上前面的C[]:
在这里插入图片描述
x -1 最后的k个1由可以拆分成以下几个儿子,(以下的左开右闭区间)
在这里插入图片描述
每一次去掉最后一个1
在这里插入图片描述
直到去k次,就把所有子节点找出来了。

  • 如何通过子节点找到父节点(对应修改的操作)
    在这里插入图片描述
    修改完x之后,直接影响到的区间,是唯一的,一定是x最左边的1左边的0变成1的那个数P 区间内的数受影响,很明显 P= x + lowbit(x),只要加上x最右边的1就可以变成以此增加就可以得到P
    在这里插入图片描述
    在这里插入图片描述
    所以这是一个迭代的过程,每往上走一次,末尾0的个数都会至少加一个,而最多只有log(x)位,这个过程最多只会进行log(x)次,该区间的每个数都被修改。
    tr[i]表示以i结尾的长度是lowbit(i)的区间和

  • 修改某一个数: 修改以后其后面的数都要变化,从变化到尾;
    在这里插入图片描述

  • 查询1 ~ x的和:从左到右加到x,注意tr数组下标要从1开始,不然当下标为0时,在这里进入死循环

在这里插入图片描述

每一个循环都是log(n)的

简单应用:leetcode. 区域和检索 - 数组可修改
答案:307.区域和检索-数组可修改

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值