Difference between revisions of "User:Chase-san/Kd-Tree"

From Robowiki
Jump to navigation Jump to search
(Reduced the source some.)
m (→‎KDTreeF: (no major change, just some minor tweaks, i++ to ++i, removing the enhanced for loop))
 
(12 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
[[Category:Code Snippets|Kd-Tree]]
 
[[Category:Code Snippets|Kd-Tree]]
This is my version of the [[Kd-Tree]], I admit it has gotten a bit large, and it could be cut down a great deal, I will add a K nearest neighbors implimentation soon enough. however it does have a singular copy, which will find the single closest neighbor (not as useful).
+
Everyone and their brother has one of these now, me and Simonton started it, but I was to inexperienced to get anything written, I took an hour or two to rewrite it today, because I am no longer completely terrible at these things. So here is mine if you care to see it.
  
[[User:Simonton|Simonton]] and I worked on kd-trees at about the same time, however I was less experienced and it turns out the whole time I had nothing but a very elusive off by 1 problem. I recently rebuilt my kd-tree into this. Which unhooks the points from the branches to speed things up, it also uses buckets, which are great fun.
+
This and all my other code in which I display on the robowiki falls under the [http://en.wikipedia.org/wiki/Zlib_License ZLIB License].
  
Also unlike most others mine is fairly modular and includes a range search (good for those old fashioned [[Pattern Matching|pattern matchers]]!!).
+
Oh yeah, am I the only one that has a Range function?
  
Please forgive the incomplete documentation. I have compacted the source down to just 3 files instead of 6 (I just internalized the Node,Branch, and Bucket classes into KDTreeB). I am adding that multiple Nearest Neightbor function later on, since it seems to be how most people use it.
+
=== KDTreeF ===
 +
<syntaxhighlight>
 +
package org.csdgn.util;
  
=== KDTreeB ===
+
import java.util.ArrayList;
<pre>
+
import java.util.Arrays;
package org.csdgn.util;
+
import java.util.List;
 +
 
 +
/**
 +
* This is a KD Bucket Tree, for fast sorting and searching of K dimensional
 +
* data.
 +
*
 +
* @author Chase
 +
*
 +
*/
 +
public class KDTree<T> {
 +
protected static final int defaultBucketSize = 48;
  
public class KDTreeB {
+
private final int dimensions;
public final static int DEFAULT_BUCKET_SIZE = 100;
+
private final int bucketSize;
protected int bucketSize;
+
private NodeKD root;
protected int dimensions;
 
protected NodeKD root;
 
protected PointKD keys[];
 
protected int size;
 
  
 
/**
 
/**
* Initializes a new KDTreeB with a number of dimensions.
+
* Constructor with value for dimensions.
 
*  
 
*  
* @param dim
+
* @param dimensions
*            - Dimensions
+
*            - Number of dimensions
 
*/
 
*/
public KDTreeB(int dim) {
+
public KDTree(int dimensions) {
this(dim, DEFAULT_BUCKET_SIZE);
+
this.dimensions = dimensions;
 +
this.bucketSize = defaultBucketSize;
 +
this.root = new NodeKD();
 
}
 
}
  
 
/**
 
/**
* Initializes a new KDTreeB with a number of dimensions and bucket size
+
* Constructor with value for dimensions and bucket size.
 
*  
 
*  
* @param dim
+
* @param dimensions
*            - Dimensions
+
*            - Number of dimensions
* @param buckets
+
* @param bucket
*            - BucketKD Size
+
*            - Size of the buckets.
 
*/
 
*/
public KDTreeB(int dim, int buckets) {
+
public KDTree(int dimensions, int bucket) {
if(dim < 1)
+
this.dimensions = dimensions;
System.err.println("Dimensions < 1: Undefined Behavior may occur.");
+
this.bucketSize = bucket;
if(buckets < 2)
+
this.root = new NodeKD();
System.err.println("Bucket Size < 2: Undefined Behavior may occur.");
 
 
bucketSize = buckets;
 
dimensions = dim;
 
keys = new PointKD[buckets];
 
size = 0;
 
 
}
 
}
  
 
/**
 
/**
* Adds a new PointKD to the Tree, uses a recursive algorithm.
+
* Add a key and its associated value to the tree.
 
*  
 
*  
* @param k
+
* @param key
*            - Point to add
+
*            - Key to add
 +
* @param val
 +
*            - object to add
 
*/
 
*/
public void add(PointKD k) {
+
public void add(double[] key, T val) {
if (null == root) {
+
root.addPoint(key, val);
root = new BucketKD(this);
 
}
 
if (size >= keys.length) {
 
// this is basically what a vector does, I am
 
// just removing the overhead of using a vector
 
PointKD tmp[] = new PointKD[keys.length * 2];
 
System.arraycopy(keys, 0, tmp, 0, size);
 
keys = tmp;
 
}
 
keys[size++] = k;
 
root.add(k);
 
 
}
 
}
  
 
/**
 
/**
* Finds the approximate nearest neighbor to PointKD k. This uses a
+
* Returns all PointKD within a certain range defined by an upper and lower
* recursive algorithm.
+
* PointKD.
 
*  
 
*  
* @param k
+
* @param low
* @return
+
*            - lower bounds of area
 +
* @param high
 +
*            - upper bounds of area
 +
* @return - All PointKD between low and high.
 
*/
 
*/
public PointKD getApproxNN(PointKD k) {
+
@SuppressWarnings("unchecked")
if (null == root)
+
public List<T> getRange(double[] low, double[] high) {
return null;
+
Object[] objs = root.range(high, low);
return root.approx(k);
+
ArrayList<T> range = new ArrayList<T>(objs.length);
 +
for(int i=0; i<objs.length; ++i) {
 +
range.add((T)objs[i]);
 +
}
 +
return range;
 
}
 
}
  
 
/**
 
/**
* Finds the nearest neighbor to PointKD k. Uses an exhastive search
+
* Gets the N nearest neighbors to the given key.
* method.
+
*  
* @param k -  
+
* @param key
* @return
+
*            - Key
 +
* @param num
 +
*            - Number of results
 +
* @return Array of Item Objects, distances within the items are the square
 +
*        of the actual distance between them and the key
 
*/
 
*/
public PointKD getNN(PointKD k) {
+
public ResultHeap<T> getNearestNeighbors(double[] key, int num) {
if (null == root)
+
ResultHeap<T> heap = new ResultHeap<T>(num);
return null;
+
root.nearest(heap, key);
return root.nearest(k);
+
return heap;
 
}
 
}
  
public PointKD[] getRange(RectKD r) {
 
if (null == root)
 
return new PointKD[0];
 
return root.range(r);
 
}
 
  
public PointKD[] getRange(PointKD low, PointKD high) {
+
// Internal tree node
return this.getRange(new RectKD(low, high));
+
private class NodeKD {
}
+
private NodeKD left, right;
 +
private double[] maxBounds, minBounds;
 +
private Object[] bucketValues;
 +
private double[][] bucketKeys;
 +
private boolean isLeaf;
 +
private int current, sliceDimension;
 +
private double slice;
  
+
private NodeKD() {
public abstract class NodeKD {
+
bucketValues = new Object[bucketSize];
protected KDTreeB ref;
+
bucketKeys = new double[bucketSize][];
protected BranchKD parent;
 
protected int depth;
 
protected RectKD rect;
 
  
protected abstract void add(PointKD k);
+
left = right = null;
protected abstract PointKD approx(PointKD k);
+
maxBounds = minBounds = null;
protected abstract PointKD nearest(PointKD k);
+
protected abstract PointKD[] range(RectKD r);
+
isLeaf = true;
}
+
+
current = 0;
public class BucketKD extends NodeKD {
+
}
protected PointKD bucket[];
 
protected int current;
 
  
protected BucketKD(KDTreeB reference) {
+
// what it says on the tin
ref = reference;
+
private void addPoint(double[] key, Object val) {
ref.root = this;
+
if(isLeaf) {
bucket = new PointKD[ref.bucketSize];
+
addLeafPoint(key,val);
parent = null;
+
} else {
rect = new RectKD();
+
extendBounds(key);
depth = 0;
+
if (key[sliceDimension] > slice) {
 +
right.addPoint(key, val);
 +
} else {
 +
left.addPoint(key, val);
 +
}
 +
}
 
}
 
}
 
 
protected BucketKD(BranchKD p) {
+
private void addLeafPoint(double[] key, Object val) {
ref = p.ref;
+
extendBounds(key);
bucket = new PointKD[ref.bucketSize];
+
if (current + 1 > bucketSize) {
parent = p;
+
splitLeaf();
rect = new RectKD();
+
addPoint(key, val);
depth = p.depth + 1;
+
return;
 +
}
 +
bucketKeys[current] = key;
 +
bucketValues[current] = val;
 +
++current;
 
}
 
}
 
+
protected PointKD approx(PointKD k) {
+
/**
double nearestDist = Double.POSITIVE_INFINITY;
+
* Find the nearest neighbor recursively.
int nearest = 0;
+
*/
for (int i = 0; i < current; i++) {
+
@SuppressWarnings("unchecked")
double distance = k.distanceSq(bucket[i]);
+
private void nearest(ResultHeap<T> heap, double[] data) {
if (distance < nearestDist) {
+
if(current == 0)
nearestDist = distance;
+
return;
nearest = i;
+
if(isLeaf) {
 +
//IS LEAF
 +
for (int i = 0; i < current; ++i) {
 +
double dist = pointDistSq(bucketKeys[i], data);
 +
heap.offer(dist, (T) bucketValues[i]);
 +
}
 +
} else {
 +
//IS BRANCH
 +
if (data[sliceDimension] > slice) {
 +
right.nearest(heap, data);
 +
if(left.current == 0)
 +
return;
 +
if (!heap.isFull() || regionDistSq(data,left.minBounds,left.maxBounds) < heap.getMaxKey()) {
 +
left.nearest(heap, data);
 +
}
 +
} else {
 +
left.nearest(heap, data);
 +
if (right.current == 0)
 +
return;
 +
if (!heap.isFull() || regionDistSq(data,right.minBounds,right.maxBounds) < heap.getMaxKey()) {
 +
right.nearest(heap, data);
 +
}
 
}
 
}
 
}
 
}
return bucket[nearest];
 
 
}
 
}
  
protected PointKD nearest(PointKD k) {
+
// gets all items from within a range
return approx(k);
+
private Object[] range(double[] upper, double[] lower) {
}
+
if (bucketValues == null) {
 
+
// Branch
protected void add(PointKD k) {
+
Object[] tmp = new Object[0];
if (current >= ref.bucketSize) {
+
if (intersects(upper, lower, left.maxBounds, left.minBounds)) {
BranchKD b = null;
+
Object[] tmpl = left.range(upper, lower);
if (null == parent)
+
if (0 == tmp.length) tmp = tmpl;
b = new BranchKD(ref);
 
else
 
b = parent.extend(this);
 
 
 
int dim = b.depth % ref.dimensions;
 
double total = 0;
 
for (int i = 0; i < current; i++) {
 
total += bucket[i].array[dim];
 
 
}
 
}
b.slice = total / current;
+
if (intersects(upper, lower, right.maxBounds, right.minBounds)) {
for (int i = 0; i < current; i++) {
+
Object[] tmpr = right.range(upper, lower);
b.add(bucket[i]);
+
if (0 == tmp.length)
 +
tmp = tmpr;
 +
else if (0 < tmpr.length) {
 +
Object[] tmp2 = new Object[tmp.length + tmpr.length];
 +
System.arraycopy(tmp, 0, tmp2, 0, tmp.length);
 +
System.arraycopy(tmpr, 0, tmp2, tmp.length, tmpr.length);
 +
tmp = tmp2;
 +
}
 
}
 
}
b.add(k);
+
return tmp;
bucket = null;
 
parent = null;
 
current = 0;
 
return;
 
 
}
 
}
rect.add(k);
+
// Leaf
bucket[current++] = k;
+
Object[] tmp = new Object[current];
}
 
 
 
protected PointKD[] range(RectKD r) {
 
PointKD[] tmp = new PointKD[current];
 
 
int n = 0;
 
int n = 0;
for (int i = 0; i < current; i++) {
+
for (int i = 0; i < current; ++i) {
if (r.contains(bucket[i])) {
+
if (contains(upper, lower, bucketKeys[i])) {
tmp[n++] = bucket[i];
+
tmp[n++] = bucketValues[i];
 
}
 
}
 
}
 
}
PointKD[] tmp2 = new PointKD[n];
+
Object[] tmp2 = new Object[n];
 
System.arraycopy(tmp, 0, tmp2, 0, n);
 
System.arraycopy(tmp, 0, tmp2, 0, n);
 
return tmp2;
 
return tmp2;
 
}
 
}
}
 
 
public class BranchKD extends NodeKD {
 
protected NodeKD left, right;
 
protected double slice;
 
  
protected BranchKD(KDTreeB reference) {
+
// These are helper functions from here down
slice = 0;
+
// check if this hyper rectangle contains a give hyper-point
ref = reference;
+
public boolean contains(double[] upper, double[] lower, double[] point) {
rect = new RectKD();
+
if (current == 0) return false;
depth = 0;
+
for (int i = 0; i < point.length; ++i) {
ref.root = this;
+
if (point[i] > upper[i] || point[i] < lower[i]) return false;
left = new BucketKD(this);
+
}
right = new BucketKD(this);
+
return true;
}
 
protected BranchKD(BranchKD b) {
 
ref = b.ref;
 
parent = b;
 
slice = 0;
 
rect = new RectKD();
 
depth = b.depth + 1;
 
left = new BucketKD(this);
 
right = new BucketKD(this);
 
 
}
 
}
  
protected void add(PointKD k) {
+
// checks if two hyper-rectangles intersect
rect.add(k);
+
public boolean intersects(double[] up0, double[] low0, double[] up1, double[] low1) {
int dim = depth % ref.dimensions;
+
for (int i = 0; i < up0.length; ++i) {
if (k.array[dim] > slice) {
+
if (up1[i] < low0[i] || low1[i] > up0[i]) return false;
right.add(k);
 
} else {
 
left.add(k);
 
 
}
 
}
 +
return true;
 
}
 
}
  
protected PointKD approx(PointKD k) {
+
private void splitLeaf() {
int dim = depth % ref.dimensions;
+
double bestRange = 0;
if (k.array[dim] > slice)
+
for(int i=0;i<dimensions;++i) {
return right.approx(k);
+
double range = maxBounds[i] - minBounds[i];
return left.approx(k);
+
if(range > bestRange) {
}
+
sliceDimension = i;
 
+
bestRange = range;
protected PointKD nearest(PointKD k) {
 
int dim = depth % ref.dimensions;
 
PointKD near = null;
 
if (k.array[dim] > slice) {
 
near = right.nearest(k);
 
double t = near.distanceSq(k);
 
if (k.distanceSq(left.rect.getNearest(k)) < t) {
 
PointKD tmp = left.nearest(k);
 
if (tmp.distanceSq(k) < t) {
 
near = tmp;
 
}
 
 
}
 
}
} else {
+
}
near = left.nearest(k);
+
double t = near.distanceSq(k);
+
left = new NodeKD();
if (k.distanceSq(right.rect.getNearest(k)) < t) {
+
right = new NodeKD();
PointKD tmp = right.nearest(k);
+
if (tmp.distanceSq(k) < t) {
+
slice = (maxBounds[sliceDimension] + minBounds[sliceDimension]) * 0.5;
near = tmp;
+
}
+
for (int i = 0; i < current; ++i) {
 +
if (bucketKeys[i][sliceDimension] > slice) {
 +
right.addLeafPoint(bucketKeys[i], bucketValues[i]);
 +
} else {
 +
left.addLeafPoint(bucketKeys[i], bucketValues[i]);
 
}
 
}
 
}
 
}
 
+
bucketKeys = null;
return near;
+
bucketValues = null;
 +
isLeaf = false;
 
}
 
}
  
protected PointKD[] range(RectKD r) {
+
// expands this hyper rectangle
PointKD[] tmp = new PointKD[0];
+
private void extendBounds(double[] key) {
if (r.intersects(left.rect)) {
+
if (maxBounds == null) {
PointKD[] tmpl = left.range(r);
+
maxBounds = Arrays.copyOf(key, dimensions);
if(0 == tmp.length)
+
minBounds = Arrays.copyOf(key, dimensions);
tmp = tmpl;
+
return;
 
}
 
}
if (r.intersects(right.rect)) {
+
for (int i = 0; i < key.length; ++i) {
PointKD[] tmpr = right.range(r);
+
if (maxBounds[i] < key[i]) maxBounds[i] = key[i];
if (0 == tmp.length)
+
if (minBounds[i] > key[i]) minBounds[i] = key[i];
tmp = tmpr;
 
else if (0 < tmpr.length) {
 
PointKD[] tmp2 = new PointKD[tmp.length + tmpr.length];
 
System.arraycopy(tmp, 0, tmp2, 0, tmp.length);
 
System.arraycopy(tmpr, 0, tmp2, tmp.length, tmpr.length);
 
tmp = tmp2;
 
}
 
 
}
 
}
return tmp;
 
 
}
 
}
 +
}
 +
 +
/* I may have borrowed these from an early version of Red's tree. I however forget. */
 +
private static final double pointDistSq(double[] p1, double[] p2) {
 +
        double d = 0;
 +
        double q = 0;
 +
        for (int i = 0; i < p1.length; ++i) {
 +
            d += (q=(p1[i] - p2[i]))*q;
 +
        }
 +
        return d;
 +
    }
  
protected BranchKD extend(BucketKD b) {
+
    private static final double regionDistSq(double[] point, double[] min, double[] max) {
if (b == left) {
+
        double d = 0;
left = null;
+
        double q = 0;
left = new BranchKD(this);
+
        for (int i = 0; i < point.length; ++i) {
return (BranchKD) left;
+
            if (point[i] > max[i]) {
} else if (b == right) {
+
            d += (q = (point[i] - max[i]))*q;
right = null;
+
            } else if (point[i] < min[i]) {
right = new BranchKD(this);
+
                d += (q = (point[i] - min[i]))*q;
return (BranchKD) right;
+
            }
}
+
        }
return null;
+
        return d;
}
+
    }
}
 
 
}
 
}
</pre>
+
</syntaxhighlight>
  
=== PointKD ===
+
=== ResultHeap ===
<pre>
+
<syntaxhighlight>
 
package org.csdgn.util;
 
package org.csdgn.util;
  
 
/**
 
/**
* A K-Dimensional Point, used in conjunction with the KD-Tree multidimensional
+
  * @author Chase
* data structure.
 
*
 
  * @author Robert Maupin
 
 
  *  
 
  *  
 +
* @param <T>
 
  */
 
  */
public class PointKD {
+
public class ResultHeap<T> {
protected double[] array;
+
private Object[] data;
 +
private double[] keys;
 +
private int capacity;
 +
private int size;
  
/**
+
protected ResultHeap(int capacity) {
* Constructor defining the number of dimensions to use in this Point;
+
this.data = new Object[capacity];
*
+
this.keys = new double[capacity];
* @param dimensions
+
this.capacity = capacity;
*            - The number of dimensions in this point
+
this.size = 0;
*/
 
public PointKD(int dimensions) {
 
array = new double[dimensions];
 
 
}
 
}
  
/**
+
protected void offer(double key, T value) {
* Creates a PointKD from an array.
+
int i = size;
*
+
for (; i > 0 && keys[i - 1] > key; --i);
* @param pos
+
if (i >= capacity) return;
*            - An array of Numbers
+
if (size < capacity) ++size;
*/
+
int j = i + 1;
public PointKD(double[] pos) {
+
System.arraycopy(keys, i, keys, j, size - j);
array = pos.clone();
+
keys[i] = key;
// array = new double[pos.length];
+
System.arraycopy(data, i, data, j, size - j);
// System.arraycopy(pos, 0, array, 0, array.length);
+
data[i] = value;
 
}
 
}
  
/**
+
public double getMaxKey() {
* Creates a copy of the selected point.
+
return keys[size - 1];
*
 
* @param p
 
*            - A point to copy
 
*/
 
public PointKD(PointKD p) {
 
// array = new double[p.array.length];
 
setPoint(p);
 
 
}
 
}
 
+
/**
+
@SuppressWarnings("unchecked")
* Sets the coordinates of this point to be the same as the selected point.
+
public T removeMax() {
*
+
if(isEmpty()) return null;
* @param p
+
return (T)data[--size];
*            - A point to copy
 
*/
 
public void setPoint(PointKD p) {
 
this.array = p.array.clone();
 
// System.arraycopy(p.array, 0, array, 0, array.length);
 
}
 
 
 
/**
 
* Returns the number of dimensions this point has.
 
*
 
* @return the dimensions
 
*/
 
public int getDimensions() {
 
return array.length;
 
}
 
 
 
/**
 
* Returns the coordinate at dimension <b>i</b>.
 
*
 
* @param i
 
*            - The dimension to retrieve.
 
* @return The coordinate at i.
 
*/
 
public double getCoordinate(int i) {
 
return array[i];
 
 
}
 
}
  
