原文:经典算法题每日演练——第十题 树状数组

有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的。

一:概序

假如我现在有个需求,就是要频繁的求数组的前n项和,并且存在着数组中某些数字的频繁修改,那么我们该如何实现这样的需求?当然大家可以往

真实项目上靠一靠。

① 传统方法:根据索引修改为O(1),但是求前n项和为O(n)。

②空间换时间方法:我开一个数组sum[],sum[i]=a[1]+....+a[i],那么有点意思,求n项和为O(1),但是修改却成了O(N),这是因为我的Sum[i]中牵

涉的数据太多了,那么问题来了,我能不能在相应的sum[i]中只保存某些a[i]的值呢?好吧,下面我们看张图。

从图中我们可以看到S[]的分布变成了一颗树,有意思吧,下面我们看看S[i]中到底存放着哪些a[i]的值。

S[1]=a[1];

S[2]=a[1]+a[2];

S[3]=a[3];

S[4]=a[1]+a[2]+a[3]+a[4];

S[5]=a[5];

S[6]=a[5]+a[6];

S[7]=a[7];

S[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

之所以采用这样的分布方式,是因为我们使用的是这样的一个公式:S[i]=a[i-2k+1]+....+a[i]。

其中:2k 中的k表示当前S[i]在树中的层数,它的值就是i的二进制中末尾连续0的个数,2k也就是表示S[i]中包含了哪些a[],

举个例子:  i=610=0110;可以发现末尾连续的0有一个,即k=1,则说明S[6]是在树中的第二层,并且S[6]中有21项,随后我们求出了起始项:

a[6-21+1]=a[5],但是在编码中求出k的值还是有点麻烦的,所以我们采用更灵巧的Lowbit技术,即:2k=i&-i 。

则:S[6]=a[6-21+1]=a[6-(6&-6)+1]=a[5]+a[6]。

二:代码

1:神奇的Lowbit函数

 1 #region 当前的sum数列的起始下标2         /// <summary>3         /// 当前的sum数列的起始下标4         /// </summary>5         /// <param name="i"></param>6         /// <returns></returns>7         public static int Lowbit(int i)8         {9             return i & -i;
10         }
11         #endregion

2:求前n项和

比如上图中,如何求Sum(6),很显然Sum(6)=S4+S6,那么如何寻找S4呢?即找到6以前的所有最大子树,很显然这个求和的复杂度为logN。

 1         #region 求前n项和2         /// <summary>3         /// 求前n项和4         /// </summary>5         /// <param name="x"></param>6         /// <returns></returns>7         public static int Sum(int x)8         {9             int ans = 0;
10
11             var i = x;
12
13             while (i > 0)
14             {
15                 ans += sumArray[i - 1];
16
17                 //当前项的最大子树
18                 i -= Lowbit(i);
19             }
20
21             return ans;
22         }
23         #endregion

3:修改

如上图中,如果我修改了a[5]的值,那么包含a[5]的S[5],S[6],S[8]的区间值都需要同步修改,我们看到只要沿着S[5]一直回溯到根即可,

同样它的时间复杂度也为logN。

 1         public static void Modify(int x, int newValue)2         {3             //拿出原数组的值4             var oldValue = arr[x];5 6             for (int i = x; i < arr.Length; i += Lowbit(i + 1))7             {8                 //减去老值,换一个新值9                 sumArray[i] = sumArray[i] - oldValue + newValue;
10             }
11         }

最后上总的代码:

View Code

  1 using System;2 using System.Collections.Generic;3 using System.Linq;4 using System.Text;5 using System.Diagnostics;6 using System.Threading;7 using System.IO;8 9 namespace ConsoleApplication210 {11     public class Program12     {13         static int[] sumArray = new int[8];14 15         static int[] arr = new int[8];16 17         public static void Main()18         {19             Init();20 21             Console.WriteLine("A数组的值:{0}", string.Join(",", arr));22             Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray));23 24             Console.WriteLine("修改A[1]的值为3");25             Modify(1, 3);26 27             Console.WriteLine("A数组的值:{0}", string.Join(",", arr));28             Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray));29 30             Console.Read();31         }32 33         #region 初始化两个数组34         /// <summary>35         /// 初始化两个数组36         /// </summary>37         public static void Init()38         {39             for (int i = 1; i <= 8; i++)40             {41                 arr[i - 1] = i;42 43                 //设置其实坐标:i=1开始44                 int start = (i - Lowbit(i));45 46                 var sum = 0;47 48                 while (start < i)49                 {50                     sum += arr[start];51 52                     start++;53                 }54 55                 sumArray[i - 1] = sum;56             }57         }58         #endregion59 60         public static void Modify(int x, int newValue)61         {62             //拿出原数组的值63             var oldValue = arr[x];64 65             arr[x] = newValue;66 67             for (int i = x; i < arr.Length; i += Lowbit(i + 1))68             {69                 //减去老值,换一个新值70                 sumArray[i] = sumArray[i] - oldValue + newValue;71             }72         }73 74         #region 求前n项和75         /// <summary>76         /// 求前n项和77         /// </summary>78         /// <param name="x"></param>79         /// <returns></returns>80         public static int Sum(int x)81         {82             int ans = 0;83 84             var i = x;85 86             while (i > 0)87             {88                 ans += sumArray[i - 1];89 90                 //当前项的最大子树91                 i -= Lowbit(i);92             }93 94             return ans;95         }96         #endregion97 98         #region 当前的sum数列的起始下标99         /// <summary>
