2018-03-25 17:29:29

树状数组是一个比较小众的数据结构,主要应用领域是快速的对mutable array进行区间求和。

对于一般的一维情况下的区间和问题,一般有以下两种解法:

1)DP

预处理:建立长度为n的数组,每个结点i保存前i个数的和,时间复杂度O(n)。

查询:直接从数组中取两个段相减,时间复杂度O(1)。

更新:这种方法比较适用与immutable数组,对于mutable数组的更新需要重新建立表,所以时间复杂度为O(n)。

2)树状数组 BIT

预处理:建立树状数组,对数组中的每个数进行update操作,时间复杂度O(nlogn)。

查询:从当前结点向根结点遍历,求总和,时间复杂度O(logn)。

更新:从当前结点向根结点遍历,更新这条路径上的所有结点的值,时间复杂度O(logn)。

一、一维树状数组

在DP算法中我们在每个结点存放的是当前结点与前面所有结点的和,这就直接导致了在做更新的时候我们也只能进行大规模的修正,树状数组的提出就是为了在更新的过程中也保证有较低的时间复杂度,要实现这个目的,显然,每个结点我们不能再存储全部的前i个数,只能进行部分存储,而这部分存储的个数,就是整个树状数组的核心和关键。

在树状数组中,每个结点存的和的个数是lowbit(i)个,这里的lowbit就是i的二进制表示的第一个1所表示的数,举例:4(0100),lowbit(4) = 100,也就是在4号结点位置要存储4个结点的和。而这3个数(自己除外)都是4号结点的子孙,这三个数显然是0011,0010,0001。

那么,parent结点和child结点到底有什么关系呢?在树状数组中,我们规定parent = child + lowbit(child)。在更新操作中,我们可以递归的向上遍历,将所有该结点的父亲结点都进行更新,时间复杂度为O(logn)。

那么,又如何进行查询呢?对于sum(1, j) = nums[1] + nums[2] + ... + nums[j]。由于在树状数组中j号结点中存了lowbit(j)个数的和,所以原式可以写成sum(1, j) = sum(1, j - lowbit(j)) + BIT(j)。因此也可以进行递归或者迭代的求解。更进一步的分析,我们可以得知在查询的过程中,其实也生成了一组树,如下图。

  1. public class BinaryIndexedTree1D {
  2. int[] BIT;
  3.  
  4. BinaryIndexedTree1D(int n) {
  5. this.BIT = new int[n + 1];
  6. }
  7.  
  8. void update(int index, int delta) {
  9. for (int i = index; i < BIT.length; i += (i & -i)) {
  10. BIT[i] += delta;
  11. }
  12. }
  13.  
  14. int query(int index) {
  15. int res = 0;
  16. for (int i = index; i > 0; i -= (i & -i)) {
  17. res += BIT[i];
  18. }
  19. return res;
  20. }
  21. }

问题描述:

问题求解:

  1. public class NumArray {
  2. FenwickTree ft;
  3. int[] ls;
  4.  
  5. public NumArray(int[] nums) {
  6. ft = new FenwickTree(nums.length);
  7. ls = nums;
  8. for (int i = 0; i < ls.length; i++) {
  9. ft.update(i + 1, ls[i]);
  10. }
  11. }
  12.  
  13. public void update(int i, int val) {
  14. ft.update(i + 1, val - ls[i]);
  15. ls[i] = val;
  16. }
  17.  
  18. public int sumRange(int i, int j) {
  19. return ft.query(j + 1) - ft.query(i);
  20. }
  21. }
  22.  
  23. class FenwickTree {
  24. int[] BIT;
  25.  
  26. FenwickTree(int n) {
  27. this.BIT = new int[n + 1];
  28. }
  29.  
  30. void update(int index, int delta) {
  31. for (int i = index; i < BIT.length; i += (i & -i)) {
  32. BIT[i] += delta;
  33. }
  34. }
  35.  
  36. int query(int index) {
  37. int res = 0;
  38. for (int i = index; i > 0; i -= (i & -i)) {
  39. res += BIT[i];
  40. }
  41. return res;
  42. }
  43. }

2019.04.28

  1. class NumArray {
  2. int[] bit;
  3. int[] numsCopy;
  4. int n;
  5.  
  6. public NumArray(int[] nums) {
  7. bit = new int[nums.length + 1];
  8. numsCopy = new int[nums.length];
  9. n = nums.length + 1;
  10. for (int i = 0; i < nums.length; i++) {
  11. update(i, nums[i]);
  12. numsCopy[i] = nums[i];
  13. }
  14. }
  15.  
  16. public void update(int i, int val) {
  17. int idx = i + 1;
  18. int delta = val - numsCopy[i];
  19. numsCopy[i] = val;
  20. for (int k = ++i; k < n; k += (k & -k)) {
  21. bit[k] += delta;
  22. }
  23. }
  24.  
  25. private int query(int i) {
  26. int res = 0;
  27. for (int k = i; k > 0; k -= (k & -k)) {
  28. res += bit[k];
  29. }
  30. return res;
  31. }
  32.  
  33. public int sumRange(int i, int j) {
  34. return query(j + 1) - query(i);
  35. }
  36. }