/**
+
public boolean isEmpty() {
* Sets the coordinate to <b>k</b> at dimension <b>i</b>.
+
return size == 0;
*
 
* @param i
 
*            - the dimension to set
 
* @param k
 
*            - the value to set the dimension to
 
*/
 
public void setCoordinate(int i, double k) {
 
array[i] = k;
 
 
}
 
}
  
/**
+
public boolean isFull() {
* Compares this to a selected point and returns the euclidean distance
+
return size == capacity;
* between them.
 
*
 
* @param p
 
*            - The Point to get the distance to.
 
* @return The distance between this and <b>p</b>.
 
*/
 
public double distance(PointKD p) {
 
return distance(this, p);
 
 
}
 
}
  
/**
+
public int size() {
* Compares this to a selected point and returns the squared euclidean
+
return size;
* distance between them.
 
*
 
* @param p
 
*            - The Point to get the distance to.
 
* @return The distance between this and <b>p</b>.
 
*/
 
public double distanceSq(PointKD p) {
 
return distanceSq(this, p);
 
 
}
 
}
  
/**
+
public int capacity() {
* Prints the class name and the point coordinates.
+
return capacity;
*/
 
public String toString() {
 
String output = getClass().getName() + "[";
 
for (int i = 0; i < array.length; i++) {
 
if (0 != i)
 
output += ",";
 
output += array[i];
 
}
 
return output + "]";
 
}
 
 
 
