数据结构-树状数组讲解

树状数组的作用:树状数组是对一个数组 改变某个元素和 求和比较实用的数据结构。其中”求和,更改“两部操做的时间复杂度都是O(log(n)),n为数组元素个数。
在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。
但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。
可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。
当n非常大时,程序会运行得非常缓慢  因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常
================================================================================================
为了便于理解,我们看下图
其中A[]代表的是原有数组,而C[]就是树状数组。C[]存的是A[i-lowbit(i)+1]~A[i]的和。这里lowbit(i)代表的是i的二进制表示中从右往左第一个1对应的值例:lowbit(6)的值为2.{此处也可以真么理解: C[i]表示A[i-2^k+1]到A[i]的和,而k则是i在二进制时末尾0的个数,   同时,我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。 }所以,我们在修改A[i]的时候可以从C[i]依次向上更改调整这条路上的所有C[]值, 这个操作的复杂度在最坏情况下就是树的高度即O(logn)。  另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。


         











     另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。

          不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,

          因此,求和操作的复杂度也是O(logn)。

          接着,我们考察这两种操作下标变化的规律:

          首先看修改操作:

          已知下标i,求其父节点的下标。
          我们可以考虑对树从逻辑上转化:


         如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。

         有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。

         因而父节点下标 p=i+2^k  (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)

         即  p = i + i&(i^(i-1)) 。

         接着对于求和操作:

         因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。

         即  p = i - i&(i^(i-1)) 。

        

         至此,我们已经比较详细的分析了树状数组的复杂度和原理。

         在最后,我们将给出一些树状数组的实现代码,希望读者能够仔细体会其中的细节。

【代码】

  求lowbit(i)


int Lowbit(int t) 

    return t & ( -t ); 

             
  求前n项和:


int Sum(int i) 

    int sum = 0; 
    while(i > 0) 
    { 
        sum += c[i]; 
        i -= Lowbit(i); 
    } 
    return sum; 


 对某个元素进行加法操作: 

void add(int i , int d) 

    while(i<= n) 
    { 
          c[i] += d; 
           i+= Lowbit(i); 
    } 
参考: http://www.cppblog.com/Ylemzy/articles/98322.html

猜你喜欢

转载自blog.csdn.net/u013077144/article/details/51212155