树状数组 Binary Indexed Tree/Fenwick Tree
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)。因此也可以进行递归或者迭代的求解。更进一步的分析,我们可以得知在查询的过程中,其实也生成了一组树,如下图。

public class BinaryIndexedTree1D {
int[] BIT;
BinaryIndexedTree1D(int n) {
this.BIT = new int[n + 1];
}
void update(int index, int delta) {
for (int i = index; i < BIT.length; i += (i & -i)) {
BIT[i] += delta;
}
}
int query(int index) {
int res = 0;
for (int i = index; i > 0; i -= (i & -i)) {
res += BIT[i];
}
return res;
}
}
问题描述:

问题求解:
public class NumArray {
FenwickTree ft;
int[] ls;
public NumArray(int[] nums) {
ft = new FenwickTree(nums.length);
ls = nums;
for (int i = 0; i < ls.length; i++) {
ft.update(i + 1, ls[i]);
}
}
public void update(int i, int val) {
ft.update(i + 1, val - ls[i]);
ls[i] = val;
}
public int sumRange(int i, int j) {
return ft.query(j + 1) - ft.query(i);
}
}
class FenwickTree {
int[] BIT;
FenwickTree(int n) {
this.BIT = new int[n + 1];
}
void update(int index, int delta) {
for (int i = index; i < BIT.length; i += (i & -i)) {
BIT[i] += delta;
}
}
int query(int index) {
int res = 0;
for (int i = index; i > 0; i -= (i & -i)) {
res += BIT[i];
}
return res;
}
}
2019.04.28
class NumArray {
int[] bit;
int[] numsCopy;
int n;
public NumArray(int[] nums) {
bit = new int[nums.length + 1];
numsCopy = new int[nums.length];
n = nums.length + 1;
for (int i = 0; i < nums.length; i++) {
update(i, nums[i]);
numsCopy[i] = nums[i];
}
}
public void update(int i, int val) {
int idx = i + 1;
int delta = val - numsCopy[i];
numsCopy[i] = val;
for (int k = ++i; k < n; k += (k & -k)) {
bit[k] += delta;
}
}
private int query(int i) {
int res = 0;
for (int k = i; k > 0; k -= (k & -k)) {
res += bit[k];
}
return res;
}
public int sumRange(int i, int j) {
return query(j + 1) - query(i);
}
}
二、二维树状数组
二维的树状数组其实就是分别对每行每列进行树状数组化,编写代码上面和一维数组是非常类似的。
public class BinaryIndexedTree2D {
int[][] BIT;
BinaryIndexedTree2D(int N, int M) {
BIT = new int[N + 1][M + 1];
}
void update(int n, int m, int delta) {
for (int i = n; i < BIT.length; i += (i & -i)) {
for (int j = m; j < BIT[0].length; j += (j & -j)) {
BIT[i][j] += delta;
}
}
}
int query(int n, int m) {
int res = 0;
for (int i = n; i > 0; i -= (i & -i)) {
for (int j = m; j > 0; j -= (j & -j)) {
res += BIT[i][j];
}
}
return res;
}
}
问题描述:

问题求解:
裸的2维树状数组问题,有个坑是所有的数字都需要对1e9进行取模,并且最终的sum结果不能为负数。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int mod = (int)1e9 + 7;
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int N = Integer.valueOf(s.split(" ")[0]);
int M = Integer.valueOf(s.split(" ")[1]);
BinaryIndexedTree2D bit = new BinaryIndexedTree2D(N, N);
for (int k = 0; k < M; k++) {
s = sc.nextLine();
String[] ls = s.split(" ");
if (ls[0].equals("Add")) {
int i = Integer.valueOf(ls[1]);
int j = Integer.valueOf(ls[2]);
int val = Integer.valueOf(ls[3]);
bit.update(i + 1, j + 1, val);
}
else if (ls[0].equals("Sum")) {
int x1 = Integer.valueOf(ls[1]);
int y1 = Integer.valueOf(ls[2]);
int x2 = Integer.valueOf(ls[3]);
int y2 = Integer.valueOf(ls[4]);
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);
}
}
}
}
class BinaryIndexedTree2D {
int[][] BIT;
int mod;
BinaryIndexedTree2D(int N, int M) {
BIT = new int[N + 1][M + 1];
mod = (int)1e9 + 7;
}
void update(int n, int m, int delta) {
for (int i = n; i < BIT.length; i += (i & -i)) {
for (int j = m; j < BIT[0].length; j += (j & -j)) {
BIT[i][j] = (BIT[i][j] + delta) % mod;
}
}
}
int query(int n, int m) {
int res = 0;
for (int i = n; i > 0; i -= (i & -i)) {
for (int j = m; j > 0; j -= (j & -j)) {
res = (res + BIT[i][j]) % mod;
}
}
return res;
}
}
三、逆序对问题
逆序对问题是一个经典的问题,使用树状数组可以很好的解决这个问题。
树状数组的核心是单点更新,区间求和。
问题的核心就变成了如何将逆序对问题转化程区间求和的问题,简单的转化方式有,构建一个频率计数桶,将出现的元素放到相应的桶中,并将桶中的数量加一。从后向前逆序遍历数组,边遍历边更新桶中的数量,当遍历到一个元素的时候,计算getSum(num - 1)就可以得到当前元素的逆序对个数。
这个方法的问题就是单纯采用数字的大小来建立桶的话,这个桶的范围可能会很大,其实我们需要的只是相对的大小,所以我们可以将nums mapping 到sort后的idx上,这样整个的空间复杂度就降到了unique num的数量级。
问题描述:

问题求解:
public List<Integer> countSmaller(int[] nums) {
List<Integer> res = new ArrayList<>();
int[] sorted = Arrays.copyOf(nums, nums.length);
Arrays.sort(sorted);
Map<Integer, Integer> ranks = new HashMap<>();
int rank = 0;
for (int i = 0; i < sorted.length; ++i)
if (i == 0 || sorted[i] != sorted[i - 1])
ranks.put(sorted[i], ++rank);
int[] bit = new int[ranks.size() + 1];
for (int i = nums.length - 1; i >= 0; --i) {
int sum = query(bit, ranks.get(nums[i]) - 1);
res.add(sum);
update(bit, ranks.get(nums[i]), 1);
}
Collections.reverse(res);
return res;
}
private int query(int[] bit, int i) {
int res = 0;
for (int k = i; k > 0; k -= (k & -k)) {
res += bit[k];
}
return res;
}
private void update(int[] bit, int i, int delta) {
for (int k = i; k < bit.length; k += (k & -k)) {
bit[k] += delta;
}
}
树状数组 Binary Indexed Tree/Fenwick Tree的更多相关文章
- 树状数组(Binary Indexed Tree) 总结
1.“树状数组”数据结构的一种应用 对含有n个元素的数组(a[1],...,a[k],...,a[n]): (1)求出第i个到第j个元素的和,sum=a[i]+...+a[j]. 进行j-i+1次加法 ...
- 树状数组(Binary Indexed Tree(BIT))
先不说别的,这个博客为我学习树状数组提供了很大帮助,奉上传送门 http://blog.csdn.net/int64ago/article/details/7429868 然后就说几个常用的操作 in ...
- 树状数组(Binary Index Tree)
一维BIT(单点更新,区间求和): Problem - 1166 #include <iostream> #include <algorithm> #include <c ...
- NYOJ 108 士兵杀敌1(树状数组)
首先,要先讲讲树状数组: 树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之 ...
- 树状数组-HDU1541-Stars一维树状数组 POJ1195-Mobile phones-二维树状数组
树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...
- 树状数组入门 hdu1541 Stars
树状数组 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次 ...
- 14-敌兵布阵(HDU1166线段树 & 树状数组)
http://acm.hdu.edu.cn/showproblem.php?pid=1166 敌兵布阵 Time Limit: 2000/1000 MS (Java/Others) Memory ...
- HDU 1166 敌兵布阵 树状数组小结(更新)
树状数组(Binary Indexed Tree(BIT), Fenwick Tree) 是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有 元素之和,但是每次只能修改一 ...
- HDU 1541.Stars-一维树状数组(详解)
树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...
随机推荐
- 安装MySQL与安装Hive
安装mysql 检查是否安装mysql: rpm -qa |grep mysql 删除已经安装的mysql: rpm -e mysql-libs-5.1.71-1.el6.x86_64 报错:因为my ...
- Redis讲解
buffer 缓冲 用于写 cache 缓存 用于读 redis 支持持久化 安装redis yum -y install redis 配置文件/etc/redis.conf 是否在后台运行 ...
- pta习题集5-16 地下迷宫探索
地道战是在抗日战争时期,在华北平原上抗日军民利用地道打击日本侵略者的作战方式.地道网是房连房.街连街.村连村的地下工事,如下图所示. 我们在回顾前辈们艰苦卓绝的战争生活的同时,真心钦佩他们的聪明才智. ...
- Key Set---hud5363(快速幂)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5363 #include <iostream> #include <cstdlib&g ...
- 【Python】读取各种文档(txt、csv、excel、pdf)方法
1.读取txt文件 注意事项: 1..txt文件同下方脚本所在的.py文件需要在同一个文件夹下 # coding=utf-8 txt读取 with open("1233.txt") ...
- Mirror--程序访问镜像数据库的超时机制
程序在访问有镜像的数据库和无镜像的数据库时,采用的链接超时时间算法不一样,因此会导致在在有镜像的数据库上设置了15 S的超时时间,而实际的超时时间仅为3.6 S,从而导致有镜像的数据库更容易超时. 在 ...
- 三个小时学会wordpress模板制作
最近接了一个项目需要用wordpress建站,版面相对简单,ytkah就琢磨着自己来设计wordpress模板,首页栏目页文章页(很多网站无外乎就这些页面),其中栏目页和首页又很像,都是调用文章列表. ...
- centos 系统软件包管理 yum 本地yum配置 扩展源epel rpm 清除yum缓存 yum provides "*/vim" 第十节课
centos 系统软件包管理 yum 本地yum配置 扩展源epel rpm 清除yum缓存 yum provides "*/vim" 第十节课 你不能保证可逆化操 ...
- CentOS 7 集群使用NTP进行时间同步
NTP时钟同步方式说明NTP在linux下有两种时钟同步方式,分别为直接同步和平滑同步: 直接同步 使用ntpdate命令进行同步,直接进行时间变更.如果服务器上存在一个12点运行的任务,当前服务器时 ...
- STL学习笔记--算法
关于STL算法需要注意的是: (1) 所有STL算法被设计用来处理一个或多个迭代器区间.第一个区间通常以起点和终点表示,至于其他区间,多数情况下只需提供起点即可,其终点可自动以第一区间的元素数推导出来 ...