470 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | |
| id: 5a23c84252665b21eecc7ecb
 | |
| title: K-d tree
 | |
| challengeType: 5
 | |
| forumTopicId: 302295
 | |
| dashedName: k-d-tree
 | |
| ---
 | |
| 
 | |
| # --description--
 | |
| 
 | |
| A k-d tree (short for *k*-dimensional tree) is a space-partitioning data structure for organizing points in a k-dimensional space. k-d trees are a useful data structure for several applications, such as searches involving a multidimensional search key (e.g. range searches and nearest neighbor searches). k-d trees are a special case of binary space partitioning trees. k-d trees are not suitable, however, for efficiently finding the nearest neighbor in high dimensional spaces. As a general rule, if the dimensionality is *k*, the number of points in the data, *N*, should be *N* ≫ 2<sup><i>k</i></sup>. Otherwise, when k-d trees are used with high-dimensional data, most of the points in the tree will be evaluated and the efficiency is no better than exhaustive search, and other methods such as approximate nearest-neighbor are used instead.
 | |
| 
 | |
| # --instructions--
 | |
| 
 | |
| Write a function to perform a nearest neighbour search using k-d tree. The function takes two parameters: an array of k-dimensional points, and a single k-dimensional point whose nearest neighbour should be returned by the function. A k-dimensional point will be given as an array of k elements.
 | |
| 
 | |
| # --hints--
 | |
| 
 | |
| `kdNN` should be a function.
 | |
| 
 | |
| ```js
 | |
| assert(typeof kdNN == 'function');
 | |
| ```
 | |
| 
 | |
| `kdNN([[[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]], [9, 2])` should return an array.
 | |
| 
 | |
| ```js
 | |
| assert(
 | |
|   Array.isArray(
 | |
|     kdNN(
 | |
|       [
 | |
|         [2, 3],
 | |
|         [5, 4],
 | |
|         [9, 6],
 | |
|         [4, 7],
 | |
|         [8, 1],
 | |
|         [7, 2]
 | |
|       ],
 | |
|       [9, 2]
 | |
|     )
 | |
|   )
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]], [9, 2])` should return `[ 8, 1 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3],
 | |
|       [5, 4],
 | |
|       [9, 6],
 | |
|       [4, 7],
 | |
|       [8, 1],
 | |
|       [7, 2]
 | |
|     ],
 | |
|     [9, 2]
 | |
|   ),
 | |
|   [8, 1]
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]], [7, 1])` should return `[ 8, 1 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3],
 | |
|       [5, 4],
 | |
|       [9, 6],
 | |
|       [4, 7],
 | |
|       [8, 1],
 | |
|       [7, 2]
 | |
|     ],
 | |
|     [7, 1]
 | |
|   ),
 | |
|   [8, 1]
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]], [3, 2])` should return `[ 2, 3 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3],
 | |
|       [5, 4],
 | |
|       [9, 6],
 | |
|       [4, 7],
 | |
|       [8, 1],
 | |
|       [7, 2]
 | |
|     ],
 | |
|     [3, 2]
 | |
|   ),
 | |
|   [2, 3]
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[2, 3, 1], [9, 4, 5], [4, 6, 7], [1, 2, 5], [7, 8, 9], [3, 6, 1]], [1, 2, 3])` should return `[ 1, 2, 5 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3, 1],
 | |
|       [9, 4, 5],
 | |
|       [4, 6, 7],
 | |
|       [1, 2, 5],
 | |
|       [7, 8, 9],
 | |
|       [3, 6, 1]
 | |
|     ],
 | |
|     [1, 2, 3]
 | |
|   ),
 | |
|   [1, 2, 5]
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[2, 3, 1], [9, 4, 5], [4, 6, 7], [1, 2, 5], [7, 8, 9], [3, 6, 1]], [4, 5, 6])` should return `[ 4, 6, 7 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3, 1],
 | |
|       [9, 4, 5],
 | |
|       [4, 6, 7],
 | |
|       [1, 2, 5],
 | |
|       [7, 8, 9],
 | |
|       [3, 6, 1]
 | |
|     ],
 | |
|     [4, 5, 6]
 | |
|   ),
 | |
|   [4, 6, 7]
 | |
| );
 | |
| ```
 | |
