找到最接近给定点的最快方法是什么?

时间:2010-12-03 21:57:09

标签: algorithm data-structures computational-geometry

找到数据数组中给定点的最近点的最快方法是什么?

例如,假设我有一个3D点的数组A(像往常一样有坐标x,y和z)和点(x_p,y_p,z_p)。如何找到A中最近的点(x_p,y_p,z_p)?

据我所知,最慢的方法是使用线性搜索。还有更好的解决方案吗?

可以添加任何辅助数据结构。

9 个答案:

答案 0 :(得分:25)

您可以在Octree中整理积分。然后你只需要搜索一小部分。

八叉树是一个相当简单的数据结构,你可以自己实现(这将是一个宝贵的学习经验),或者你可能会找到一些有用的图书馆来帮助你。

答案 1 :(得分:16)

如果您正在进行一次性最近邻居查询,那么线性搜索确实是您可以获得的最佳效果。这当然是假设数据没有预先构建。

但是,如果您要进行大量查询,则会有一些space-partitioning data structures。这些会进行一些预处理以形成结构,但随后可以非常快速地回答最近邻查询。

由于您正在处理3D空间,因此我建议您查看octreeskd-trees。 Kd树更通用(它们适用于任意维度),如果你实现一个合适的平衡算法(例如中位数很好),可以比八叉树更高效,但是八叉树更容易实现。

ANN是一个很棒的库,使用这些数据结构,但也允许近似最近邻居查询,这些查询速度明显更快但误差很小,因为它们只是近似值。如果您不能收到任何错误,请将错误绑定为0。

答案 2 :(得分:4)

我建议KD-tree工作正常。也适合最近邻搜索。

答案 3 :(得分:1)

我的理解四叉树是2d,但你可以计算3d的东西非常相似。这将加快您的搜索速度,但如果动态完成,则需要更多时间来计算索引。我建议一旦计算索引然后存储它。在每次查找时,你都会找出所有的外部四边形,然后按照你的方式寻找命中...它看起来像是一个橙色。随着四边形变小,速度将大大增加。一切都有折衷。

答案 4 :(得分:1)

除非它们没有按照正确的数据结构进行组织,否则唯一的方法就是线性搜索。

答案 5 :(得分:1)

我会在O(log(n))时间内使用KD树来执行此操作,假设这些点是随机分布的,或者您可以保持树的平衡。

http://en.wikipedia.org/wiki/Kd-tree

KD树非常适合这种空间查询,甚至允许您检索查询点的最近k个邻居。

答案 6 :(得分:1)

我需要在实时环境中对许多最近邻居进行相当大的搜索,并且在简单性和速度方面都能找到更好的算法。

获取所有要点并将副本放入d列表中,其中d是空间的维度。在您的情况下3.根据它们的维度对这三个列表进行排序。这花费了d(nlog(n))时间。这就是数据结构。

我们在所有相关点的每个维度中维护这些正确排序的列表。诀窍在于,根据定义,一个方向上的距离必须小于或等于欧氏距离。因此,如果一个方向上的距离大于我们当前最接近的已知点的最近距离,则该点不能更近,更重要的是,该方向上的所有点都不能更大。一旦这对于2 * d方向成立,我们根据定义得到最接近的点。

对于每个特定元素,我们可以对已排序列表进行二元搜索,以找到所需点在两个不同维度中的最近位置。数学上我们知道,如果+ x -x + y -y中的距离(其他尺寸很容易添加)方向超过了已知的欧几里德距离点的最小距离,那么该点必须超过距离,并且因为它是“sa”按照定义排序数组,当我们超过该方向的距离时,我们知道我们可以中止该方向,因为在那个方向上没有更好的答案。但是,当我们在这四个方向扩展时,我们可以减少m的值,因为它等于我们找到的最近点的欧氏距离。

所以我们需要按照该轴排序的每个轴的排序列表。这很简单。

然后查询列表:

  • 我们二进制搜索每个列表(dlog(n))。
  • 我们找到当前的最小距离,m。 (最初它可以是无限的)
  • 对于每个清单,我们都是积极和消极的方向。
  • 对于我们所拥有的每个2 * d方向,
    • 我们横向列表,当我们找到更近的点时降低m。
  • 当一个方向证明自己在数学上没有结果时,我们就不再那样搜索了。
  • 当没有方向时,我们找到了最接近的观点。

