找到每个点的最近邻点(最近邻搜索)

4
我正在编写一个方法,该方法以点数组作为输入,并为数组中的每个点查找除其本身外最接近它的点。我目前是通过暴力方法实现这一点(检查每个点与其他每个点)。我的当前实现没有对数组进行排序,但可以使用 CompareByX 方法按 p.x 值对其进行排序。我正在检查算法的运行时间,当 n 值很大时,它变得非常耗时。我对这个主题不是很了解,也知道很少关于不同类型数据结构的知识,任何简单的帮助都将是极好的!
我的当前代码如下:
import java.util.*;
import java.lang.*;
import java.io.*;

class My2dPoint {
  double x;
  double y;

  public My2dPoint(double x1, double y1) {
    x=x1;
    y=y1;
  }

}


class CompareByX implements Comparator<My2dPoint> {
    public int compare(My2dPoint p1, My2dPoint p2) {
    if (p1.x < p2.x) return -1;
        if (p1.x == p2.x) return 0;
        return 1;
    }
}

    /* An object of the above comparator class is used by java.util.Arrays.sort() in main to sort an array of points by x-coordinates */

class Auxiliaries {

    public static double distSquared(My2dPoint p1, My2dPoint p2) {
        double result;
        result = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y);
        return result;
    }

}

public class HW3 {
    public static void main (String argv []) throws IOException {
        int range = 1000000; // Range of x and y coordinates in points

        System.out.println("Enter the number of points");

        InputStreamReader reader1 = new InputStreamReader(System.in);
        BufferedReader buffer1 = new BufferedReader(reader1);
        String npoints = buffer1.readLine();
        int numpoints = Integer.parseInt(npoints);

        // numpoints is now the number of points we wish to generate

        My2dPoint inputpoints [] = new My2dPoint [numpoints];

        // array to hold points

        int closest [] = new int [numpoints];

        // array to record soln; closest[i] is index of point closest to i'th

        int px, py;
        double dx, dy, dist;
        int i,j;
        double currbest;
        int closestPointIndex;
        long tStart, tEnd;

        for (i = 0; i < numpoints; i++) {

          px = (int) ( range * Math.random());
          dx = (double) px;
          py = (int) (range * Math.random());
          dy = (double) py;
          inputpoints[i] = new My2dPoint(dx, dy);

        }

        // array inputpoints has now been filled



        tStart = System.currentTimeMillis();

        // find closest [0]


        closest[0] = 1;
        currbest = Auxiliaries.distSquared(inputpoints[0],inputpoints[1]);
        for (j = 2; j < numpoints; j++) {
           dist = Auxiliaries.distSquared(inputpoints[0],inputpoints[j]);
           if (dist < currbest) {
               closest[0] = j;
               currbest = dist;
           }
        }

        // now find closest[i] for every other i 

        for (i = 1; i < numpoints; i++) {
            closest[i] = 0;
            currbest = Auxiliaries.distSquared(inputpoints[i],inputpoints[0]);
            for (j = 1; j < i; j++) {
              dist = Auxiliaries.distSquared(inputpoints[i],inputpoints[j]);
              if (dist < currbest) {
               closest[i] = j;
               currbest = dist;
          }
            }

            for (j = i+1; j < numpoints; j++) {
              dist = Auxiliaries.distSquared(inputpoints[i],inputpoints[j]);
              if (dist < currbest) {
          closest[i] = j;
                  currbest = dist;
          }
            }
        }

        tEnd = System.currentTimeMillis();
        System.out.println("Time taken in Milliseconds: " + (tEnd - tStart));
    }
}
6个回答

2

1
最近邻 -> kd-树。没错。 - Waldheinz

2
我会先按照x排序。然后,我会使用点之间的x距离作为快速拒绝测试:一旦您找到与一个邻居的距离,任何更近的邻居都必须在x上更接近。这避免了在x范围之外的点进行所有distSquared计算。每次找到更近的邻居时,还会缩小需要搜索的x范围。
此外,如果P2是P1最近的邻居,则将P1用作P2最近邻居的初始猜测。
编辑:经过再次思考,我会按照具有最大范围的维度进行排序。