100         /// 当前的sum数列的起始下标
101         /// </summary>
102         /// <param name="i"></param>
103         /// <returns></returns>
104         public static int Lowbit(int i)
105         {
106             return i & -i;
107         }
108         #endregion
109     }
110 }

经典算法题每日演练——第十题 树状数组相关推荐

  1. 经典算法题每日演练——第二十二题 奇偶排序

    原文:经典算法题每日演练--第二十二题 奇偶排序 这个专题因为各种原因好久没有继续下去了,MM吧...你懂的,嘿嘿,不过还得继续写下去,好长时间不写,有些东西有点生疏了, 这篇就从简单一点的一个&qu ...

  2. 经典算法题每日演练——第十九题 双端队列

    经典算法题每日演练--第十九题 双端队列 原文:经典算法题每日演练--第十九题 双端队列 话说大学的时候老师说妹子比工作重要~,工作可以再换,妹子这个...所以...这两个月也就一直忙着Fall in ...

  3. 经典算法题每日演练——第六题 协同推荐SlopeOne 算法

    原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法 相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,"商品推荐","猜你喜欢&quo ...

  4. 经典算法题每日演练——第七题 KMP算法

    原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树 ...

  5. [打题集]小鱼比可爱(树状数组)

    题目链接: https://www.luogu.com.cn/problem/P1428?contestId=59884 [题目描述] 人比人,气死人:鱼比鱼,难死鱼.小鱼最近参加了一个"比 ...

  6. python 树状数组_【算法日积月累】19-高级数据结构:树状数组

    树状数组能解决的问题 树状数组,也称作"二叉索引树"(Binary Indexed Tree)或 Fenwick 树. 它可以高效地实现如下两个操作: 1.数组前缀和的查询: 2. ...

  7. [蓝桥杯][算法提高VIP]分苹果(差分||树状数组)

    题目描述 小朋友排成一排,老师给他们分苹果. 小朋友从左到右标号1-N.有M个老师,每次第i个老师会给第Li个到第Ri个,一共Ri-Li+1个小朋友每人发Ci个苹果. 最后老师想知道每个小朋友有多少苹 ...

  8. 经典算法题每日演练——第二十四题 梳排序

    这篇再看看一个经典的排序,梳排序,为什么取名为梳,可能每个梳都有自己的gap吧,大梳子gap大一点,小梳子gap小一点. 上一篇我们看到鸡尾酒排序是在冒泡排序上做了一些优化,将单向的比较变成了双向,同 ...

  9. 经典算法题每日演练——第三题 猴子吃桃

    猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾就多吃了一个.第二天早上又将剩下的桃子吃了一半,还是不过瘾又多 吃了一个.以后每天都吃前一天剩下的一半再加一个.到第10天刚好剩一个.问猴子第一天摘了多 ...

最新文章

  1. mass Framework在后端的核心模块
  2. 朋友圈里的一张组合逻辑图
  3. python菜鸟基础教程-python基础菜鸟教程,Python的基础语法
  4. python魅力_魅力python------if - else 语句
  5. 将Apache添加为Linux的服务 实现自启动
  6. netcore mvc快速开发系统(菜单,角色,权限[精确到按钮])开源
  7. 程序员除了编代码,还能做哪些职业规划?
  8. 条码软件如何制作SCC-14条形码
  9. 这些好玩的 GitHub 项目,你知道几个?
  10. excel怎么一个格子斜分_表格excel怎样把一格用斜线分为三格
  11. idea java文件重命名_IDEA项目重命名的操作
  12. php 获取提成的公式,拿提成的工资怎么算的有公式吗_工资提成计算公式
  13. firefox 浏览器证书问题
  14. 互动媒体技术——《代码本色》习作二:向量
  15. 无标号有根仙人掌计数
  16. 《惯性导航》第二版秦永元 知识点总结之一 《第一章 绪论》
  17. 手机内存无法计算机,电脑内存出现故障怎么办 电脑内存常见故障解决方法【详解】...
  18. Operating System
  19. 物理风雨100年:索尔维会议的前生今世
  20. 华为AR161w-s设备

热门文章

  1. vimrc-20201028重新配置
  2. centos查看当前目录文件夹的大小与tar解压到指定目录
  3. Transformer architecture的解释
  4. java点到原点距离_java-从经纬度坐标到欧氏距离(EuclideanDistance)完整代码
  5. qt icon如何显示gif_收集Qt支持的emoji表情-第五弹
  6. 【企业管理】《人与绩效》读书笔记--怎样做员工
  7. Mysql安装 定期弹出黑框作用及关闭
  8. Linux网络状态工具ss命令操作详解
  9. ElementUI中el-upload中怎样限制上传文件的格式
  10. Nginx实现负载均衡时常用的分配服务器策略