我们已经对列表进行了排序,需要在列表中的每个方向上找到我们要搜索的点。我们二进制搜索以保持我们的时间复杂度log(n)。然后我们有最佳的当前距离(可能是无限远),然后我们向我们可用的每个方向移动。当我们找到新点时,我们会更新到目前为止最接近的点。诀窍是,只要一个方向上的距离远超过我们当前已知的最近点,我们就会退出。

因此,如果我们在已知的最近距离13处有一个点,那么只要该方向上的距离超过我们最接近的已知距离,我们就可以中止检查+ x,-x,+ y,-y方向。因为如果它比我们当前的m更进一步+ x,则可以在数学上证明+ x的所有剩余值都更远。随着我们获得更好和更好的最近点,我们需要搜索的空间量越来越小。

如果我们在一个方向上耗尽了点,那么该方向就完成了。 如果到该线的一个维度的点的距离本身大于m,则该方向结束。

当所有方向证明只有比我们目前为止的最佳点更远的点时,解决方案是m。

- 由于我们逐渐减少m,所以每个维度所需的距离作为一个整体迅速下降,尽管像所有算法一样,它在较高维度下的下降速度较慢。但是,如果只有一个维度的距离大于我们迄今为止的最佳距离,那么必然会出现这些方向上其他所有点都可以更好的情况。

时间复杂性似乎与更好的复杂性相提并论。但是,在数据结构的简单性方面,该算法显然获胜。还有许多其他属性使这个算法成为一个重要的竞争者。更新内容时,您可以使用性能非常好的列表,因为您经常对已经排序的列表或几乎排序的列表进行排序。你正在迭代数组。实际表现中,大多数数据结构都很糟糕。一般来说,由于缓存和内存的布局,我们应该对这些事情不了解,但它很重要。当前相关数据旁边的数据很多实际访问速度更快。如果我们已经知道我们将在列表中查找它的位置,我们可以更快地解决它(因为我们不必通过二进制搜索找到它)。和其他允许的技巧重复使用上一次迭代中的信息。额外的维度基本上是免费的(除了那个值没有更快收敛,但那是因为球体中的随机分布点比同一半径的圆圈更多)。

public class EuclideanNeighborSearch2D {
    public static final int INVALID = -1;
    static final Comparator<Point> xsort = new Comparator<Point>() {
        @Override
        public int compare(Point o1, Point o2) {
            return Double.compare(o1.x, o2.x);
        }
    };
    static final Comparator<Point> ysort = new Comparator<Point>() {
        @Override
        public int compare(Point o1, Point o2) {
            return Double.compare(o1.y, o2.y);
        }
    };

    ArrayList<Point> xaxis = new ArrayList<>();
    ArrayList<Point> yaxis = new ArrayList<>();

    boolean dirtySortX = false;
    boolean dirtySortY = false;

    public Point findNearest(float x, float y, float minDistance, float maxDistance) {
        Point find = new Point(x,y);

        sortXAxisList();
        sortYAxisList();

        double findingDistanceMaxSq = maxDistance * maxDistance;
        double findingDistanceMinSq = minDistance * minDistance;

        Point findingIndex = null;

        int posx = Collections.binarySearch(xaxis, find, xsort);
        int posy = Collections.binarySearch(yaxis, find, ysort);
        if (posx < 0) posx = ~posx;
        if (posy < 0) posy = ~posy;

        int mask = 0b1111;

        Point v;

        double vx, vy;
        int o;
        int itr = 0;
        while (mask != 0) {
            if ((mask & (1 << (itr & 3))) == 0) {
                itr++;
                continue; //if that direction is no longer used.
            }
            switch (itr & 3) {
                default:
                case 0: //+x
                    o = posx + (itr++ >> 2);
                    if (o >= xaxis.size()) {
                        mask &= 0b1110;
                        continue;
                    }
                    v = xaxis.get(o);
                    vx = x - v.x;
                    vy = y - v.y;
                    vx *= vx;
                    vy *= vy;
                    if (vx > findingDistanceMaxSq) {
                        mask &= 0b1110;
                        continue;
                    }
                    break;
                case 1: //+y
                    o = posy + (itr++ >> 2);
                    if (o >= yaxis.size()) {
                        mask &= 0b1101;
                        continue;
                    }
                    v = yaxis.get(o);
                    vx = x - v.x;
                    vy = y - v.y;
                    vx *= vx;
                    vy *= vy;
                    if (vy > findingDistanceMaxSq) {
                        mask &= 0b1101;
                        continue;
                    }
                    break;
                case 2: //-x
                    o = posx + ~(itr++ >> 2);
                    if (o < 0) {
                        mask &= 0b1011;
                        continue;
                    }
                    v = xaxis.get(o);
                    vx = x - v.x;
                    vy = y - v.y;
                    vx *= vx;
                    vy *= vy;
                    if (vx > findingDistanceMaxSq) {
                        mask &= 0b1011;
                        continue;
                    }
                    break;
                case 3: //-y
                    o = posy + ~(itr++ >> 2);
                    if (o < 0) {
                        mask = mask & 0b0111;
                        continue;
                    }
                    v = yaxis.get(o);
                    vx = x - v.x;
                    vy = y - v.y;
                    vx *= vx;
                    vy *= vy;
                    if (vy > findingDistanceMaxSq) {
                        mask = mask & 0b0111;
                        continue;
                    }
                    break;
            }
            double d = vx + vy;

            if (d <= findingDistanceMinSq) continue;

            if (d < findingDistanceMaxSq) {
                findingDistanceMaxSq = d;
                findingIndex = v;
            }

        }
        return findingIndex;
    }

