我正在写一个二进制索引树。作为文档,它需要nlogn时间来预处理。但我无法理解为什么。
在我的情况下,我正在从数组构建树,这应该花费2n时间,因为第一次遍历数组一次使其成为二进制树然后更新总和我再次以POST顺序遍历树。所以总共2n,而不是nlogn。
任何人都可以解释为什么需要nlogn时间来预处理二进制索引树。
public class BITree {
private class BTN {
int data;
int index;
BTN left,right;
public BTN(int data) {
this.data = data;
}
}
BTN head = null;
public BTN toBT(int[] arr,int start,int end){
if(start <= end){
int mid = start + (end - start)/2;
BTN btn = new BTN(arr[mid]);
btn.index = mid+1;
btn.left = toBT(arr,start,mid-1);
btn.right = toBT(arr,mid+1,end);
return btn;
}
return null;
}
public int sumAtIndex(BTN btn,int index){
int sum = 0;
if(index < btn.index)
sum += sumAtIndex(btn.left,index);
else if(index > btn.index) {
sum += btn.data + sumAtIndex(btn.right, index);
}
if(btn.index == index)
return btn.data + sum;
return sum;
}
public int replaceSum(BTN btn){
if(btn == null){
return 0;
}
int l = replaceSum(btn.left);
int r = replaceSum(btn.right);
int sum = btn.data + l + r;
btn.data += l;
return sum;
}
void inOrder(BTN btn){
if(btn != null) {
inOrder(btn.left);
System.out.print((btn.index+":"+btn.data)+",");
inOrder(btn.right);
}
}
public static void main(String[] args) {
int[] arr = {5,1,6,4,2,3,3};
BITree s2 = new BITree();
BTN btn = s2.toBT(arr,0,arr.length-1);
s2.replaceSum(btn);
s2.inOrder(btn);
System.out.println();
System.out.println(s2.sumAtIndex(btn,3));
}
}
答案 0 :(得分:0)
此问题与以下内容重复:Is it possible to build a Fenwick tree in O(n)?
@Thilo,感谢您指出预处理BIT的优化方法。这可以在O(n)时间内完成。
https://en.wikipedia.org/wiki/Talk:Fenwick_tree
https://stackoverflow.com/a/31070683/3080158
@SanketMakani,感谢您分享链接,它很好地解释了BIT。
这是工作代码,具有O(n)预处理时间。
package com.rabin;
import java.util.StringJoiner;
/**
*
*/
public class BITree {
/**
* O(logn)
* @param arr
* @param index
* @param val
*/
void update(int arr[],int index, int val)
{
index++;
for(; index <= arr.length-1; index += index&-index)
arr[index] += val;
}
/**
* O(logn)
* @param arr
* @param noOfElements
* @return
*/
int query(int[] arr,int noOfElements)
{
int sum = 0;
for(; noOfElements > 0; noOfElements -= noOfElements&-noOfElements)
sum += arr[noOfElements-1];
return sum;
}
/**
* O(n)
* @param arr
*/
void toBIT(int[] arr){
int n = arr.length;
for(int i=1;i<=n;i++){
int j = i+ (i & -i);
if(j <= n)
arr[j-1] += arr[i-1];
}
}
static String arrayToString(int[] arr){
StringJoiner sj = new StringJoiner(",","[","]");
for(int i = 0; i< arr.length ;i++){
sj.add(String.valueOf(arr[i]));
}
return sj.toString();
}
public static void main(String[] args) {
int[] arr = {5,1,6,4,2,3,3};
BITree bit = new BITree();
System.out.println("Original Array:" +arrayToString(arr));
bit.toBIT(arr);
System.out.println("BIT Array:" +arrayToString(arr));
System.out.println("Sum of first 5 nos : "+ bit.query(arr,5));
bit.update(arr,0,8);
System.out.println("Sum of first 5 nos after update : "+ bit.query(arr,5));
}
}
答案 1 :(得分:0)
@RBanerjee编写的代码很好,最好用一个附加索引来实现BIT,这有助于代码理解。另外,它还表示另外一件事-BIT索引中的最低有效1位表示特定索引存储了多少个元素。例如index = 2(010)可以表示BIT中的索引2拥有2个元素的值,类似地,4(100)表示4、6(110)存储2个值(即索引5和6),依此类推。
此外,在您的更新方法中,您本身并没有更新该值。您正在添加给定的值。我认为这并不意味着更新的含义。这是一个非常主观的讨论,但我认为它是更新而不是增量。因此,如果索引5最初保留值2,而当我想将其更新为-1时,则意味着索引5更新后的值是-1而不是1。
作为额外的步骤,最好提供一种查询数组中范围的方法。例如索引2和5(含)之间的值是什么。
<!-- language: java -->
package DataStructureImplementation;
import java.util.StringJoiner;
public class BinaryIndexedTree {
private final int[] bit;
private final int[] nums;
private final int n;
public BinaryIndexedTree(int[] nums) {
n = nums.length;
bit = new int[n + 1];
this.nums = nums;
System.arraycopy(nums, 0, bit, 1, nums.length);
build();
}
/**
* Builds a binary indexed tree in O(n) time.
*/
private void build() {
int j;
for (int i = 1; i <= n; ++i) {
j = i + (i & -i);
if (j <= n) bit[j] += bit[i];
}
}
/**
* Updates an indexed item in the original array to the given value.
* Also updates the values in the 'BIT' in O(logn) time.
* @param index - index of the item to update
* @param value - value to update to
*/
public void update(int index, int value) {
int diff = value - nums[index];
nums[index] = value;
index++;
while (index <= n) {
bit[index] += diff;
index += (index & -index);
}
}
/**
* Queries the sum of the first 'K' indices in the original array in O(logn) time.
* @param k - the number of items to aggregate.
* @return - the sum of first 'K' numbers in the original array.
* @throws Exception - if 'K' is out of bounds.
*/
public int query(int k) throws Exception {
if (k < 0 || k > n) throw new Exception("Invalid query range : " + k);
int sum = 0;
while (k > 0) {
sum += bit[k];
k -= (k & -k);
}
return sum;
}
/**
* Queries the sum of numbers from the original array between index1 and index2 (inclusive) in O(logn) time.
* @param index1 - left index.
* @param index2 - right index.
* @return - the sum of numbers between the given ranges.
* @throws Exception - if range is out of bounds.
*/
public int queryRange(int index1, int index2) throws Exception {
return query(index2 + 1) - query(index1);
}
/**
* Helper method to print the array contents.
* @param nums - the array to print.
* @return - the contents of the array as string.
*/
static String arrayToString(int[] nums){
StringJoiner stringJoiner = new StringJoiner(",","[","]");
for (int n : nums) {
stringJoiner.add(String.valueOf(n));
}
return stringJoiner.toString();
}
public static void main(String[] args) throws Exception {
int[] nums = {5,8,5,4,2,3};
BinaryIndexedTree binaryIndexedTree = new BinaryIndexedTree(nums);
System.out.println("Original Array : " + arrayToString(nums));
System.out.println("BIT Array : " + arrayToString(binaryIndexedTree.bit));
System.out.println("Sum of first 5 nos : " + binaryIndexedTree.query(5));
binaryIndexedTree.update(4,-1);
System.out.println("Original Array after update : " + arrayToString(nums));
System.out.println("BIT Array after update : " + arrayToString(binaryIndexedTree.bit));
System.out.println("Sum of first 5 nos after update : " + binaryIndexedTree.query(5));
System.out.println("Sum of numbers in range 2-5 : " + binaryIndexedTree.queryRange(2, 5));
}
}