前言
二叉索引数。
二进制中负数
一个8位的二进制
- 无符号:表正数,范围是:0-255
- 有符号:
- 表正数,范围是:0-127
- 表负数,范围是:-1(10000001) - -128(1000000)(没有-0,-0就是0)
但在一般计算中,我们不使用上述负数的原码,而使用它的补码(除符号位取反+1),这是为何?
以 10 + (-8)为例,如果直接原码计算,结果出错:
如何解决呢?(引入模的思想):
考虑一个时钟,指向了10点,需要调回2点,可以表示成(1). 10 - 8 = 2,即往回调8,如果考虑模,我们也可以往前调 12(表范围) - 8 = 4,即(2). 10 + 4 = 14 --> 14 % 12 = 2
把这种思想引入到二进制中,10 - 8 可以写成 10 + 2^8(表范围)- 8,而2^8-8 = (2^8 - 1) - 8 + 1,其中,(2^8 - 1) - 8计算如下:
然后再+1。
也就是说,遇到x-y时,先把y取反,再+1,得到z,然后就等价于计算x+z了。(z是y的补码)
把加法拓展到任何运算,只要我们用补码来表示二进制,就可以忽略符号位了,下面是与运算的例子。
x&-x
x&-x保留二进制x最低位的1,其余位置都为0.
假设A是01串,A’是A取反,B是全0串,C是全1串。下面考虑x的奇偶的不同表示:
- 偶数:表示成(A)1(B)
- 奇数:表示成(A)1
则-x表示为(补码):
- 偶数:(A’)0© + 1 = (A’)1(B)
- 奇数:(A’)0 + 1 = (A’)1
所以x&-x为:
- 偶数:(B)1(B)
- 奇数:(B)1
即不管奇偶,该位运算都是保留最低位的1,其余位置统统置零。
x&(x-1)
x&(x-1)消除二进制x最低位的1,其余位置不变.
x-1表示为:
- 偶数:(A)0©
- 奇数:(A)0
所以x&(x-1)为:
- 偶数:(A)0(B)
- 奇数:(A)0
即不管奇偶,该位运算都是消去最低位的1,其余位置保持不变。
二叉索引树
以一个长度为9的数组A为例,一般要求前缀和i(表示到下标i为止(包含i)前面所有元素的和),只需要遍历这个数组,创建一个sum数组B。
//为方便讲解,sum[i]实际=A[0]+A[1]+...A[i-1]
int[] A = new int[9]; // 不管它里面具体是什么元素
int[] sum = new int[A.length + 1];
sum[1] = A[0];
for(int i = 2; i <= A.length; i ++){
sum[i] = sum[i-1] + A[i-1];
}
如果要方位[0,i]中间的前缀和,则
int ans = sum[i] + A[i];
如果需要访问[i,j]中间的和,则
int ans = sum[j] - sum[i] + A[j]
建立sum数组的时间为O(n),查找的时间复杂度为O(1),而如果A中的某一个元素被修改了,则需要重新构建这个sum,所以修改的时间复杂度为O(n)。
下面所讲的二叉索引树的建立时间为O(nlogn),查找时间为O(logn),修改的时间为O(logn)。
和sum数组相同,二叉索引树也是存在一个数组中,记作数组C。不同的是,C[i]记录的是[i-lowbit(i)+1-1,i-1]间的和。
这里的lowbit指的是对于一个二进制i,其最低位的1的构成的二进制数,也就是上述x&-x。举个例子:8的二进制为1000,则lowbit(8) = 8;4的二进制为100,则lowbit(4) = 4;3的二进制为11,则lowbit(3) = 1。
代码:
首先定义一个lowbit函数:
public int lowbit(int x){
return x & - x
}
然后根据输入的数组A,构建数组C:
public int[] build(int[] A){
int[] C = new int[A.length + 1];
for(int i = 1; i < C.length; i++){
for(int j = i - lowbit(i) + 1, j <=i; j ++){
C[i] += A[j - 1]; // 注意这里的j-1,也就代表了C[i]所表示的区间为[i-lowbit(i)+1-1,i-1]
}
}
return C;
}
接着定义前缀和的计算函数:
public int get_num(int index){
index = index + 1;
int ans = 0;
while(index > 0){
ans += C[index];
index -= lowbit(index);
}
}
i…j区间的计算函数:
public int i_j_sum(int i, int j){
int sum_i = get_sum(i);
int sum_j = get_sum(j);
return sum_j - sum_i + A[i];
}
A中第k个元素改变之后,对C的更新函数:
public int[] update(int index, int new_num){
int old = A[index];
index = index + 1;
while(index < C.length){
C[index] += new_num - old;
index += lowbit(index);
}
}