    private void sortXAxisList() {
        if (!dirtySortX) return;
        Collections.sort(xaxis, xsort);
        dirtySortX = false;
    }

    private void sortYAxisList() {
        if (!dirtySortY) return;
        Collections.sort(yaxis,ysort);
        dirtySortY = false;
    }

    /**
     * Called if something should have invalidated the points for some reason.
     * Such as being moved outside of this class or otherwise updated.
     */
    public void update() {
        dirtySortX = true;
        dirtySortY = true;
    }

    /**
     * Called to add a point to the sorted list without needing to resort the list.
     * @param p Point to add.
     */
    public final void add(Point p) {
        sortXAxisList();
        sortYAxisList();
        int posx = Collections.binarySearch(xaxis, p, xsort);
        int posy = Collections.binarySearch(yaxis, p, ysort);
        if (posx < 0) posx = ~posx;
        if (posy < 0) posy = ~posy;
        xaxis.add(posx, p);
        yaxis.add(posy, p);
    }

    /**
     * Called to remove a point to the sorted list without needing to resort the list.
     * @param p Point to add.
     */
    public final void remove(Point p) {
        sortXAxisList();
        sortYAxisList();
        int posx = Collections.binarySearch(xaxis, p, xsort);
        int posy = Collections.binarySearch(yaxis, p, ysort);
        if (posx < 0) posx = ~posx;
        if (posy < 0) posy = ~posy;
        xaxis.remove(posx);
        yaxis.remove(posy);
    }
}

更新:关于,在评论中,k点问题。你会注意到变化很小。唯一相关的事情是,如果发现点v小于当前的m(findingDistanceMaxSq),那么该点被添加到堆中,并且m的值被设置为等于查找位置和发现位置之间的欧几里德距离。第k个元素。算法的常规版本可以看作k = 1的情况。我们搜索我们想要的1个元素,并且当发现v更接近时,我们将m更新为等于唯一的(k = 1)元素。

请记住,我只是以距离平方形式进行距离比较,因为我只需要知道它是否更远,而且我不会浪费时钟周期的平方根功能。

我知道有一个完美的数据结构用于将k元素存储在一个大小有限的堆中。显然,阵列插入不是最佳选择。但是,除了过多的java依赖apis之外,对于那个特定的类而言根本就没有一个,尽管显然Google Guava是一个。但是,如果赔率很高,你根本不会注意到你的k很可能不那么大。但是,它确实使得插入存储在k时间内的点的时间复杂度。还有一些事情,比如从元素的发现点缓存距离。

最后,而且可能最紧迫的是,我用来测试代码的项目正处于转型期,所以我还没有设法对此进行测试。但是,它肯定会显示你是如何做到这一点的:你存储到目前为止最好的k个结果,并使m等于到第k个最近点的距离。 - 其他一切都是一样的。

示例来源。

