
org.zodiac.algorithms.structures.FenwickTree Maven / Gradle / Ivy
package org.zodiac.algorithms.structures;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* A Fenwick tree or binary indexed tree is a data structure providing efficient methods
* for calculation and manipulation of the prefix sums of a table of values. Fenwick trees
* primarily solve the problem of balancing prefix sum calculation efficiency with element
* modification efficiency.
*
* This class is meant to be somewhat generic, all you'd have to do is extend
* the Data abstract class to store your custom data. I've included a range sum
* implementations.
*
* @see Fenwick Tree (Wikipedia)
*
*
*/
@SuppressWarnings("unchecked")
public class FenwickTree {
private Object[] array;
public FenwickTree(List data) {
/*Find the largest index.*/
int n = 0;
for (Data d : data)
if (d.index > n)
n = d.index;
n = next(n+1);
array = new Object[n];
/*Add the data.*/
for (D d : data)
update(d.index, d);
}
/**
* Stabbing query.
*
* @param index index for query
* @return data at index.
*/
public D query(int index) {
return query(index, index);
}
/**
* Range query.
*
* @param start start of range (inclusive)
* @param end end of range (inclusive)
* @return data for range.
*/
public D query(int start, int end) {
final D e = lookup(end);
final D s = lookup(start-1);
final D c = (D) e.copy();
if (s != null)
c.separate(s);
return c;
}
private D lookup(int index) {
/*Tree index is 1 based.*/
index++;
index = Math.min(array.length - 1, index);
if (index <= 0)
return null;
D res = null;
while (index > 0) {
if (res == null) {
final D data = (D) array[index];
if (data != null)
res = (D) data.copy();
} else{
res.combined((D) array[index]);
}
index = prev(index);
}
return res;
}
private void update(int index, D value) {
/*Tree index is 1 based.*/
index++;
while (index < array.length) {
D data = (D) array[index];
if (data == null) {
data = (D) value.copy();
data.index = index;
array[index] = data;
} else {
data.combined(value);
}
index = next(index);
}
}
private static final int prev(int x) {
return x & (x - 1);
}
private static final int next(int x) {
return 2 * x - prev(x);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(FenwickTreePrinter.getString(this));
return builder.toString();
}
protected static class FenwickTreePrinter {
public static String getString(FenwickTree tree) {
if (tree.array.length == 0)
return "Tree has no nodes.";
final D first = (D) tree.array[1];
if (first == null)
return "Tree has no nodes.";
final StringBuilder builder = new StringBuilder();
builder.append("└── dummy \n");
builder.append(getString(tree, 0, tree.array.length, "", true));
return builder.toString();
}
private static String getString(FenwickTree tree, int start, int end, String prefix, boolean isTail) {
if (end > tree.array.length || (end - start == 0))
return "";
final StringBuilder builder = new StringBuilder();
final D value = (D) tree.array[start];
if (value != null)
builder.append(prefix + (isTail ? "└── " : "├── ") + value + "\n");
int next = start + 1;
final List children = new ArrayList(2);
while (next < end) {
children.add(next);
next = next(next);
}
for (int i = 0; i < children.size() - 1; i++)
builder.append(getString(tree, children.get(i), children.get(i+1), prefix + (isTail ? " " : "│ "), false));
if (children.size() >= 1)
builder.append(getString(tree, children.get(children.size()-1), end, prefix + (isTail ? " " : "│ "), true));
return builder.toString();
}
}
public abstract static class Data implements Comparable {
protected int index = Integer.MIN_VALUE;
/**
* Constructor for data at index.
*
* @param index of data.
*/
protected Data(int index) {
this.index = index;
}
/**
* Clear the indices.
*/
public void clear() {
index = Integer.MIN_VALUE;
}
/**
* Combined this data with the Data parameter.
*
* @param data to combined with.
* @return Data which represents the combination.
*/
public abstract Data combined(Data data);
/**
* Separate this data with the Data parameter.
*
* @param data to separate with.
* @return Data which represents the combination.
*/
public abstract Data separate(Data data);
/**
* Deep copy of data.
*
* @return deep copy.
*/
public abstract Data copy();
/**
* Query inside this data object.
*
* @param startOfRange of range to query for.
* @param endOfRange of range to query for.
* @return Data queried for or NULL if it doesn't match the query.
*/
public abstract Data query(long startOfRange, long endOfRange);
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("[").append(index).append("]");
return builder.toString();
}
@Override
public int compareTo(Data d) {
if (this.index < d.index)
return -1;
if (d.index < this.index)
return 1;
return 0;
}
/**
* Data structure representing sum of the range.
*/
public static final class RangeSumData extends Data {
public N sum = null;
public RangeSumData(int index, N number) {
super(index);
this.sum = number;
}
@Override
public void clear() {
super.clear();
sum = null;
}
@Override
public Data combined(Data data) {
RangeSumData q = null;
if (data instanceof RangeSumData) {
q = (RangeSumData) data;
this.combined(q);
}
return this;
}
@Override
public Data separate(Data data) {
RangeSumData q = null;
if (data instanceof RangeSumData) {
q = (RangeSumData) data;
this.separate(q);
}
return this;
}
/**
* Combined range sum data.
*
* @param data resulted from combination.
*/
private void combined(RangeSumData data) {
if (this.sum == null && data.sum == null)
return;
else if (this.sum != null && data.sum == null)
return;
else if (this.sum == null && data.sum != null)
this.sum = data.sum;
else {
/* TODO: This is ugly and how to handle number overflow? */
if (this.sum instanceof BigDecimal || data.sum instanceof BigDecimal) {
BigDecimal result = ((BigDecimal)this.sum).add((BigDecimal)data.sum);
this.sum = (N)result;
} else if (this.sum instanceof BigInteger || data.sum instanceof BigInteger) {
BigInteger result = ((BigInteger)this.sum).add((BigInteger)data.sum);
this.sum = (N)result;
} else if (this.sum instanceof Long || data.sum instanceof Long) {
Long result = (this.sum.longValue() + data.sum.longValue());
this.sum = (N)result;
} else if (this.sum instanceof Double || data.sum instanceof Double) {
Double result = (this.sum.doubleValue() + data.sum.doubleValue());
this.sum = (N)result;
} else if (this.sum instanceof Float || data.sum instanceof Float) {
Float result = (this.sum.floatValue() + data.sum.floatValue());
this.sum = (N)result;
} else {
/*Integer .*/
Integer result = (this.sum.intValue() + data.sum.intValue());
this.sum = (N)result;
}
}
}
/**
* Separate range sum data.
*
* @param data resulted from combination.
*/
private void separate(RangeSumData data) {
if (this.sum == null && data.sum == null)
return;
else if (this.sum != null && data.sum == null)
return;
else if (this.sum == null && data.sum != null)
this.sum = data.sum;
else {
/* TODO: This is ugly and how to handle number overflow? */
if (this.sum instanceof BigDecimal || data.sum instanceof BigDecimal) {
BigDecimal result = ((BigDecimal)this.sum).subtract((BigDecimal)data.sum);
this.sum = (N)result;
} else if (this.sum instanceof BigInteger || data.sum instanceof BigInteger) {
BigInteger result = ((BigInteger)this.sum).subtract((BigInteger)data.sum);
this.sum = (N)result;
} else if (this.sum instanceof Long || data.sum instanceof Long) {
Long result = (this.sum.longValue() - data.sum.longValue());
this.sum = (N)result;
} else if (this.sum instanceof Double || data.sum instanceof Double) {
Double result = (this.sum.doubleValue() - data.sum.doubleValue());
this.sum = (N)result;
} else if (this.sum instanceof Float || data.sum instanceof Float) {
Float result = (this.sum.floatValue() - data.sum.floatValue());
this.sum = (N)result;
} else {
/*Integer .*/
Integer result = (this.sum.intValue() - data.sum.intValue());
this.sum = (N)result;
}
}
}
@Override
public Data copy() {
return new RangeSumData(index, sum);
}
@Override
public Data query(long startOfQuery, long endOfQuery) {
if (endOfQuery < this.index || startOfQuery > this.index)
return null;
return copy();
}
@Override
public int hashCode() {
return 31 * (int)(this.index + this.sum.hashCode());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RangeSumData))
return false;
RangeSumData data = (RangeSumData) obj;
if (this.index == data.index && this.sum.equals(data.sum))
return true;
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(super.toString()).append(" ");
builder.append("sum=").append(sum);
return builder.toString();
}
}
}
}