/**
 
* @return a distinct copy of this object
 
*/
 
public Object clone() {
 
return new PointKD(this);
 
}
 
 
 
/**
 
* Compares two Points and returns the euclidean distance between them.
 
*
 
* @param a
 
*            - The first set of numbers
 
* @param b
 
*            - The second set of numbers
 
* @return The distance between <b>a</b> and <b>b</b>.
 
*/
 
public static final double distance(PointKD a, PointKD b) {
 
return distance(a.array, b.array);
 
}
 
 
 
/**
 
* Compares two Points and returns the squared euclidean distance between
 
* them.
 
*
 
* @param a
 
*            - The first set of numbers
 
* @param b
 
*            - The second set of numbers
 
* @return The distance between <b>a</b> and <b>b</b>.
 
*/
 
public static final double distanceSq(PointKD a, PointKD b) {
 
return distanceSq(a.array, b.array);
 
}
 
 
 
/**
 
* Compares arrays of double and returns the euclidean distance between
 
* them.
 
*
 
* @param a
 
*            - The first set of numbers
 
* @param b
 
*            - The second set of numbers
 
* @return The distance between <b>a</b> and <b>b</b>.
 
*/
 
public static final double distance(double[] a, double[] b) {
 
return Math.sqrt(distanceSq(a, b));
 
}
 
 
 