public static double distanceSq(double x0, double y0, double x1, double y1) {
    double dx = x1 - x0;
    double dy = y1 - y0;
    dx *= dx;
    dy *= dy;
    return dx + dy;
}
public Collection<Point> findNearest(int k, final float x, final float y, float minDistance, float maxDistance) {
    sortXAxisList();
    sortYAxisList();

    double findingDistanceMaxSq = maxDistance * maxDistance;
    double findingDistanceMinSq = minDistance * minDistance;
    ArrayList<Point> kpointsShouldBeHeap = new ArrayList<>(k);
    Comparator<Point> euclideanCompare = new Comparator<Point>() {
        @Override
        public int compare(Point o1, Point o2) {
            return Double.compare(distanceSq(x, y, o1.x, o1.y), distanceSq(x, y, o2.x, o2.y));
        }
    };

    Point find = new Point(x, y);
    int posx = Collections.binarySearch(xaxis, find, xsort);
    int posy = Collections.binarySearch(yaxis, find, ysort);
    if (posx < 0) posx = ~posx;
    if (posy < 0) posy = ~posy;

    int mask = 0b1111;

    Point v;

    double vx, vy;
    int o;
    int itr = 0;
    while (mask != 0) {
        if ((mask & (1 << (itr & 3))) == 0) {
            itr++;
            continue; //if that direction is no longer used.
        }
        switch (itr & 3) {
            default:
            case 0: //+x
                o = posx + (itr++ >> 2);
                if (o >= xaxis.size()) {
                    mask &= 0b1110;
                    continue;
                }
                v = xaxis.get(o);
                vx = x - v.x;
                vy = y - v.y;
                vx *= vx;
                vy *= vy;
                if (vx > findingDistanceMaxSq) {
                    mask &= 0b1110;
                    continue;
                }
                break;
            case 1: //+y
                o = posy + (itr++ >> 2);
                if (o >= yaxis.size()) {
                    mask &= 0b1101;
                    continue;
                }
                v = yaxis.get(o);
                vx = x - v.x;
                vy = y - v.y;
                vx *= vx;
                vy *= vy;
                if (vy > findingDistanceMaxSq) {
                    mask &= 0b1101;
                    continue;
                }
                break;
            case 2: //-x
                o = posx + ~(itr++ >> 2);
                if (o < 0) {
                    mask &= 0b1011;
                    continue;
                }
                v = xaxis.get(o);
                vx = x - v.x;
                vy = y - v.y;
                vx *= vx;
                vy *= vy;
                if (vx > findingDistanceMaxSq) {
                    mask &= 0b1011;
                    continue;
                }
                break;
            case 3: //-y
                o = posy + ~(itr++ >> 2);
                if (o < 0) {
                    mask = mask & 0b0111;
                    continue;
                }
                v = yaxis.get(o);
                vx = x - v.x;
                vy = y - v.y;
                vx *= vx;
                vy *= vy;
                if (vy > findingDistanceMaxSq) {
                    mask = mask & 0b0111;
                    continue;
                }
                break;
        }
        double d = vx + vy;
        if (d <= findingDistanceMinSq) continue;
        if (d < findingDistanceMaxSq) {
            int insert = Collections.binarySearch(kpointsShouldBeHeap, v, euclideanCompare);
            if (insert < 0) insert = ~insert;
            kpointsShouldBeHeap.add(insert, v);
            if (k < kpointsShouldBeHeap.size()) {
                Point kthPoint = kpointsShouldBeHeap.get(k);
                findingDistanceMaxSq = distanceSq(x, y, kthPoint.x, kthPoint.y);
            }
        }
    }
    //if (kpointsShouldBeHeap.size() > k) {
    //    kpointsShouldBeHeap.subList(0,k);
    //}
    return kpointsShouldBeHeap;
}

答案 7 :(得分:0)

考虑到搜索,“最快”的方法是使用voxels。使用1:1点 - 体素图,访问时间是恒定的并且非常快,只需将坐标移动到体素原点(如果需要)的点原点的中心,然后向下舍入位置并访问体素数组那个价值。在某些情况下,这是一个不错的选择。正如前面所解释的,当1:1的地图很难得到(点数太多,体素分辨率太小,自由空间太大)时,八叉树会更好。

答案 8 :(得分:-2)

检查出来..你也可以参考CLRS计算几何章节.. http://www.cs.ucsb.edu/~suri/cs235/ClosestPair.pdf