| 
 | |
| `kdNN([[2, 3, 1], [9, 4, 5], [4, 6, 7], [1, 2, 5], [7, 8, 9], [3, 6, 1]], [8, 8, 8])` should return `[ 7, 8, 9 ]`.
 | |
| 
 | |
| ```js
 | |
| assert.deepEqual(
 | |
|   kdNN(
 | |
|     [
 | |
|       [2, 3, 1],
 | |
|       [9, 4, 5],
 | |
|       [4, 6, 7],
 | |
|       [1, 2, 5],
 | |
|       [7, 8, 9],
 | |
|       [3, 6, 1]
 | |
|     ],
 | |
|     [8, 8, 8]
 | |
|   ),
 | |
|   [7, 8, 9]
 | |
| );
 | |
| ```
 | |
| 
 | |
| # --seed--
 | |
| 
 | |
| ## --seed-contents--
 | |
| 
 | |
| ```js
 | |
| function kdNN(fpoints, fpoint) {
 | |
| 
 | |
| }
 | |
| ```
 | |
| 
 | |
| # --solutions--
 | |
| 
 | |
| ```js
 | |
| function kdNN(fpoints, fpoint) {
 | |
|   function Node(obj, dimension, parent) {
 | |
|     this.obj = obj;
 | |
|     this.left = null;
 | |
|     this.right = null;
 | |
|     this.parent = parent;
 | |
|     this.dimension = dimension;
 | |
|   }
 | |
| 
 | |
|   function kdTree(points, metric, dimensions) {
 | |
|     var self = this;
 | |
| 
 | |
|     function buildTree(points, depth, parent) {
 | |
|       var dim = depth % dimensions.length,
 | |
|         median,
 | |
|         node;
 | |
| 
 | |
|       if (points.length === 0) {
 | |
|         return null;
 | |
|       }
 | |
|       if (points.length === 1) {
 | |
|         return new Node(points[0], dim, parent);
 | |
|       }
 | |
| 
 | |
|       points.sort(function(a, b) {
 | |
|         return a[dimensions[dim]] - b[dimensions[dim]];
 | |
|       });
 | |
| 
 | |
|       median = Math.floor(points.length / 2);
 | |
|       node = new Node(points[median], dim, parent);
 | |
|       node.left = buildTree(points.slice(0, median), depth + 1, node);
 | |
|       node.right = buildTree(points.slice(median + 1), depth + 1, node);
 | |
| 
 | |
|       return node;
 | |
|     }
 | |
| 
 | |
|     this.root = buildTree(points, 0, null);
 | |
| 
 | |
|     this.insert = function(point) {
 | |
|       function innerSearch(node, parent) {
 | |
|         if (node === null) {
 | |
|           return parent;
 | |
|         }
 | |
| 
 | |
|         var dimension = dimensions[node.dimension];
 | |
|         if (point[dimension] < node.obj[dimension]) {
 | |
|           return innerSearch(node.left, node);
 | |
|         } else {
 | |
|           return innerSearch(node.right, node);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       var insertPosition = innerSearch(this.root, null),
 | |
|         newNode,
 | |
|         dimension;
 | |
| 
 | |
|       if (insertPosition === null) {
 | |
|         this.root = new Node(point, 0, null);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       newNode = new Node(
 | |
|         point,
 | |
|         (insertPosition.dimension + 1) % dimensions.length,
 | |
|         insertPosition
 | |
|       );
 | |
|       dimension = dimensions[insertPosition.dimension];
 | |
| 
 | |
|       if (point[dimension] < insertPosition.obj[dimension]) {
 | |
|         insertPosition.left = newNode;
 | |
|       } else {
 | |
|         insertPosition.right = newNode;
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     this.nearest = function(point, maxNodes, maxDistance) {
 | |
|       var i, result, bestNodes;
 | |
| 
 | |
|       bestNodes = new BinaryHeap(function(e) {
 | |
|         return -e[1];
 | |
|       });
 | |
| 
 | |
|       function nearestSearch(node) {
 | |
|         var bestChild,
 | |
|           dimension = dimensions[node.dimension],
 | |
|           ownDistance = metric(point, node.obj),
 | |
|           linearPoint = {},
 | |
|           linearDistance,
 | |
|           otherChild,
 | |
|           i;
 | |
| 
 | |
|         function saveNode(node, distance) {
 | |
|           bestNodes.push([node, distance]);
 | |
|           if (bestNodes.size() > maxNodes) {
 | |
|             bestNodes.pop();
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         for (i = 0; i < dimensions.length; i += 1) {
 | |
|           if (i === node.dimension) {
 | |
|             linearPoint[dimensions[i]] = point[dimensions[i]];
 | |
|           } else {
 | |
|             linearPoint[dimensions[i]] = node.obj[dimensions[i]];
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         linearDistance = metric(linearPoint, node.obj);
 | |
| 
 | |
|         if (node.right === null && node.left === null) {
 | |
|           if (
 | |
|             bestNodes.size() < maxNodes ||
 | |
|             ownDistance < bestNodes.peek()[1]
 | |
|           ) {
 | |
|             saveNode(node, ownDistance);
 | |
|           }
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         if (node.right === null) {
 | |
|           bestChild = node.left;
 | |
|         } else if (node.left === null) {
 | |
|           bestChild = node.right;
 | |
|         } else {
 | |
|           if (point[dimension] < node.obj[dimension]) {
 | |
|             bestChild = node.left;
 | |
|           } else {
 | |
|             bestChild = node.right;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         nearestSearch(bestChild);
 | |
| 
 | |
|         if (bestNodes.size() < maxNodes || ownDistance < bestNodes.peek()[1]) {
 | |
|           saveNode(node, ownDistance);
 | |
|         }
 | |
| 
 | |
|         if (
 | |
|           bestNodes.size() < maxNodes ||
 | |
|           Math.abs(linearDistance) < bestNodes.peek()[1]
 | |
|         ) {
 | |
|           if (bestChild === node.left) {
 | |
|             otherChild = node.right;
 | |
|           } else {
 | |
|             otherChild = node.left;
 | |
|           }
 | |
|           if (otherChild !== null) {
 | |
|             nearestSearch(otherChild);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (maxDistance) {
 | |
|         for (i = 0; i < maxNodes; i += 1) {
 | |
|           bestNodes.push([null, maxDistance]);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (self.root) nearestSearch(self.root);
 | |
| 
 | |
|       result = [];
 | |
| 
 | |
|       for (i = 0; i < Math.min(maxNodes, bestNodes.content.length); i += 1) {
 | |
|         if (bestNodes.content[i][0]) {
 | |
|           result.push([bestNodes.content[i][0].obj, bestNodes.content[i][1]]);
 | |
|         }
 | |
|       }
 | |
|       return result;
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   function BinaryHeap(scoreFunction) {
 | |
|     this.content = [];
 | |
|     this.scoreFunction = scoreFunction;
 | |
|   }
 | |
| 
 | |
|   BinaryHeap.prototype = {
 | |
|     push: function(element) {
 | |
|       // Add the new element to the end of the array.
 | |
|       this.content.push(element);
 | |
|       // Allow it to bubble up.
 | |
|       this.bubbleUp(this.content.length - 1);
 | |
|     },
 | |
| 
 | |
|     pop: function() {
 | |
|       // Store the first element so we can return it later.
 | |
|       var result = this.content[0];
 | |
|       // Get the element at the end of the array.
 | |
|       var end = this.content.pop();
 | |
|       // If there are any elements left, put the end element at the
 | |
|       // start, and let it sink down.
 | |
|       if (this.content.length > 0) {
 | |
|         this.content[0] = end;
 | |
|         this.sinkDown(0);
 | |
|       }
 | |
|       return result;
 | |
|     },
 | |
| 
 | |
|     peek: function() {
 | |
|       return this.content[0];
 | |
|     },
 | |
| 
 | |
|     size: function() {
 | |
|       return this.content.length;
 | |
|     },
 | |
| 
 | |
|     bubbleUp: function(n) {
 | |
|       // Fetch the element that has to be moved.
 | |
|       var element = this.content[n];
 | |
|       // When at 0, an element can not go up any further.
 | |
|       while (n > 0) {
 | |
|         // Compute the parent element's index, and fetch it.
 | |
|         var parentN = Math.floor((n + 1) / 2) - 1,
 | |
|           parent = this.content[parentN];
 | |
|         // Swap the elements if the parent is greater.
 | |
|         if (this.scoreFunction(element) < this.scoreFunction(parent)) {
 | |
|           this.content[parentN] = element;
 | |
|           this.content[n] = parent;
 | |
|           // Update 'n' to continue at the new position.
 | |
|           n = parentN;
 | |
|         }
 | |
|         // Found a parent that is less, no need to move it further.
 | |
|         else {
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     sinkDown: function(n) {
 | |
|       // Look up the target element and its score.
 | |
|       var length = this.content.length,
 | |
|         element = this.content[n],
 | |
|         elemScore = this.scoreFunction(element);
 | |
| 
 | |
|       while (true) {
 | |
|         // Compute the indices of the child elements.
 | |
|         var child2N = (n + 1) * 2,
 | |
|           child1N = child2N - 1;
 | |
|         // This is used to store the new position of the element,
 | |
|         // if any.
 | |
|         var swap = null;
 | |
|         // If the first child exists (is inside the array)...
 | |
|         if (child1N < length) {
 | |
|           // Look it up and compute its score.
 | |
|           var child1 = this.content[child1N],
 | |
|             child1Score = this.scoreFunction(child1);
 | |
|           // If the score is less than our element's, we need to swap.
 | |
|           if (child1Score < elemScore) swap = child1N;
 | |
|         }
 | |
|         // Do the same checks for the other child.
 | |
|         if (child2N < length) {
 | |
|           var child2 = this.content[child2N],
 | |
|             child2Score = this.scoreFunction(child2);
 | |
|           if (child2Score < (swap == null ? elemScore : child1Score)) {
 | |
|             swap = child2N;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // If the element needs to be moved, swap it, and continue.
 | |
|         if (swap != null) {
 | |
|           this.content[n] = this.content[swap];
 | |
|           this.content[swap] = element;
 | |
|           n = swap;
 | |
|         }
 | |
|         // Otherwise, we are done.
 | |
|         else {
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   var dims = [];
 | |
| 
 | |
|   for (var i = 0; i < fpoint.length; i++) dims.push(i);
 | |
| 
 | |
|   var tree = new kdTree(
 | |
|     fpoints,
 | |
|     function(e1, e2) {
 | |
|       var d = 0;
 | |
|       var e3 = e1;
 | |
|       if (!Array.isArray(e1)) {
 | |
|         e3 = [];
 | |
|         for (var key in e1) e3.push(e1[key]);
 | |
| 
 | |
|         e1 = e3;
 | |
|       }
 | |
|       e1.forEach(function(e, i) {
 | |
|         var sqd = e1[i] - e2[i];
 | |
|         d += sqd * sqd;
 | |
|       });
 | |
|       return d;
 | |
|     },
 | |
|     dims
 | |
|   );
 | |
| 
 | |
|   return tree.nearest(fpoint, 1, 1000)[0][0];
 | |
| }
 | |
| ```
 |