/**
 
* Compares arrays of double and returns the squared euclidean distance
 
* between them.
 
*
 
* @param a
 
*            - The first set of numbers
 
* @param b
 
*            - The second set of numbers
 
* @return The distance squared between <b>a</b> and <b>b</b>.
 
*/
 
public static final double distanceSq(double[] a, double[] b) {
 
if (a.length != b.length || a.length < 1)
 
return -1;
 
double total = 0;
 
for (int i = 0; i < a.length; i++)
 
total += (b[i] - a[i]) * (b[i] - a[i]);
 
return total;
 
}
 
}
 
</pre>
 
 
 
=== RectKD ===
 
<pre>
 
package org.csdgn.util;
 
 
 
/**
 
* A K-Dimensional Hyper Rectangle, used in conjunction with the KD-Tree
 
* multidimensional data structure.
 
*
 
* @author Robert Maupin
 
*
 
*/
 
public class RectKD {
 
PointKD upper;
 
PointKD lower;
 
 
 
/**
 
* Creates an empty RectKD
 
*/
 
public RectKD() {
 
upper = null;
 
lower = null;
 
}
 
 
 
public RectKD(PointKD s) {
 
this(s, s);
 
}
 
 
 
public RectKD(PointKD l, PointKD u) {
 
this();
 
if (l.array.length == u.array.length) {
 
upper = new PointKD(u);
 
lower = new PointKD(l);
 
}
 
}
 
 
 
public void add(PointKD p) {
 
if (upper == null) {
 
upper = new PointKD(p);
 
lower = new PointKD(p);
 
return;
 
}
 
 
 
for (int i = 0; i < upper.array.length; i++) {
 
if (p.array[i] > upper.array[i])
 
upper.array[i] = p.array[i];
 
if (p.array[i] < lower.array[i])
 
lower.array[i] = p.array[i];
 
}
 
}
 
 
 
public boolean contains(PointKD p) {
 
if (upper == null)
 
return false;
 
if (upper.array.length != p.array.length)
 
return false;
 
boolean inside = true;
 
for (int i = 0; i < upper.array.length; i++) {
 
if (p.array[i] > upper.array[i])
 
inside = false;
 
if (p.array[i] < lower.array[i])
 
inside = false;
 
}
 
return inside;
 
}
 
 
 
public boolean intersects(RectKD r) {
 
boolean check = false;
 
 
 
if (null == r)
 
return false;
 
if (null == r.lower)
 
return false;
 
if (null == r.upper)
 
return false;
 
if (r.upper.array.length != upper.array.length)
 
return false;
 
 
 
int len = upper.array.length;
 
for (int i = 0; i < len; i++) {
 
if (upper.array[i] < r.lower.array[i])
 
check = true;
 
if (lower.array[i] > r.upper.array[i])
 
check = true;
 
}
 
 
 
return !check;
 
}
 
 
 
public PointKD getNearest(PointKD p) {
 
if (upper == null)
 
return null;
 
if (upper.array.length != p.array.length)
 
return null;
 
PointKD near = new PointKD(p);
 
for (int i = 0; i < upper.array.length; i++) {
 
near.array[i] = p.array[i];
 
if (p.array[i] > upper.array[i])
 
near.array[i] = upper.array[i];
 
if (p.array[i] < lower.array[i])
 
near.array[i] = lower.array[i];
 
}
 
return near;
 
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>

Latest revision as of 21:00, 7 November 2012

Everyone and their brother has one of these now, me and Simonton started it, but I was to inexperienced to get anything written, I took an hour or two to rewrite it today, because I am no longer completely terrible at these things. So here is mine if you care to see it.

This and all my other code in which I display on the robowiki falls under the ZLIB License.

Oh yeah, am I the only one that has a Range function?

KDTreeF

package org.csdgn.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This is a KD Bucket Tree, for fast sorting and searching of K dimensional
 * data.
 * 
 * @author Chase
 * 
 */
public class KDTree<T> {
	protected static final int defaultBucketSize = 48;

	private final int dimensions;
	private final int bucketSize;
	private NodeKD root;

	/**
	 * Constructor with value for dimensions.
	 * 
	 * @param dimensions
	 *            - Number of dimensions
	 */
	public KDTree(int dimensions) {
		this.dimensions = dimensions;
		this.bucketSize = defaultBucketSize;
		this.root = new NodeKD();
	}

	/**
	 * Constructor with value for dimensions and bucket size.
	 * 
	 * @param dimensions
	 *            - Number of dimensions
	 * @param bucket
	 *            - Size of the buckets.
	 */
	public KDTree(int dimensions, int bucket) {
		this.dimensions = dimensions;
		this.bucketSize = bucket;
		this.root = new NodeKD();
	}

	/**
	 * Add a key and its associated value to the tree.
	 * 
	 * @param key
	 *            - Key to add
	 * @param val
	 *            - object to add
	 */
	public void add(double[] key, T val) {
		root.addPoint(key, val);
	}

	/**
	 * Returns all PointKD within a certain range defined by an upper and lower
	 * PointKD.
	 * 
	 * @param low
	 *            - lower bounds of area
	 * @param high
	 *            - upper bounds of area
	 * @return - All PointKD between low and high.
	 */
	@SuppressWarnings("unchecked")
	public List<T> getRange(double[] low, double[] high) {
		Object[] objs = root.range(high, low);
		ArrayList<T> range = new ArrayList<T>(objs.length);
		for(int i=0; i<objs.length; ++i) {
			range.add((T)objs[i]);
		}
		return range;
	}

	/**
	 * Gets the N nearest neighbors to the given key.
	 * 
	 * @param key
	 *            - Key
	 * @param num
	 *            - Number of results
	 * @return Array of Item Objects, distances within the items are the square
	 *         of the actual distance between them and the key
	 */
	public ResultHeap<T> getNearestNeighbors(double[] key, int num) {
		ResultHeap<T> heap = new ResultHeap<T>(num);
		root.nearest(heap, key);
		return heap;
	}


	// Internal tree node
	private class NodeKD {
		private NodeKD left, right;
		private double[] maxBounds, minBounds;
		private Object[] bucketValues;
		private double[][] bucketKeys;
		private boolean isLeaf;
		private int current, sliceDimension;
		private double slice;

		private NodeKD() {
			bucketValues = new Object[bucketSize];
			bucketKeys = new double[bucketSize][];

			left = right = null;
			maxBounds = minBounds = null;
			
			isLeaf = true;
			
			current = 0;
		}

		// what it says on the tin
		private void addPoint(double[] key, Object val) {
			if(isLeaf) {
				addLeafPoint(key,val);
			} else {
				extendBounds(key);
				if (key[sliceDimension] > slice) {
					right.addPoint(key, val);
				} else {
					left.addPoint(key, val);
				}
			}
		}
		
		private void addLeafPoint(double[] key, Object val) {
			extendBounds(key);
			if (current + 1 > bucketSize) {
				splitLeaf();
				addPoint(key, val);
				return;
			}
			bucketKeys[current] = key;
			bucketValues[current] = val;
			++current;
		}
		
		/**
		 * Find the nearest neighbor recursively.
		 */
		@SuppressWarnings("unchecked")
		private void nearest(ResultHeap<T> heap, double[] data) {
			if(current == 0)
				return;
			if(isLeaf) {
				//IS LEAF
				for (int i = 0; i < current; ++i) {
					double dist = pointDistSq(bucketKeys[i], data);
					heap.offer(dist, (T) bucketValues[i]);
				}
			} else {
				//IS BRANCH
				if (data[sliceDimension] > slice) {
					right.nearest(heap, data);
					if(left.current == 0)
						return;
					if (!heap.isFull() || regionDistSq(data,left.minBounds,left.maxBounds) < heap.getMaxKey()) {
						left.nearest(heap, data);
					}
				} else {
					left.nearest(heap, data);
					if (right.current == 0)
						return;
					if (!heap.isFull() || regionDistSq(data,right.minBounds,right.maxBounds) < heap.getMaxKey()) {
						right.nearest(heap, data);
					}
				}
			}
		}

		// gets all items from within a range
		private Object[] range(double[] upper, double[] lower) {
			if (bucketValues == null) {
				// Branch
				Object[] tmp = new Object[0];
				if (intersects(upper, lower, left.maxBounds, left.minBounds)) {
					Object[] tmpl = left.range(upper, lower);
					if (0 == tmp.length) tmp = tmpl;
				}
				if (intersects(upper, lower, right.maxBounds, right.minBounds)) {
					Object[] tmpr = right.range(upper, lower);
					if (0 == tmp.length)
						tmp = tmpr;
					else if (0 < tmpr.length) {
						Object[] tmp2 = new Object[tmp.length + tmpr.length];
						System.arraycopy(tmp, 0, tmp2, 0, tmp.length);
						System.arraycopy(tmpr, 0, tmp2, tmp.length, tmpr.length);
						tmp = tmp2;
					}
				}
				return tmp;
			}
			// Leaf
			Object[] tmp = new Object[current];
			int n = 0;
			for (int i = 0; i < current; ++i) {
				if (contains(upper, lower, bucketKeys[i])) {
					tmp[n++] = bucketValues[i];
				}
			}
			Object[] tmp2 = new Object[n];
			System.arraycopy(tmp, 0, tmp2, 0, n);
			return tmp2;
		}

		// These are helper functions from here down
		// check if this hyper rectangle contains a give hyper-point
		public boolean contains(double[] upper, double[] lower, double[] point) {
			if (current == 0) return false;
			for (int i = 0; i < point.length; ++i) {
				if (point[i] > upper[i] || point[i] < lower[i]) return false;
			}
			return true;
		}

		// checks if two hyper-rectangles intersect
		public boolean intersects(double[] up0, double[] low0, double[] up1, double[] low1) {
			for (int i = 0; i < up0.length; ++i) {
				if (up1[i] < low0[i] || low1[i] > up0[i]) return false;
			}
			return true;
		}

		private void splitLeaf() {
			double bestRange = 0;
			for(int i=0;i<dimensions;++i) {
				double range = maxBounds[i] - minBounds[i];
				if(range > bestRange) {
					sliceDimension = i;
					bestRange = range;
				}
			}
			
			left = new NodeKD();
			right = new NodeKD();
			
			slice = (maxBounds[sliceDimension] + minBounds[sliceDimension]) * 0.5;
			
			for (int i = 0; i < current; ++i) {
				if (bucketKeys[i][sliceDimension] > slice) {
					right.addLeafPoint(bucketKeys[i], bucketValues[i]);
				} else {
					left.addLeafPoint(bucketKeys[i], bucketValues[i]);
				}
			}
			bucketKeys = null;
			bucketValues = null;
			isLeaf = false;
		}

		// expands this hyper rectangle
		private void extendBounds(double[] key) {
			if (maxBounds == null) {
				maxBounds = Arrays.copyOf(key, dimensions);
				minBounds = Arrays.copyOf(key, dimensions);
				return;
			}
			for (int i = 0; i < key.length; ++i) {
				if (maxBounds[i] < key[i]) maxBounds[i] = key[i];
				if (minBounds[i] > key[i]) minBounds[i] = key[i];
			}
		}
	}
	
	/* I may have borrowed these from an early version of Red's tree. I however forget. */
	private static final double pointDistSq(double[] p1, double[] p2) {
        double d = 0;
        double q = 0;
        for (int i = 0; i < p1.length; ++i) {
            d += (q=(p1[i] - p2[i]))*q;
        }
        return d;
    }

    private static final double regionDistSq(double[] point, double[] min, double[] max) {
        double d = 0;
        double q = 0;
        for (int i = 0; i < point.length; ++i) {
            if (point[i] > max[i]) {
            	d += (q = (point[i] - max[i]))*q;
            } else if (point[i] < min[i]) {
                d += (q = (point[i] - min[i]))*q;
            }
        }
        return d;
    }
}

ResultHeap

package org.csdgn.util;

/**
 * @author Chase
 * 
 * @param <T>
 */
public class ResultHeap<T> {
	private Object[] data;
	private double[] keys;
	private int capacity;
	private int size;

	protected ResultHeap(int capacity) {
		this.data = new Object[capacity];
		this.keys = new double[capacity];
		this.capacity = capacity;
		this.size = 0;
	}

	protected void offer(double key, T value) {
		int i = size;
		for (; i > 0 && keys[i - 1] > key; --i);
		if (i >= capacity) return;
		if (size < capacity) ++size;
		int j = i + 1;
		System.arraycopy(keys, i, keys, j, size - j);
		keys[i] = key;
		System.arraycopy(data, i, data, j, size - j);
		data[i] = value;
	}

	public double getMaxKey() {
		return keys[size - 1];
	}
	
	@SuppressWarnings("unchecked")
	public T removeMax() {
		if(isEmpty()) return null;
		return (T)data[--size];
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public boolean isFull() {
		return size == capacity;
	}

	public int size() {
		return size;
	}

	public int capacity() {
		return capacity;
	}
}