• 树状数组解决数组单点更新后快速查询区间和的问题


    要解决的问题

    数组在不变的情况下,前缀和数组可以用来加速生成i ~ j位置的累加和信息, 假设前缀和数组为preSum,那么i...j的累加和

    sum[i...j] = preSum[j] - preSum[i-1]

    但是如果数组要单点修改,则以上的情况不适用,树状数组(index tree)就是解决这个问题,时间复杂度可以达到O(logN)。

    注:本文所有涉及到的数组操作均从下标1开始计算,下标0位置弃而不用

    用辅助数组保存累加信息

    申请一个辅助数组help, 大小和原始数组arr一样,保存原始数组的累加信息,但是要基于如下规则来保存。

    遍历的位置i如果是奇数,则只管自己这个位置的内容,即:arr[i] = help[i]

    遍历的位置i如果是偶数,则按如下规律:

    00100001 ~ 0010的累加和,
    0010000001 ~ 00100的累加和,
    01100101 ~ 0110的累加和

    ...

    即某个偶数位置接管的区间是:把这个偶数位置二进制最右边的1抹掉后再加1得出的位置一直到这个位置本身。

    比如:

    1010111000这个位置,把最右边的1抹掉后,值为1010110000,再加1,值为1010110001,所以1010111000这个位置接管的累加和是从1010110001 ~ 1010111000

    通过以上做法生成help数组后,我们可以通过这个help数组快速响应单点更新,并快速求得前缀和。

    根据help数组快速计算前缀累加和

    按如上流程生成help数组后,如果要计算1..i位置的累加和,则有如下规则:

    第一步,提取出i最右侧的1,假设为x,将help[i] + help[i-x],得出a1
    第二步,继续提取i-x最右侧的1,假设为y,将a + help[i- x - y],得出a2
    ...
    直到i提取完所有最右侧的1,求累加,得到的结果即为1...i上的累加和。

      public int sum(int index) {
       int ret = 0;
       while (index > 0) {
        ret += tree[index];
        index -= index & -index;
       }
       return ret;
      }
    

    其中index&-index即为index最右侧的1。

    i位置的前缀和

    如果单点有更新,比如需要在index位置上的值增加一个d,此时需要考虑哪些位置受到了牵连。

    单点的二进制最末尾的1加1,依次到数组结尾,都是受到牵连的位置

      public void add(int index, int d) {
       while (index <= N) {
        tree[index] += d;
        index += index & -index;
       }
      }
    

    index += index & -index; 即为受到牵连的位置,所以这些位置都要执行加d的操作。

    线段树 VS 树状数组

    线段树是树状数组的升级版,树状数组只能做到单点更新后,维持累加和信息的快速更新,线段树可以支持范围更新,但是树状数组可以很方便改成二维或者三维的,对于线段树来说,改成二维的太复杂。

    二维树状数组

    二维树状数组主要解决:在单点变化时候,快速更新从左上角位置(1,1)累加到(i,j)位置的累加和信息这个问题。

    熟悉一维数组后,二维的树状数组比较简单,原先一维数组只需要考虑1...i位置累加和,现在二维除了考虑1...i位置累加和,还要考虑1...j位置的累加和

    二维树状数组的sum方法和update方法如下

     private int sum(int row, int col) {
      int sum = 0;
      for (int i = row + 1; i > 0; i -= i & (-i)) {
       for (int j = col + 1; j > 0; j -= j & (-j)) {
        sum += tree[i][j];
       }
      }
      return sum;
     }
    
     public void update(int row, int col, int val) {
      if (N == 0 || M == 0) {
       return;
      }
      int add = val - nums[row][col];
      nums[row][col] = val;
      for (int i = row + 1; i <= N; i += i & (-i)) {
       for (int j = col + 1; j <= M; j += j & (-j)) {
        tree[i][j] += add;
       }
      }
     }
    

    即在一维的条件下,增加了一个循环。

    现在有了二维树状数组,如果要求整个二维平面中,任意[row1,col1]位置到[row2,col2]位置组成的矩形累加和信息,则可以很方便通过二维数状数组计算出来:

     public int sumRegion(int row1, int col1, int row2, int col2) {
      if (N == 0 || M == 0) {
       return 0;
      }
      return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
     }
    

    线段树和树状数组题目

    segment-tree

    binary-indexed-tree

    更多

    算法和数据结构笔记

    参考资料

    程序员代码面试指南(第2版)

    算法和数据结构体系班-左程云

  • 相关阅读:
    上位机软件开发基于Log4Net实现日志信息存储至数据库
    VS2022安装教程和使用说明来了
    给你的属性加个说明
    这篇文章告诉你自定义特性能做什么?
    新阁上位机开发手把手教你开发圆盘仪表控件
    上位机软件开发C#实现USB插拔检测
    新阁上位机开发详解西门子PLC通信
    新阁上位机开发comboBox控件的数据绑定方法
    高并发场景案例分享(二)count实时查询之坑
    安卓开发入门(一)开发环境搭建
  • 原文地址:https://www.cnblogs.com/greyzeng/p/15343780.html
Copyright © 2020-2023  润新知