博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
树状数组 讲解
阅读量:5014 次
发布时间:2019-06-12

本文共 3098 字,大约阅读时间需要 10 分钟。

 

1 概述  2  3   树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],  用lowbit函数维护了一个树的结构那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构, 4   支持随时修改某个元素的值,复杂度也为log级别。 5   来观察这个图: 6   令这棵树的结点编号为C1,C2...Cn。令每个结点的值为这棵树的值的总和,那么容易发现: 7   C1 = A1 8   C2 = A1 + A2 9   C3 = A310   C4 = A1 + A2 + A3 + A411   C5 = A512   C6 = A5 + A613   C7 = A714   C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A815   ...16   C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A1617   这里有一个有趣的性质:18   设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,19   所以很明显:Cn = A(n – 2^k + 1) + ... + An20   算这个2^k有一个快捷的办法,定义一个函数如下即可:21   int lowbit(int x){22   return x&(x^(x–1));23   }24   当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:25   step1: 令sum = 0,转第二步;26   step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;27   step3: 令n = n – lowbit(n),转第二步。28   可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:29   n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。30   那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。31   所以修改算法如下(给某个结点i加上x):32   step1: 当i > n时,算法结束,否则转第二步;33   step2: Ci = Ci + x, i = i + lowbit(i)转第一步。34   i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。35   对于数组求和来说树状数组简直太快了!
1 int lowbit(int x) 2 { 3     return x&(-x); 4 } 5  6  7 int sum(int k) 8 { 9     int ans=0;10     while(k>0)11     {12         ans+=c[k];13         k=k-lowbit(k);14     }15     return ans;16 }17 18 19 int add(int pos ,int num)20 {21     while(pos<=n)22     {23         c[pos]+=num;24         pos+=lowbit(pos);25     }26 }

 

与线段树的比较  树状数组是一个可以很高效的进行区间统计的数据结构。在思想上类似于线段树,比线段树节省空间,编程复杂度比线段树低,但适用范围比线段树小。  以简单的求和为例。设原数组为a[1..N],树状数组为c[1..N],其中c[k] = a[k-(2^t)+1] + ... + a[k]。比如c[6] = a[5] + a[6]。也就是说,把k表示成二进制1***10000,那么c[k]就是1***00001 + 1***00010 + ... + 1***10000这一段数的和。设一个函数lowestbit(k)为取得k的最低非零位,容易发现,根据上面的表示方法,从a[1]到a[k]的所有数的总和即为sum[k] = c[k] + c[k-lowestbit(k)] + c[k-lowestbit(k)-lowestbit(k-lowestbit(k))] + ... 于是可以在logk的时间内求出sum[k]。当数组中某元素发生变化时,需要改动的c值是c[k],c[k+lowestbit(k)], c[k+lowestbit(k)+lowestbit(k+lowestbit(k))] ... 这个复杂度是logN (N为最大范围)  扩展到多维情况:以二维为例,用c[k1][k2]表示c[k1-(2^t1)+1][k2-(2^t2)+1] + ... + c[k1][k2]的总和。可以用类似的方法进行处理。复杂度为(logn)^k (k为维数)  树状数组相比线段树的优势:空间复杂度略低,编程复杂度低,容易扩展到多维情况。劣势:适用范围小,对可以进行的运算也有限制,比如每次要查询的是一个区间的最小值,似乎就没有很好的解决办法。  多维情况的几道题目:  POJ 2155 Matrix  URAL 1470 UFOs  其中POJ 2155是一道很不错的题目,表面上看,这题的要求似乎和树状数组的使用方法恰好相反,改变的是一个区间,查询的反而是一个点。实际上可以通过一个转化巧妙的解决。  首先对于每个数A定义集合up(A)表示{A, A+lowestbit(A), A+lowestbit(A)+lowestbit(A+lowestbit(A))...} 定义集合down(A)表示{A, A-lowestbit(A), A-lowestbit(A)-lowestbit(A-lowestbit(A)) ... , 0}。可以发现对于任何A
二维树状数组:int lowbit(int x){    return x&(-x);}void add(int x,int y,int d)//或for循环  s 是节点个数{    int i=x;    int j=y;    while(i<=s)    {        j=y;        while(j<=s)        {            map[i][j]+=d;            j=j+lowbit(j);        }        i=i+lowbit(i);    }}int sum(int l,int b){    int i=l;    int j=b;    int ans=0;    while(i>0)    {        j=b;        while(j>0)        {            ans+=map[i][j];            j-=lowbit(j);        }        i-=lowbit(i);    }    return ans;}

 

转载于:https://www.cnblogs.com/acSzz/archive/2012/04/20/2459573.html

你可能感兴趣的文章
删除U盘时提示无法停止‘通用卷’设备的解决方法!!不要每次都硬拔了,对电脑有不小的损害!!!...
查看>>
Java中接口与接口和类之间的关系
查看>>
芯片TPS70925
查看>>
linux shell 发送email 附件
查看>>
人群密度估计 CrowdCount
查看>>
JSON.parse()和JSON.stringify()
查看>>
.net 常用正则表达式
查看>>
Java泛型中的标记符含义:
查看>>
初遇GitHub
查看>>
[C# 网络编程系列]专题八:P2P编程
查看>>
Jsの练习-数组常用方法 -forEach()
查看>>
动态绑定treeview的方法
查看>>
jvm参数
查看>>
3-1 案例环境初始化
查看>>
读《构建之法》第四章和十七章有感
查看>>
01背包
查看>>
开发一个12306网站要多少钱?技术分析12306合格还是不合格
查看>>
Selenium 入门到精通系列:六
查看>>
HTTP与TCP的区别和联系
查看>>
android 实现2张图片层叠效果
查看>>