树状数组

  • 从一个简单的问题谈起

    考虑一个简单的问题,给定一串有序数字A1...An并不断进行插入数的操作且保证始终有序,在修改过程中陆续给定若干数字N1,N2...Nm,求解A1到ANi的数的和的大小。

    我们试想,如果维护一个简单的array A来存储这串数的话,插入不是问题,时间是O(1),但是问题是如果询问m次,有n个数的话,查询区间大小的时间是O(n * m),显然如果在n和m较大的情况下是十分耗时的。那么换种思路,如果我们维护一个array B存储的是从1到i这段区间的数的和。那么会使查询时间优化至O(1),但面临的问题是每次修改都会需要变化从修改位置pos到n的所有数字的数值大小,显然修改操作是O(n * m)的时间复杂度。也是十分耗时的。
    那么有没有一种数据结构可以解决这种问题呢。或者说,使插入和查找时间复杂度都折中的一种结构?那么下面介绍的就是插入和查找都是O(mlogn),那么他怎么实现的呢?

  • 写在树状数组之前

    再次考虑一个问题,给定一个数N,如何将其操作后变成二进制只保留其最低位的1呢。举个例子,如10110(十进制24),经过操作以后变成00010。

    这个问题的其实还是很简单的,无非是找到一个方法使得保留最低位的1的情况下,使得别的位取反或置0。那么可以想到一个方法就是将原数取反后加1,这样在最低位的1高位的数仍然是与原数对应位置的数是相反的,而最低位1低位的数则全部由1进位为0,取反后成为0的最低位1经过进位后重新成为1.得到的新的数与原数进行与操作,即可得到题意求解的答案。
    拿举的例子操作一下,00010110 取反后 -> 11101001 加1-> 11101010 与原式子00010110与 -> 00000010,得解
    如果写成代码如下:

    x & -x

    这就是树状数组的灵魂操作——lowbit,它的做什么的呢,下面做介绍。

  • 先浅谈下状数组的表象
    特别强调了,虽然它的名字后缀是数组,但是归根到底,这就是棵树。先看看他的样子:树状数组
    其中array A代表的是原串数组,而C便是树状数组。我们先通过直观感受找找规律。
    C[1] = A[1]
    C[2] = C[1] + A[2] = A[1] + A[2]
    C[3] = A[3]
    C[4] = C[2] + C[3] + A[4] = A[1] + A[2] + A[3] + A[4]
    C[5] = A[5]
    C[6] = C[5] + A[6] = A[5] + A[6]
    C[7] = A[7]
    C[8] = C[4] + C[6] + C[7] + A[8] = (C[2] + C[3] + A[4]) + (A[6] + C[5]) + A[7] + A[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]
    通过上面的八个式子,我们可以感受到树状数组mlogn的原理——二分
    C[2]是分为1 (A[1])和 2 [C[1]]两边,C[4]是分为1,2 (C[2])一边,3,4 (C[3]A[4])一边。C[8]是分为1~4 (C[4])一边,5~8 (C[6],C[7],A[8])一边。经过二分,在查找的时候,时间复杂度就降到了O(n),举个例子,如果我们要计算1~5这段区间的值的话,就是C[4] + C[5],同样,如果要是计算1~7的值,其实就是C[4] + C[6] + A[7],说到这里可能很多不知道为什么要这么计算,只是从表面上看是这样算出来的,其实它的计算过程是这样的,拿到一个数n我们先看它满足的最高位1是多少,如5和7都是100也就是4,那么加上C[4],然后进一步求次高位的1,发现5不满足110而7满足,所以7再加上C[6],发现5满足101所以加上C[5],而7已经包含110了,所以继续往下找是111,也满足,再加上C[7]。得解。
    当然,或许有人说,当添加7的时候,对C[7]还是未知的,那么其实也很简单,只需要利用递归的思想,求解1~6的和后再加上A[7].
  • array C与array A的关系
    现在的当务之急或许应该是求得怎么通过array A求array C,这就用上了前面提到的lowbit操作。我们还是通过观察找规律先,下面我们统一用二进制描述数字,这样比较方便。
    C[0001] = A[0001] (0001对应的是从0001开始递减1的1个数)
    C[0010] = A[0001] + A[0010] (0010对应的是从0010开始递减1的2个数)
    C[0011] = A[0011] (0011对应的是从0011开始递减1的1个数)
    C[0100] = A[0001] + A[0010] + A[0011] + A[0100] (0100对应的是从0100开始递减1的4个数)
    C[0101] = A[0101] (0101对应的是从0101开始递减1的1个数)
    C[0110] = A[0101] + A[0110] (0110对应的是从0110开始递减1的2个数)
    C[0111] = A[0111] (0111对应的是从0111开始递减1的1个数)
    C[1000] = A[0001] + A[0010] + A[0011] + A[0100] + A[0101] + A[0110] + A[0111] + A[1000] (1000对应的是从1000开始递减1的8个数)
    通过上面的式子,我们是不是可以清晰的发现,其实C[i]就是从i开始的递减1的lowbit(i)个数?这样我们就得到了array A和array C的关系,下面就剩两个解决问题的操作,即如何进行求和和插入修改?
  • 两个关键操作
    对于添加操作,我们发现,从二进制考虑其实是改变包含与它的所有高位1,在8这个范围里,我们举个例子,这样可以参考上面的图。如果添加3的话,需要改变哪些数字?显然可以得到需要改变C[4],C[8],为什么不需要改变剩下的数字呢,因为它们完全不包含3这个数字。由上面我们得到的结论,每个数字i只包含从该数开始的递减1的lowbit(i)个数,所以反之添加的时候我们需要改变的其实就是从该数字i开始的x个lowbit(i)个数,且这是一种包含操作,即每次需要更新i的值。
    按照3我们进行计算,0011,lowbit(0011) = 0001, 0011+0001 = 0100,此时改变C[4]的值。i = 0100,lowbit(0100) = 0100,0100 + 0100 = 1000,改变C[8]的值,下次操作肯定超出数值范围8,操作结束。
    如果用代码表示的话,则如下:

    void add(elemtype x, val){
        tree[x] += val;
        x += x & -x;
    }

    解决了插入操作后,求和操作就非常简单了,其实就是添加的反操作,就是将从数字i开始递减lowbit(i)的数对应的树状数组的值进行叠加。当然也是包含操作,代码如下:

    elemtype check(elemtype x){
        elemtype sum = 0;
        while(x > 0){
            sum += tree[x];
            x -= x & -x;
        }
    }
  • 树状数组小结
       树状数组可以解决很多问题,最经典的莫过于多有真子集问题,当模型和模型之间存在着包含关系的时候,求解每个集合真子集的个数等问题可以用树状数组轻松解决。我的另外几篇文章就是介绍了有关几道树状数组的题目,有兴趣可以加以研究。树状数组能让在维护数组的基础上求和的时间复杂度在O(mlogn)级别,在面对大量的n的时候尤为有效。而且它的思想很简单,代码十分简洁,是为广大算法爱好者所喜欢和欣赏的一个数据结构。

查看原文:http://chilumanxi.org/2016/02/05/%e6%a0%91%e7%8a%b6%e6%95%b0%e7%bb%84/

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值