经典算法题每日演练——第十题 树状数组
有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的。
一:概序
假如我现在有个需求,就是要频繁的求数组的前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=01102 ;可以发现末尾连续的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 }
最后上总的代码:
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 }
经典算法题每日演练——第十题 树状数组相关推荐
- 经典算法题每日演练——第二十二题 奇偶排序
原文:经典算法题每日演练--第二十二题 奇偶排序 这个专题因为各种原因好久没有继续下去了,MM吧...你懂的,嘿嘿,不过还得继续写下去,好长时间不写,有些东西有点生疏了, 这篇就从简单一点的一个&qu ...
- 经典算法题每日演练——第十九题 双端队列
经典算法题每日演练--第十九题 双端队列 原文:经典算法题每日演练--第十九题 双端队列 话说大学的时候老师说妹子比工作重要~,工作可以再换,妹子这个...所以...这两个月也就一直忙着Fall in ...
- 经典算法题每日演练——第六题 协同推荐SlopeOne 算法
原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法 相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,"商品推荐","猜你喜欢&quo ...
- 经典算法题每日演练——第七题 KMP算法
原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树 ...
- [打题集]小鱼比可爱(树状数组)
题目链接: https://www.luogu.com.cn/problem/P1428?contestId=59884 [题目描述] 人比人,气死人:鱼比鱼,难死鱼.小鱼最近参加了一个"比 ...
- python 树状数组_【算法日积月累】19-高级数据结构:树状数组
树状数组能解决的问题 树状数组,也称作"二叉索引树"(Binary Indexed Tree)或 Fenwick 树. 它可以高效地实现如下两个操作: 1.数组前缀和的查询: 2. ...
- [蓝桥杯][算法提高VIP]分苹果(差分||树状数组)
题目描述 小朋友排成一排,老师给他们分苹果. 小朋友从左到右标号1-N.有M个老师,每次第i个老师会给第Li个到第Ri个,一共Ri-Li+1个小朋友每人发Ci个苹果. 最后老师想知道每个小朋友有多少苹 ...
- 经典算法题每日演练——第二十四题 梳排序
这篇再看看一个经典的排序,梳排序,为什么取名为梳,可能每个梳都有自己的gap吧,大梳子gap大一点,小梳子gap小一点. 上一篇我们看到鸡尾酒排序是在冒泡排序上做了一些优化,将单向的比较变成了双向,同 ...
- 经典算法题每日演练——第三题 猴子吃桃
猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾就多吃了一个.第二天早上又将剩下的桃子吃了一半,还是不过瘾又多 吃了一个.以后每天都吃前一天剩下的一半再加一个.到第10天刚好剩一个.问猴子第一天摘了多 ...
最新文章
- mass Framework在后端的核心模块
- 朋友圈里的一张组合逻辑图
- python菜鸟基础教程-python基础菜鸟教程,Python的基础语法
- python魅力_魅力python------if - else 语句
- 将Apache添加为Linux的服务 实现自启动
- netcore mvc快速开发系统(菜单,角色,权限[精确到按钮])开源
- 程序员除了编代码,还能做哪些职业规划?
- 条码软件如何制作SCC-14条形码
- 这些好玩的 GitHub 项目,你知道几个?
- excel怎么一个格子斜分_表格excel怎样把一格用斜线分为三格
- idea java文件重命名_IDEA项目重命名的操作
- php 获取提成的公式,拿提成的工资怎么算的有公式吗_工资提成计算公式
- firefox 浏览器证书问题
- 互动媒体技术——《代码本色》习作二:向量
- 无标号有根仙人掌计数
- 《惯性导航》第二版秦永元 知识点总结之一 《第一章 绪论》
- 手机内存无法计算机,电脑内存出现故障怎么办 电脑内存常见故障解决方法【详解】...
- Operating System
- 物理风雨100年:索尔维会议的前生今世
- 华为AR161w-s设备