经典算法题——第十题 树状数组
来源:互联网 发布:焦作淘宝客公司 编辑:程序博客网 时间:2024/05/07 11:45
有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的。
一:概序
假如我现在有个需求,就是要频繁的求数组的前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函数
#region 当前的sum数列的起始下标 /// <summary> /// 当前的sum数列的起始下标 /// </summary> /// <param name="i"></param> /// <returns></returns> public static int Lowbit(int i) { return i & -i; } #endregion
2:求前n项和
比如上图中,如何求Sum(6),很显然Sum(6)=S4+S6,那么如何寻找S4呢?即找到6以前的所有最大子树,很显然这个求和的复杂度为logN。
#region 求前n项和 /// <summary> /// 求前n项和 /// </summary> /// <param name="x"></param> /// <returns></returns> public static int Sum(int x) { int ans = 0; var i = x; while (i > 0) { ans += sumArray[i - 1]; //当前项的最大子树 i -= Lowbit(i); } return ans; } #endregion
3:修改
如上图中,如果我修改了a[5]的值,那么包含a[5]的S[5],S[6],S[8]的区间值都需要同步修改,我们看到只要沿着S[5]一直回溯到根即可,
同样它的时间复杂度也为logN。
public static void Modify(int x, int newValue) { //拿出原数组的值 var oldValue = arr[x]; for (int i = x; i < arr.Length; i += Lowbit(i + 1)) { //减去老值,换一个新值 sumArray[i] = sumArray[i] - oldValue + newValue; } }
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Diagnostics;using System.Threading;using System.IO;namespace ConsoleApplication2{ public class Program { static int[] sumArray = new int[8]; static int[] arr = new int[8]; public static void Main() { Init(); Console.WriteLine("A数组的值:{0}", string.Join(",", arr)); Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray)); Console.WriteLine("修改A[1]的值为3"); Modify(1, 3); Console.WriteLine("A数组的值:{0}", string.Join(",", arr)); Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray)); Console.Read(); } #region 初始化两个数组 /// <summary> /// 初始化两个数组 /// </summary> public static void Init() { for (int i = 1; i <= 8; i++) { arr[i - 1] = i; //设置其实坐标:i=1开始 int start = (i - Lowbit(i)); var sum = 0; while (start < i) { sum += arr[start]; start++; } sumArray[i - 1] = sum; } } #endregion public static void Modify(int x, int newValue) { //拿出原数组的值 var oldValue = arr[x]; arr[x] = newValue; for (int i = x; i < arr.Length; i += Lowbit(i + 1)) { //减去老值,换一个新值 sumArray[i] = sumArray[i] - oldValue + newValue; } } #region 求前n项和 /// <summary> /// 求前n项和 /// </summary> /// <param name="x"></param> /// <returns></returns> public static int Sum(int x) { int ans = 0; var i = x; while (i > 0) { ans += sumArray[i - 1]; //当前项的最大子树 i -= Lowbit(i); } return ans; } #endregion #region 当前的sum数列的起始下标 /// <summary> /// 当前的sum数列的起始下标 /// </summary> /// <param name="i"></param> /// <returns></returns> public static int Lowbit(int i) { return i & -i; } #endregion }}
- 经典算法题——第十题 树状数组
- 经典算法题每日演练——第十题 树状数组
- 经典算法题每日演练——第十题 树状数组
- 算法学习—树状数组
- DP+树状数组——FZU 2236 第十四个目标
- 算法学习——树状数组
- POJ 2155 Matrix (二维树状数组+经典题)
- POJ3321 线段树,树状数组 建树经典题
- 树分治经典题+树状数组(hdu4918)
- POJ3321 树状数组,多叉树转线性结构,经典基础题
- pku2352——Stars(经典树状数组的应用)
- 树状数组模板题—hdu1166+poj2352
- 树状数组经典
- 树状数组经典讲解
- fzoj 2236 第十四个目标 (树状数组&LIS&dp)好题
- fzu oj 2236 第十四个目标 树状数组好题 dp
- FZU 2236 第十四个目标【Dp+离散化+树状数组】套路题
- 树状数组题
- Majority Element II leetcode 229
- MySQL 水平拆分
- 移动应用开发必备工具盘点
- sendtoTarget()与sendtoMessage()
- java中的同步容器和并发容器
- 经典算法题——第十题 树状数组
- uc笔记03---环境变量,内存管理,进程映像/堆栈区别,虚拟内存,malloc 特点
- web技术之负载均衡与缓存技术
- Windows下配置Apache虚拟机
- 求二级制数中的0的个数
- cocos2dx中调用Andriod中的返回键和菜单监听
- ini文件三之操作
- 利用StoryBoard修改placeholder颜色(UITextField的placeholder)
- 如何才能系统的学习 iOS 开发,理解一些规则和深层次的机制原理?