web-dev-qa-db-fra.com

Quel est le moyen le plus rapide pour trouver le point le plus proche d'un point donné?

Quel est le moyen le plus rapide de trouver le point le plus proche du point donné dans le tableau de données?

Par exemple, supposons que j'ai un tableau A de points 3D (avec les coordonnées x, y et z, comme d'habitude) et un point (x_p, y_p, z_p). Comment trouver le point le plus proche dans A à (x_p, y_p, z_p)?

Pour autant que je sache, la façon la plus lente de le faire est d'utiliser la recherche linéaire. Y a-t-il de meilleures solutions?

L'ajout de toute structure de données auxiliaire est possible.

34
qutron

Vous pouvez organiser vos points dans un Octree . Il vous suffit ensuite de rechercher un petit sous-ensemble.

Un Octree est une structure de données assez simple que vous pouvez implémenter vous-même (ce qui serait une expérience d'apprentissage précieuse), ou vous pouvez trouver des bibliothèques utiles pour vous aider à démarrer.

25
dkamins

Si vous effectuez une requête ponctuelle sur le plus proche voisin, une recherche linéaire est vraiment la meilleure que vous puissiez obtenir. Ceci suppose bien sûr que les données ne soient pas pré-structurées.

Cependant, si vous allez faire beaucoup de requêtes, il y a quelques structures de données de partitionnement de l'espace . Celles-ci nécessitent un certain prétraitement pour former la structure, mais peuvent alors répondre très rapidement aux requêtes des voisins les plus proches.

Puisque vous avez affaire à l'espace 3D, je vous recommande de regarder soit octrees ou kd-trees . Les arbres Kd sont plus génériques (ils fonctionnent pour des dimensions arbitraires) et peuvent être rendus plus efficaces que les octrees si vous implémentez un algorithme d'équilibrage approprié (par exemple, la médiane fonctionne bien), mais les octrees sont plus faciles à implémenter.

ANN est une excellente bibliothèque utilisant ces structures de données, mais permettant également approximative les requêtes du plus proche voisin qui sont beaucoup plus rapides mais qui ont une petite erreur car ce ne sont que des approximations. Si vous ne pouvez pas prendre d'erreur, définissez l'erreur liée à 0.

16
marcog

Je suggère que l'arbre KD fonctionnera bien. Aussi bon pour les recherches de voisins les plus proches.

4
Vedang Joshi

Je comprends que le quadtree est pour 2d, mais vous pourriez calculer quelque chose pour 3d qui est très similaire. Cela accélérera votre recherche, mais il faudra beaucoup plus de temps pour calculer l'index s'il est effectué à la volée. Je suggère de calculer l'indice une fois, puis de le stocker. À chaque recherche, vous déterminez tous les quads extérieurs, puis vous vous frayez un chemin à la recherche de hits ... cela ressemblerait à une orange. La vitesse augmentera considérablement à mesure que les quads deviennent plus petits. Tout a un compromis.

1
CrazyDart

À moins qu'ils ne soient pas organisés dans une structure de données appropriée, la seule façon sera la recherche linéaire.

1
ruslik

J'utiliserais un arbre KD pour le faire en O(log(n)) temps, en supposant que les points sont distribués au hasard ou que vous avez un moyen de garder l'arbre équilibré.

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

Les arbres KD sont excellents pour ce type de requête spatiale et vous permettent même de récupérer les k voisins les plus proches d'un point de requête.

1
Tom

J'avais besoin de faire cela assez lourdement pour la recherche de nombreux voisins les plus proches dans un environnement en temps réel, et de trouver un meilleur algorithme à la fois en termes de simplicité et de vitesse.

Prenez tous vos points et mettez une copie dans les listes d où d est la dimensionnalité de l'espace. Dans votre cas 3. Triez ces trois listes selon leur dimension. Cela coûte d(nlog(n)) temps. Et c'est tout pour la structure des données.

Nous conservons ces listes correctement triées dans chaque dimension pour tous les points en question. L'astuce est que, par définition, la distance dans une direction doit être inférieure ou égale à la distance euclidienne. Donc, si la distance dans une direction est supérieure à notre distance actuelle la plus proche du point connu le plus proche, ce point ne peut pas être plus proche, et plus important encore, tous les points dans cette direction ne peuvent pas être plus grands. Une fois que cela est vrai pour les directions 2 * d que nous avons, nous avons par définition le point le plus proche.

Pour chaque élément particulier, nous pouvons effectuer une recherche binaire dans les listes triées pour trouver la position la plus proche où le point requis pourrait être dans les deux dimensions différentes. Mathématiquement, nous savons que si la distance dans les directions + x -x + y -y (d'autres dimensions sont faciles à ajouter) dépasse la plus petite distance euclidienne connue jusqu'à un point, ce point doit dépasser la distance, et puisqu'il s'agit d'un tableau trié, par définition, lorsque nous dépassons cette distance dans cette direction, nous savons que nous pouvons abandonner cette direction, car il ne peut y avoir de meilleure réponse dans cette direction. Mais, en nous développant dans ces quatre directions, nous pouvons réduire notre valeur de m car elle est égale à la distance euclidienne du point le plus proche que nous avons trouvé.

Nous avons donc seulement besoin de listes triées pour chaque axe triées en fonction de cet axe. Ce qui est assez simple.

Puis pour interroger la liste:

  • Nous recherchons binaire dans chacune des listes (dlog (n)).
  • Nous trouvons notre distance minimale actuelle, m. (au départ ça peut être l'infini)
  • Pour chaque liste, nous voyageons dans les directions positives et négatives.
  • Pour chacune des directions 2 * d que nous avons,
    • Nous traversons les listes, abaissant m lorsque nous trouvons des points plus proches.
  • Lorsqu'une direction se révèle mathématiquement stérile, nous arrêtons de chercher de cette façon.
  • Lorsqu'aucune direction ne reste, nous avons trouvé notre point le plus proche.

Nous avons trié les listes et devons trouver le point que nous recherchons dans chaque direction de la liste. Nous recherchons binaire pour garder notre journal de complexité temporelle (n). Ensuite, nous avons notre meilleure distance actuelle (peut-être l'infini), puis nous nous déplaçons dans chaque direction dont nous disposons. Au fur et à mesure que nous trouvons de nouveaux points, nous mettons à jour le point le plus proche jusqu'à présent. L'astuce est que nous quittons dès que la distance dans cette seule direction est plus éloignée que notre point le plus proche connu.

Donc, si nous avons un point à une distance connue la plus proche de 13, nous pouvons abandonner la vérification dans les directions + x, -x, + y, -y, dès que la distance dans cette direction dépasse notre distance connue la plus proche. Parce que si elle est plus éloignée de + x que notre m actuel, toutes les valeurs restantes de + x peuvent être mathématiquement prouvées plus éloignées. À mesure que nous obtenons de meilleurs points de plus en plus proches, la quantité d'espace dont nous avons besoin pour chercher diminue de plus en plus.

Si nous manquons de points dans une direction, cette direction est terminée. Si la distance à un point le long de cette seule dimension de la ligne est elle-même supérieure à m, cette direction est terminée.

La solution est m lorsque toutes les directions se sont avérées n'avoir que des points qui doivent être plus éloignés que notre meilleur point jusqu'à présent.

- Puisque nous réduisons progressivement m, la distance dans chaque dimension nécessaire dans son ensemble diminue rapidement, bien que, comme tous les algorithmes, elle diminue moins rapidement dans les dimensions supérieures. Mais, si la distance dans une seule dimension est supérieure à la meilleure que nous ayons jusqu'à présent, il doit nécessairement être le cas que tous les autres points, dans cette direction, ne peuvent pas être meilleurs.

Avec le temps, la complexité semble comparable aux meilleures. Mais, dans la simplicité des infrastructures de données, cet algorithme l'emporte clairement. Il existe de nombreuses autres propriétés qui font de cet algorithme un concurrent sérieux. Lorsque vous mettez à jour des éléments, vous pouvez utiliser les listes avec de très bonnes performances, car vous triez très souvent des listes déjà triées ou des listes presque triées. Vous itérez des tableaux. En termes réels de performances réelles, la plupart des infrastructures de données sont nulles. Généralement à cause de la mise en cache et de la façon dont la mémoire est disposée, nous sommes censés être agnostiques à propos de telles choses, mais cela compte beaucoup. Les données juste à côté de vos données pertinentes actuelles sont beaucoup plus rapides à accéder réellement. Si nous savons déjà où le point que nous allons chercher dans les listes, nous pouvons le résoudre encore plus rapidement (car nous n'aurions pas à le trouver avec une recherche binaire). Et d'autres astuces autorisées réutilisant les informations de l'itération précédente ici et là. Et les dimensions supplémentaires sont essentiellement gratuites (sauf que la valeur ne converge pas plus vite, mais c'est parce qu'il y a plus de points répartis de manière aléatoire dans une sphère qu'un cercle de même rayon).


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);
    }
}

Mise à jour: Concernant, dans les commentaires, le problème des k-points. Vous remarquerez que très peu de choses ont changé. La seule chose pertinente était que si le point v se trouve être inférieur au m actuel (FindingDistanceMaxSq), ce point est ajouté au tas et la valeur de m est définie comme étant égale à la distance euclidienne entre la position de recherche et la position kième élément. La version régulière de l'algorithme peut être considérée comme le cas où k = 1. Nous recherchons le 1 élément que nous voulons et nous mettons à jour m pour qu'il soit le seul (k = 1) élément lorsque v se trouve être plus proche.

Gardez à l'esprit que je ne fais que des comparatifs de distance sous forme de distance au carré, car je n'ai besoin que de savoir si elle est plus éloignée et je ne perds pas de cycles d'horloge sur les fonctions de racine carrée.

Et je sais qu'il existe une structure de données parfaite pour stocker les k-éléments dans un tas de taille limitée. Évidemment, une insertion de tableau n'est pas optimale pour cela. Mais, à part trop Java apis dépendants, il n'y en avait tout simplement pas pour cette classe particulière, mais apparemment Google Guava en fait un. Mais, vous ne remarquerez pas du tout étant donné que les chances sont bonnes votre k n'est probablement pas si grand. Mais, cela rend la complexité temporelle pour une insertion dans des points stockés dans k-time. Il y a aussi des choses comme la mise en cache de la distance du point de recherche pour les éléments.

Enfin, et probablement le plus pressant, le projet que j'utiliserais pour tester le code est en transition, donc je n'ai pas réussi à le tester. Mais, cela montre certainement comment vous procédez: vous stockez les k meilleurs résultats jusqu'à présent, et faites m égal à la distance au kème point le plus proche. - Tout le reste reste le même.

Exemple de source.

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;
}
1
Tatarize