package ca.pfv.spmf.datastructures.kdtree;
/* This file is copyright (c) 2008-2013 Philippe Fournier-Viger
* 
* This file is part of the SPMF DATA MINING SOFTWARE
* (http://www.philippe-fournier-viger.com/spmf).
* 
* SPMF is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
* 
* SPMF is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with
* SPMF. If not, see .
*/

import java.util.Random;

import ca.pfv.spmf.datastructures.redblacktree.RedBlackTree;

/**
 * This is an implementation of a "KD tree" based on the description in the 
 * book: "Algorithms in a Nutshell" by Heineman et al. (2008).
 * 

* * This implementation uses the Randomized-Select algorithm described in the * book "Introduction to algorithms" book by Cormen et al. (2001) as suggested * by Heineman. *

* * Elements that are inserted into the tree have to be arrays of double. *

* * The class provide methods for * - building the tree by inserting points, * - using the tree to find the nearest neighbor to a given point * - using the tree to find the k nearest neighbors to a given point *

* * To find the k-nearest neighboors, the closest points are stored in a red black tree. * * @author Philippe Fournier-Viger */ public class KDTree { private int nodeCount = 0; // number of nodes in the tree private KDNode root = null; // the tree root int dimensionCount = 0; // number of dimensions // random number generator used by the Randomized-select algorithm private static Random random = new Random(System.currentTimeMillis()); /** * Default constructor */ public KDTree() { } /** * Get the number of nodes in the KD-TREE * @return the number of nodes */ public int size() { return nodeCount; } /** * This method build the KDtree from a set of points. * This method should be called only once. * @param points an array of points, where each point is a double[] */ public void buildtree(double[][] points) { if (points.length == 0) { return; } dimensionCount = points[0].length; root = generateNode(0, points, 0, points.length - 1); } /** * Generate a node for the d-dimension for points (left, right). * @param currentD the current dimension * @param points arrays of points * @param left left * @param right right * @return a node */ private KDNode generateNode(int currentD, double[][] points, int left, int right) { // if there is no point if (right < left) { return null; } nodeCount++; // if there is only a single point if (right == left) { return new KDNode(points[left], currentD); } // else if there is more than one point // We calculate the desired rank that correspond to the median. int m = (right - left) / 2; // we select the median point double[] medianNode = randomizedSelect(points, m, left, right, currentD); // we will use this point to separate the two lower branches of the tree. KDNode node = new KDNode(medianNode, currentD); currentD++; if (currentD == dimensionCount) { currentD = 0; } // recursively create subnodes for the two branches of the three node.below = generateNode(currentD, points, left, left + m -1); node.above = generateNode(currentD, points, left + m +1, right); return node; } /** * Method to select the ith smallest integer of an array in average linear * time. It is based on the pseudo-code of the Randomized-Select algorithm in * the book "Introduction to algorithms" by Cormen et al. (2001), with some * modifications such as using a while loop instead of recursive calls. * * @param a: array of integers * @param i: the rank i of the desired integer. * @param currentD: the dimension that is used * @return the element in the array "a" that is larger than i elements. */ private double[] randomizedSelect(double[][] points, int i, int left, int right, int currentD) { int p = left; int r = right; while (true) { if (p == r) { return points[p]; } int q = randomizedPartition(points, p, r, currentD); int k = q - p + 1; if (i == k - 1) { return points[q]; } else if (i < k) { r = q - 1; } else { i = i - k; p = q + 1; } } } /** * Private method used by the randomized-select method * (see the book for details). */ private int randomizedPartition(double[][] points, int p, int r, int currentD) { int i = 0; if (p < r) { i = p + random.nextInt(r - p); } else { i = r + random.nextInt(p - r); } swap(points, r, i); return partition(points, p, r, currentD); // call the partition method of // quicksort. } /** * Private method used by the randomized-select method * (see the book for details). */ private int partition(double[][] points, int p, int r, int currentD) { double[] x = points[r]; int i = p - 1; for (int j = p; j <= r - 1; j++) { if (points[j][currentD] <= x[currentD]) { i = i + 1; swap(points, i, j); } } swap(points, i + 1, r); return i + 1; } /** * swapping two points in an array. * @param array the array * @param i the first point * @param j the second point */ private void swap(double[][] array, int i, int j) { double[] valueI = array[i]; array[i] = array[j]; array[j] = valueI; } //===================================================================================== //======================= To find the first nearest neighboor ========================= //===================================================================================== double [] nearestNeighboor = null; // the current nearest neighboor. double minDist = 0; // the distance of the current nearest neighbor with the target point. /** * Method to get the nearest neighbor */ public double[] nearest(double[] targetPoint) { if (root == null){ return null; } // Find the node where the point would be inserted and calculate the distance findParent(targetPoint, root, 0); // After that, start from the root and check all rectangles that overlap the // distance with the parent. If a point with a distance smaller than the parent is found, // then return it. nearest(root, targetPoint); return nearestNeighboor; } /** * This method find the node where this point would be inserted in the kdd-tree. * @param target : the point * @param node : the current node in the tree * @param d : the dimension used at this level of the tree. * @return : the node where the point would be inserted. */ private void findParent(double[] target, KDNode node, int d) { // IF the node would be inserted in the branch "below" this node. if(target[d] < node.values[d]){ if (++d == dimensionCount) { d = 0; } if(node.below == null){ nearestNeighboor = node.values; minDist = distance(node.values, target); return; } findParent(target, node.below, d); } // IF the node would be inserted in the branch "above" this node. if(++d == dimensionCount) { d = 0; } if(node.above == null){ nearestNeighboor = node.values; minDist = distance(node.values, target); return; } findParent(target, node.above, d); } private void nearest(KDNode node, double[] targetPoint) { // If shorter, update minimum double d = distance(node.values, targetPoint); if (d < minDist) { minDist = d; nearestNeighboor = node.values; } int dMinus1 = node.d-1; if(dMinus1 <0){ dMinus1 = dimensionCount - 1; } // calculate perpendiculary distance with preceding dimensions. double perpendicularyDistance = Math.abs(node.values[node.d] - targetPoint[dMinus1]); if (perpendicularyDistance < minDist) { // explore both side of the tree if (node.above != null) { nearest(node.above, targetPoint); } if (node.below != null) { nearest(node.below, targetPoint); } } else { // only explore one side of the three if (targetPoint[dMinus1] < node.values[node.d]) { if (node.below != null) { nearest(node.below, targetPoint); } } else if (node.above != null) { nearest(node.above, targetPoint); } } } /** * Calcule the euclidian distance between two points * @param node1 the first point. * @param node2 the second point. * @return */ private double distance(double[] node1, double[] node2) { double sum = 0; for(int i=0; i< node1.length; i++){ sum += Math.pow(node1[i] - node2[i], 2); } return Math.sqrt(sum); } //===================================================================================== //======================= Method to find the k nearest neighboor ========================= //===================================================================================== RedBlackTree resultKNN = null; // field to store the current k nearest neighboor with the target point int k =0; // the parameter k. /** * Method to get the k nearest neighboors */ public RedBlackTree knearest(double[] targetPoint, int k) { this.k = k; this.resultKNN = new RedBlackTree(); if (root == null){ return null; } // First traverse the tree to find the place where the node would be inserted. findParent_knn(targetPoint, root, 0); // Now start back at the root, and check all rectangles that have a perpendicular distance // smaller than the k best points found until now. nearest_knn(root, targetPoint); // return the k nearest neighbors. return resultKNN; } /** * traverse the tree to find the place where the node would be inserted. * @param target the vector * @param node the current node * @param d the current dimension */ private void findParent_knn(double[] target, KDNode node, int d) { // If the node would be inserted in the branch "below" if(target[d] < node.values[d]){ if (++d == dimensionCount) { d = 0; } if(node.below == null){ tryToSave(node, target); return; } tryToSave(node.below, target); findParent_knn(target, node.below, d); } // If the node would be inserted in the branch "above". if(++d == dimensionCount) { d = 0; } if(node.above == null){ tryToSave(node, target); return; } tryToSave(node.above, target); findParent_knn(target, node.above, d); } /** * Method to try to save a node in the set of the current closest k neighbors. * @param node the node to be added. * @param target the target node. */ private void tryToSave(KDNode node, double[] target) { if(node == null){ return; } double distance = distance(target, node.values); if(resultKNN.size() == k && resultKNN.maximum().distance < distance){ return; } KNNPoint point = new KNNPoint(node.values, distance); if(resultKNN.contains(point)){ return; } resultKNN.add(point); if(resultKNN.size() > k){ resultKNN.popMaximum(); } } /** * Start back at the root, and check all rectangles that have a perpendicular distance * smaller than the k best points found until now. * @param node the current node * @param targetPoint the vector */ private void nearest_knn(KDNode node, double[] targetPoint) { tryToSave(node, targetPoint); int dMinus1 = node.d-1; if(dMinus1 < 0){ dMinus1 = dimensionCount - 1; } double perpendicularDistance = Math.abs(node.values[node.d] - targetPoint[dMinus1]); if (perpendicularDistance < resultKNN.maximum().distance) { // explore the "above" and "below" branches. if (node.above != null) { nearest_knn(node.above, targetPoint); } if (node.below != null) { nearest_knn(node.below, targetPoint); } } else { // explore one side of the tree. if (targetPoint[dMinus1] < node.values[node.d]) { if (node.below != null) { nearest_knn(node.below, targetPoint); } } else { if (node.above != null) { nearest_knn(node.above, targetPoint); } } } } // ======================================================================================= /** * Convert a vector of double to a string representation * @param values the vector * @return a string */ private String toString(double [] values){ StringBuffer buffer = new StringBuffer(); for(Double element : values ){ buffer.append(" " + element); } return buffer.toString(); } /** * Convert this tree to a string representation */ public String toString(){ return toString(root, " "); } /** * Convert a substree to a string while using some indentation. * @param node the node * @param indent the current indentation * @return a string */ private String toString(KDNode node, String indent){ if(node == null){ return ""; } String newIndent1 = indent + " |"; String newIndent2 = indent + " |"; return toString(node.values) + " (" + node.d +") \n" + indent + toString(node.above, newIndent1) + "\n" + indent + toString(node.below, newIndent2); } }

Ads help maintain this website.