来源:亚马逊访谈问题
给定二维空间中的一个点p和其他N个,在距离p最近的N个点中找到K个
做到这一点的最佳方法是什么?
这个Wiki页面在构建算法方面没有提供太多帮助。任何想法/方法。
解决方案1生成大小为K的堆,并以最小距离收集点O(NLogK)复杂性。
解决方案2:取大小为N的数组并按距离排序。应使用快速排序(囤积修改)。作为答案,先拿K分。这是太NlogN复杂度,但可以优化到近似O(N)。如果跳过对不必要的子数组的排序。当您将数组拆分为2个子数组时,应该只取第Kth个索引所在的数组。复杂性为:N+N/2+N/4+…=O(N)。
解决方案3:搜索结果数组中的第K个元素,并取所有小于所创建的点。存在O(N)代数,类似于中值搜索。
注意:最好使用距离的sqr来避免sqrt运算,如果点具有整数坐标,则会更快。
作为面试答案,最好使用解决方案2或3。
对于单个查询。。。
维护大小为k
的堆。
对于每个点,计算到点P
的距离。将该距离插入堆中,如果堆的大小大于k
,则从堆中删除最大值。
运行时间:O(n log k)
解决方案1
private List<Point> nearestKPoint_1(List<Point> list, final Point center, int k) {
List<Point> ans = new ArrayList<>();
PriorityQueue<Point> maxHeap = new PriorityQueue<>(k + 1, new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
return distance(center, o2) - distance(center, o1);
}
});
for (Point p : list) {
maxHeap.offer(p);
if (maxHeap.size() > k) {
maxHeap.poll();
}
}
Iterator<Point> i = maxHeap.iterator();
while (i.hasNext()) {
ans.add(i.next());
}
return ans;
}
public int distance(Point p1, Point p2) {
return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}
static class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
if (x != point.x) return false;
return y == point.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
解决方案2
private List<Point> nearestKPoint_2(List<Point> list, final Point center, int k) {
List<Point> ans = new ArrayList<>();
Distance[] nums = new Distance[list.size()];
for (int i = 0; i < nums.length; i++) {
nums[i] = new Distance(distance(center, list.get(i)), i);
}
quickSelect(nums, k);
for (int i = 0; i < k; i++) {
ans.add(list.get(nums[i].i));
}
return ans;
}
private void quickSelect(Distance[] nums, int k) {
int start = 0, end = nums.length - 1;
while (start < end) {
int p = partition(nums, start, end);
if (p == k) {
return;
} else if (p < k) {
start = p + 1;
} else {
end = p - 1;
}
}
}
private int partition(Distance[] nums, int start, int end) {
Distance pivot = nums[start];
int i = start, j = end + 1;
while (true) {
while (i < end && nums[++i].compareTo(pivot) < 0);
while (j > start && nums[--j].compareTo(pivot) > 0);
if (i >= j) {
break;
}
swap(nums, i, j);
}
swap(nums, start, j);
return j;
}
private void swap(Distance[] nums, int i, int j) {
Distance tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
class Distance implements Comparable<Distance> {
int d;
int i;
public Distance(int d, int i) {
this.d = d;
this.i = i;
}
@Override
public int compareTo(Distance o) {
return this.d - o.d;
}
}
您可以使用KD树http://en.wikipedia.org/wiki/K-d_tree以划分空间并给定点,您将能够使用二进制搜索逐步搜索邻居。使用这种方法的好处是,当您在运行时逐个或分批接收点/查询时,它可以轻松扩展到在线版本。
// point_type pt, length_sq(p) { return pt[0] * pt[0] + pt[1] * pt[1]}
// std::vector<point_type> points to search.
// The algorithm should recursion depth to
// O(k * log(points.size())), and
// running time to O(points.size()).
std::nth_element(
points.begin(),
points.begin() + k,
points.end(),
[&pt](point_type const & a)
{
return length_squared(a - pt);
});
// points[0], ... , points[k - 1] are the closest points to pt
class Solution {
public int[][] kClosest(int[][] points, int K) {
double [] combinationArr = new double[points.length];
Hashtable<Double,int[]> pt = new Hashtable();
for (int i = 0; i <points.length; i++) {
int [] in = points[i];
for (int j = 0; j < in.length - 1; j++) {
Integer x = in[j];
Integer y = in[j + 1];
double powerX=Math.pow(x, 2);
double powerY = Math.pow(y, 2);
double combination= (Double)(Math.sqrt(powerX + powerY));
pt.put(combination, points[i]);
combinationArr[i] = combination;
}
}
Arrays.sort(combinationArr);
int [][] kpoints = new int[K][K];
for (int n = 0; n < K; n++) {
kpoints[n] = pt.get(combinationArr[n]);
}
return kpoints;
}
}
使用LINQ 的C#解决方案
public int[][] KClosest(int[][] points, int[][] p, int K) {
var orderedPoints = points.OrderBy(point => Math.Pow(point[0]-p[0], 2) + Math.Pow(point[1]-p[1], 2));
return orderedPoints.Take(K).ToArray();
}
下面的方法有什么问题?
1) 计算从给定点到其他点的距离。
2) 将该点的距离和索引存储到TreeMap<Double,Integer> map
3) 从地图中选择前K个元素。它的值将给出点数组中Point元素的索引。
映射是根据其键的自然顺序进行排序的,或者由映射创建时提供的比较器进行排序
public static void NearestPoints() {
int[][] Points = { new int[] { -16, 5 },
new int[] { -1, 2 },
new int[] { 4, 3 },
new int[] { 10, -2 },
new int[] { 0, 3 },
new int[] {- 5, -9 } };
// Linq Order by default use quick sort which will be best suited in this.
var orderPoint = from i in Enumerable.Range(0, Points.Length)
orderby Math.Sqrt( Points[i][0] * Points[i][0]
+ Points[i][1] * Points[i][1] )
select new int[][] { Points[i] };
var result = orderPoint.Take(3);
}