package edu.wlu.cs.levy.CG; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.Stack; /** * KDTree is a class supporting KD-tree insertion, deletion, equality search, range search, and nearest neighbor(s) * using double-precision floating-point keys. Splitting dimension is chosen naively, by depth modulo K. Semantics are * as follows: * * <UL> * <LI>Two different keys containing identical numbers should retrieve the same value from a given KD-tree. Therefore * keys are cloned when a node is inserted. <BR> * <BR> * <LI>As with Hashtables, values inserted into a KD-tree are <I>not</I> cloned. Modifying a value between insertion and * retrieval will therefore modify the value stored in the tree. * </UL> * * Implements the Nearest Neighbor algorithm (Table 6.4) of * * <PRE> * @techreport{AndrewMooreNearestNeighbor, * author = {Andrew Moore}, * title = {An introductory tutorial on kd-trees}, * institution = {Robotics Institute, Carnegie Mellon University}, * year = {1991}, * number = {Technical Report No. 209, Computer Laboratory, * University of Cambridge}, * address = {Pittsburgh, PA} * } * </PRE> * * * @author Simon Levy, Bjoern Heckel * @version %I%, %G% * @since JDK1.2 */ @SuppressWarnings("serial") public class KDTree<T> implements Serializable { // number of milliseconds final long m_timeout; // K = number of dimensions final private int m_K; // root of KD-tree private KDNode<T> m_root; // count of nodes private int m_count; /** * Creates a KD-tree with specified number of dimensions. * * @param k * number of dimensions */ public KDTree(int k) { this(k, 0); } public KDTree(int k, long timeout) { this.m_timeout = timeout; m_K = k; m_root = null; } /** * Insert a node in a KD-tree. Uses algorithm translated from 352.ins.c of * * <PRE> * @Book{GonnetBaezaYates1991, * author = {G.H. Gonnet and R. Baeza-Yates}, * title = {Handbook of Algorithms and Data Structures}, * publisher = {Addison-Wesley}, * year = {1991} * } * </PRE> * * @param key * key for KD-tree node * @param value * value at that key * * @throws KeySizeException * if key.length mismatches K * @throws KeyDuplicateException * if key already in tree */ public void insert(float[] key, T value) throws KeySizeException, KeyDuplicateException { this.edit(key, new Editor.Inserter<T>(value)); } /** * Edit a node in a KD-tree * * @param key * key for KD-tree node * @param editor * object to edit the value at that key * * @throws KeySizeException * if key.length mismatches K * @throws KeyDuplicateException * if key already in tree */ public void edit(float[] key, Editor<T> editor) throws KeySizeException, KeyDuplicateException { if (key.length != m_K) { throw new KeySizeException(); } synchronized (this) { // the first insert has to be synchronized if (null == m_root) { m_root = KDNode.create(new HPoint(key), editor); m_count = m_root.deleted ? 0 : 1; return; } } m_count += KDNode.edit(new HPoint(key), editor, m_root, 0, m_K); } /** * Find KD-tree node whose key is identical to key. Uses algorithm translated from 352.srch.c of Gonnet & * Baeza-Yates. * * @param key * key for KD-tree node * * @return object at key, or null if not found * * @throws KeySizeException * if key.length mismatches K */ public T search(float[] key) throws KeySizeException { if (key.length != m_K) { throw new KeySizeException(); } KDNode<T> kd = KDNode.srch(new HPoint(key), m_root, m_K); return kd == null ? null : kd.v; } public void delete(float[] key) throws KeySizeException, KeyMissingException { delete(key, false); } /** * Delete a node from a KD-tree. Instead of actually deleting node and rebuilding tree, marks node as deleted. * Hence, it is up to the caller to rebuild the tree as needed for efficiency. * * @param key * key for KD-tree node * @param optional * if false and node not found, throw an exception * * @throws KeySizeException * if key.length mismatches K * @throws KeyMissingException * if no node in tree has key */ public void delete(float[] key, boolean optional) throws KeySizeException, KeyMissingException { if (key.length != m_K) { throw new KeySizeException(); } KDNode<T> t = KDNode.srch(new HPoint(key), m_root, m_K); if (t == null) { if (optional == false) { throw new KeyMissingException(); } } else { if (KDNode.del(t)) { m_count--; } } } /** * Find KD-tree node whose key is nearest neighbor to key. * * @param key * key for KD-tree node * * @return object at node nearest to key, or null on failure * * @throws KeySizeException * if key.length mismatches K */ public T nearest(float[] key) throws KeySizeException { List<T> nbrs = nearest(key, 1, null); return nbrs.get(0); } /** * Find KD-tree nodes whose keys are <i>n</i> nearest neighbors to key. * * @param key * key for KD-tree node * @param n * number of nodes to return * * @return objects at nodes nearest to key, or null on failure * * @throws KeySizeException * if key.length mismatches K */ public List<T> nearest(float[] key, int n) throws KeySizeException, IllegalArgumentException { return nearest(key, n, null); } /** * Find KD-tree nodes whose keys are within a given Euclidean distance of a given key. * * @param key * key for KD-tree node * @param dist * Euclidean distance * * @return objects at nodes with distance of key, or null on failure * * @throws KeySizeException * if key.length mismatches K */ public List<T> nearestEuclidean(float[] key, float dist) throws KeySizeException { return nearestDistance(key, dist, new EuclideanDistance()); } /** * Find KD-tree nodes whose keys are within a given Hamming distance of a given key. * * @param key * key for KD-tree node * @param dist * Hamming distance * * @return objects at nodes with distance of key, or null on failure * * @throws KeySizeException * if key.length mismatches K */ public List<T> nearestHamming(float[] key, float dist) throws KeySizeException { return nearestDistance(key, dist, new HammingDistance()); } /** * Find KD-tree nodes whose keys are <I>n</I> nearest neighbors to key. Uses algorithm above. Neighbors are returned * in ascending order of distance to key. * * @param key * key for KD-tree node * @param n * how many neighbors to find * @param checker * an optional object to filter matches * * @return objects at node nearest to key, or null on failure * * @throws KeySizeException * if key.length mismatches K * @throws IllegalArgumentException * if <I>n</I> is negative or exceeds tree size */ public List<T> nearest(float[] key, int n, Checker<T> checker) throws KeySizeException, IllegalArgumentException { if (n <= 0) { return new LinkedList<T>(); } NearestNeighborList<KDNode<T>> nnl = getnbrs(key, n, checker); n = nnl.getSize(); Stack<T> nbrs = new Stack<T>(); for (int i = 0; i < n; ++i) { KDNode<T> kd = nnl.removeHighest(); nbrs.push(kd.v); } return nbrs; } /** * Range search in a KD-tree. Uses algorithm translated from 352.range.c of Gonnet & Baeza-Yates. * * @param lowk * lower-bounds for key * @param uppk * upper-bounds for key * * @return array of Objects whose keys fall in range [lowk,uppk] * * @throws KeySizeException * on mismatch among lowk.length, uppk.length, or K */ public List<T> range(float[] lowk, float[] uppk) throws KeySizeException { if (lowk.length != uppk.length) { throw new KeySizeException(); } else if (lowk.length != m_K) { throw new KeySizeException(); } else { List<KDNode<T>> found = new LinkedList<KDNode<T>>(); KDNode.rsearch(new HPoint(lowk), new HPoint(uppk), m_root, 0, m_K, found); List<T> o = new LinkedList<T>(); for (KDNode<T> node : found) { o.add(node.v); } return o; } } public int size() { /* added by MSL */ return m_count; } @Override public String toString() { return m_root.toString(0); } private NearestNeighborList<KDNode<T>> getnbrs(float[] key) throws KeySizeException { return getnbrs(key, m_count, null); } private NearestNeighborList<KDNode<T>> getnbrs(float[] key, int n, Checker<T> checker) throws KeySizeException { if (key.length != m_K) { throw new KeySizeException(); } NearestNeighborList<KDNode<T>> nnl = new NearestNeighborList<KDNode<T>>(n); // initial call is with infinite hyper-rectangle and max distance HRect hr = HRect.infiniteHRect(key.length); float max_dist_sqd = Float.MAX_VALUE; HPoint keyp = new HPoint(key); if (m_count > 0) { long timeout = this.m_timeout > 0 ? System.currentTimeMillis() + this.m_timeout : 0; KDNode.nnbr(m_root, keyp, hr, max_dist_sqd, 0, m_K, nnl, checker, timeout); } return nnl; } private List<T> nearestDistance(float[] key, float dist, DistanceMetric metric) throws KeySizeException { NearestNeighborList<KDNode<T>> nnl = getnbrs(key); int n = nnl.getSize(); Stack<T> nbrs = new Stack<T>(); for (int i = 0; i < n; ++i) { KDNode<T> kd = nnl.removeHighest(); // HPoint p = kd.k; if (metric.distance(kd.k.coord, key) < dist) { nbrs.push(kd.v); } } return nbrs; } /* * private void writeObject(java.io.ObjectOutputStream out) throws * IOException { * * } * * private void readObject(java.io.ObjectInputStream in) throws IOException, * ClassNotFoundException { * * } */ }