二、二维树状数组

二维的树状数组其实就是分别对每行每列进行树状数组化,编写代码上面和一维数组是非常类似的。

  1. public class BinaryIndexedTree2D {
  2. int[][] BIT;
  3.  
  4. BinaryIndexedTree2D(int N, int M) {
  5. BIT = new int[N + 1][M + 1];
  6. }
  7.  
  8. void update(int n, int m, int delta) {
  9. for (int i = n; i < BIT.length; i += (i & -i)) {
  10. for (int j = m; j < BIT[0].length; j += (j & -j)) {
  11. BIT[i][j] += delta;
  12. }
  13. }
  14. }
  15.  
  16. int query(int n, int m) {
  17. int res = 0;
  18. for (int i = n; i > 0; i -= (i & -i)) {
  19. for (int j = m; j > 0; j -= (j & -j)) {
  20. res += BIT[i][j];
  21. }
  22. }
  23. return res;
  24. }
  25. }

问题描述:

问题求解:

裸的2维树状数组问题,有个坑是所有的数字都需要对1e9进行取模,并且最终的sum结果不能为负数。

  1. import java.util.Scanner;
  2.  
  3. public class Main {
  4. public static void main(String[] args) {
  5. int mod = (int)1e9 + 7;
  6. Scanner sc = new Scanner(System.in);
  7. String s = sc.nextLine();
  8. int N = Integer.valueOf(s.split(" ")[0]);
  9. int M = Integer.valueOf(s.split(" ")[1]);
  10. BinaryIndexedTree2D bit = new BinaryIndexedTree2D(N, N);
  11. for (int k = 0; k < M; k++) {
  12. s = sc.nextLine();
  13. String[] ls = s.split(" ");
  14. if (ls[0].equals("Add")) {
  15. int i = Integer.valueOf(ls[1]);
  16. int j = Integer.valueOf(ls[2]);
  17. int val = Integer.valueOf(ls[3]);
  18. bit.update(i + 1, j + 1, val);
  19. }
  20. else if (ls[0].equals("Sum")) {
  21. int x1 = Integer.valueOf(ls[1]);
  22. int y1 = Integer.valueOf(ls[2]);
  23. int x2 = Integer.valueOf(ls[3]);
  24. int y2 = Integer.valueOf(ls[4]);
  25. System.out.println((bit.query(x2 + 1, y2 + 1) - bit.query(x2 + 1, y1) - bit.query(x1, y2 + 1) + bit.query(x1, y1) + mod) % mod);
  26. }
  27. }
  28. }
  29. }
  30.  
  31. class BinaryIndexedTree2D {
  32. int[][] BIT;
  33. int mod;
  34.  
  35. BinaryIndexedTree2D(int N, int M) {
  36. BIT = new int[N + 1][M + 1];
  37. mod = (int)1e9 + 7;
  38. }
  39.  
  40. void update(int n, int m, int delta) {
  41. for (int i = n; i < BIT.length; i += (i & -i)) {
  42. for (int j = m; j < BIT[0].length; j += (j & -j)) {
  43. BIT[i][j] = (BIT[i][j] + delta) % mod;
  44. }
  45. }
  46. }
  47.  
  48. int query(int n, int m) {
  49. int res = 0;
  50. for (int i = n; i > 0; i -= (i & -i)) {
  51. for (int j = m; j > 0; j -= (j & -j)) {
  52. res = (res + BIT[i][j]) % mod;
  53. }
  54. }
  55. return res;
  56. }
  57. }

三、逆序对问题

逆序对问题是一个经典的问题,使用树状数组可以很好的解决这个问题。

树状数组的核心是单点更新,区间求和。

问题的核心就变成了如何将逆序对问题转化程区间求和的问题,简单的转化方式有,构建一个频率计数桶,将出现的元素放到相应的桶中,并将桶中的数量加一。从后向前逆序遍历数组,边遍历边更新桶中的数量,当遍历到一个元素的时候,计算getSum(num - 1)就可以得到当前元素的逆序对个数。

这个方法的问题就是单纯采用数字的大小来建立桶的话,这个桶的范围可能会很大,其实我们需要的只是相对的大小,所以我们可以将nums mapping 到sort后的idx上,这样整个的空间复杂度就降到了unique num的数量级。

问题描述:

问题求解:

  1. public List<Integer> countSmaller(int[] nums) {
  2. List<Integer> res = new ArrayList<>();
  3. int[] sorted = Arrays.copyOf(nums, nums.length);
  4. Arrays.sort(sorted);
  5. Map<Integer, Integer> ranks = new HashMap<>();
  6. int rank = 0;
  7. for (int i = 0; i < sorted.length; ++i)
  8. if (i == 0 || sorted[i] != sorted[i - 1])
  9. ranks.put(sorted[i], ++rank);
  10. int[] bit = new int[ranks.size() + 1];
  11. for (int i = nums.length - 1; i >= 0; --i) {
  12. int sum = query(bit, ranks.get(nums[i]) - 1);
  13. res.add(sum);
  14. update(bit, ranks.get(nums[i]), 1);
  15. }
  16. Collections.reverse(res);
  17. return res;
  18. }
  19.  
  20. private int query(int[] bit, int i) {
  21. int res = 0;
  22. for (int k = i; k > 0; k -= (k & -k)) {
  23. res += bit[k];
  24. }
  25. return res;
  26. }
  27.  
  28. private void update(int[] bit, int i, int delta) {
  29. for (int k = i; k < bit.length; k += (k & -k)) {
  30. bit[k] += delta;
  31. }
  32. }

  

树状数组 Binary Indexed Tree/Fenwick Tree的更多相关文章

  1. 树状数组(Binary Indexed Tree) 总结

    1.“树状数组”数据结构的一种应用 对含有n个元素的数组(a[1],...,a[k],...,a[n]): (1)求出第i个到第j个元素的和,sum=a[i]+...+a[j]. 进行j-i+1次加法 ...

  2. 树状数组(Binary Indexed Tree(BIT))

    先不说别的,这个博客为我学习树状数组提供了很大帮助,奉上传送门 http://blog.csdn.net/int64ago/article/details/7429868 然后就说几个常用的操作 in ...

  3. 树状数组(Binary Index Tree)

    一维BIT(单点更新,区间求和): Problem - 1166 #include <iostream> #include <algorithm> #include <c ...

  4. NYOJ 108 士兵杀敌1(树状数组)

    首先,要先讲讲树状数组: 树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之 ...

  5. 树状数组-HDU1541-Stars一维树状数组 POJ1195-Mobile phones-二维树状数组

    树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...

  6. 树状数组入门 hdu1541 Stars

    树状数组 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次 ...

  7. 14-敌兵布阵(HDU1166线段树 & 树状数组)

    http://acm.hdu.edu.cn/showproblem.php?pid=1166 敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory ...

  8. HDU 1166 敌兵布阵 树状数组小结(更新)

    树状数组(Binary Indexed Tree(BIT), Fenwick Tree) 是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有 元素之和,但是每次只能修改一 ...

  9. HDU 1541.Stars-一维树状数组(详解)

    树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...

随机推荐

  1. Linux(Centos)下搭建SVN服务器

    鉴于在搭建时,参考网上很多资料,网上资料在有用的同时,也坑了很多人,本文的目的,也就是想让后继之人在搭建svn服务器时不再犯错,不再被网上漫天的坑爹作品所坑害,故此总结! /******开始***** ...

  2. 徐州网络赛J-Maze Designer【最小生成树】【LCA】

    After the long vacation, the maze designer master has to do his job. A tour company gives him a map ...

  3. python3.7.2 ssl版本过低导致pip无法使用的问题

    环境:系统是centos6.6,python:python3.7.2 问题:安装好python3.pip后,在通过pip install xx 安装模块时,发现无法安装的问题,提示版本太低,系统默认的 ...

  4. Python开发【十八章】:Web框架

    Web框架本质 1.众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端 #!/usr/bin/env python # -*- codin ...

  5. Disruptor的伪共享解决方案

    1.术语 术语 英文单词 描述 内存屏障 Memory Barriers 是一组处理器指令,用于实现对内存操作的顺序限制. In the Java Memory Model a volatile fi ...

  6. [css]浮动-清除浮动的3种方法

    清除浮动的方法: 内墙法 注: 这是个奇淫技巧,没什么原理可言,记住即可 这个技巧又使得父box重新可以被子box撑开高度了. 隔墙法-适用于2个box之间上下排列 由于2个box高度依旧是0, 彼此 ...

  7. 搭建markdown图床-腾讯云COS

    背景介绍 书写markdown笔记时,如何处理图片,实在是有些棘手的问题.每一张图都保存在当前文件夹? 每张图都自己重命名?每次上传到cnblogs博客都需要一张一张拖动?markdown已经非常成功 ...

  8. Android开发新手问题

    因为最近在用空闲时间学习Android开发,期间确实遇到了一些问题.而且因为我之前在公司里一直都是在使用Eclipse进行开发,所以最初我学习Android时也就选择了Google的包含android ...

  9. c/c++值传递和引用传递

    今天看数据结构的时候,因为是c语言版的,刚开始学的时候就对指针搞的焦头烂额,今天,发现参数传递的时候,&符号也莫名其妙,搜了一篇好文,转载下来. 一. 函数参数传递机制的基本理论 函数参数传递 ...

  10. Codeforces Round #524 (Div. 2) Solution

    A. Petya and Origami Water. #include <bits/stdc++.h> using namespace std; #define ll long long ...