2
有一些标准的方法可以改进这种搜索,而你想要变得多么复杂,取决于你要搜索多少点。
一个相当普遍且简单的方法是按X或Y对点进行排序。然后,对于每个点,你都应该向前和向后寻找附近的点。记住最近发现的点离当前点的距离,并且当X(或Y)的差异大于此时,就知道没有更近的点需要查找了。
你还可以使用树来将空间分区。维基百科有一页提供了一些可能的算法。但有时设置它们的成本会比节约的成本更高。这是你必须根据你要搜索的点数来决定的事情。

1
另一种可能性,比创建kd树更简单的方法是使用“邻域矩阵”。首先将所有点放入2D正方形矩阵中。然后可以运行完全或部分空间排序,使点在矩阵内有序排列。Y值较小的点可以移动到矩阵的顶部行,同样,Y值较大的点会进入底部行。X坐标较小的点应该移动到左侧的列,对称地,X值较大的点会进入右侧的列。在进行空间排序之后(有许多方法可以实现此操作,包括串行或并行算法),您可以通过访问邻域矩阵中实际存储点P的相邻单元格来查找给定点P的最近点。您可以在以下论文中阅读更多详细信息(您会在网上找到其PDF副本):“基于新兴行为的GPU超大规模人群模拟”。
排序步骤提供了一些有趣的选择。你可以只使用论文中描述的奇偶排序算法,这个算法非常容易实现(甚至可以在CUDA上实现)。如果你仅运行一遍此算法,它将给出一个部分排序,如果你的矩阵接近排序,则已经很有用了。也就是说,如果你的点移动缓慢,这将节省大量计算。
如果你需要完整的排序,你可以按照以下维基百科页面所述多次运行奇偶排序算法。

http://en.wikipedia.org/wiki/Odd%E2%80%93even_sort

如果更改很小,进行一到两次奇偶排序即可使数组再次排序。

在一个退化的情况下,假设所有点都沿着x轴排列,这种方法仍然有效吗? - Bob Coder
1
当然,如果您有一个不均匀的点分布,那么您将会有一些不太好的邻域。在极限情况下,就像您所说的,如果所有点都放置在一个轴上,您仍将把它们排序到矩阵中,但很有可能空间附近的许多点在矩阵内部会相距甚远。因此,您的空间分布越均匀,您的矩阵就越好,矩阵内部的邻域也更加一致。我已经在PasteBin上放了一些样本代码来进行排序这里 - mgmalheiros
我对一个退化案例进行了测试。它可以排序,但是一些附近的点发现彼此相距甚远。我想知道是否可能提出一种更健壮的算法?有什么意见吗?顺便说一句,谢谢您的回复,我很感激。 - Bob Coder
@BobCoder:当你把x和y坐标加在一起时,情况会变得更糟。例如,将x和y坐标转换为二进制并连接两个值。然后对点进行排序。这将沿着z曲线(也称为怪物曲线)对点进行排序。它具有更好的空间属性,并且相对容易和快速。 - Micromega

1

可以使用kd-tree,或者使用一个好的最近邻搜索库。Weka 包含其中之一。


0

如果你的点比较接近,你可以按照距离从某个点进行排序(我认为可以是任意点,但如果该点被视为原点,则可能必须是所有点都在同一象限内)。

假设感兴趣的点是点A,并且距离为D。

选择最靠近点A的点,并且在排序列表中距离点A相对较小的n个索引内(使用较大的n提供可能更好的初始猜测,但需要更长时间)。如果该点与点A之间的线性距离为g,则可以知道最近的点距离A必须至多为g。这样,您只需考虑列表中距离在D-g和D+g之间的点。

绘制图表可能有助于理解它。如果任何人感兴趣,我将添加一个图表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接