Как найти ближайшие точки к заданной точке

Вычисляем ближайшие объекты по координатам

Время на прочтение
5 мин

Количество просмотров 11K

Я разрабатывал один проект по недвижимости и появилась задача показывать объекты расположенные в радиусе 20 км с просматриваемым. Т.е. у нас есть объект, в нашем случае это поселок, и нужно отображать находящиеся рядом поселки из нашей базы данных в радиусе 20 км, при этом имея только координаты их расположения.

Исследование

Итак для решения задачи началось «гугление». И первое что было нагуглено это алгоритм расчета расстояний между двумя точками на шаре, статья уходила в Wikipedia. Статья конечно интересное, но как она поможет в моем деле!? Как выяснилось от нее будет толк, но не сразу. Смысл в том, что просчет расстояний по координатам хорош, но как делать выборку из базы и рассчитывать координаты «на ходу»!? Вероятно данным способом никак. Конечно, решение в лоб было каким то образом просчитать насколько можно сдвинуться от координаты чтобы получить нужное расстояние и запросить через какой-нибудь SQL BETWEEN.

Снова гуглим и нагугливаем ВОПРОС на Хабр Q&A. В лучшем ответе решение есть, но оно указывает, что у нас длина одного градуса в километрах равно 111 км, но это далеко не всегда так. Отсюда было понятно, что решение слишком не точное. Читаем дальше и там некий @alex40 предлагает решение как раз с between но выбирать по квадрату. Суть его решения заключается в том, чтобы взять диапазон координат по квадрату а не по окружности и запросить выборку как раз с оператором BETWEEN. И глядя на элегантность этого решения, я понял, что надо делать как то так, но оставался вопрос, что квадрат вносит слишком большую неточность.

Схема решения

Взять изначальную точку координат, и прибавить к ней число градусов соответствующее расстоянию, необходимому для выборки.

Если соединить эти точки, на которые мы прибавили изначальную координату получится квадрат (простите не ровно нарисовал).

Далее с помощью все той же википедии я открыл для себя несколько формул, и одна из них прямо то что нужно. Это формула вычисления длины градуса. Помните в начале я говорил, что 111 км. это слишком абстрактно из первого решения в вопросе. Так вот, эта формула позволяет вычислять длину градуса непосредственно на конкретной меридиане или параллели, нужно только знать радиус нашего шара. Там же на странице википедии есть предварительно рассчитанные данные, по которым можно будет проверить свою формулу

φ

Δ1
lat

Δ1
long

110.574 km

111.320 km

15°

110.649 km

107.551 km

30°

110.852 km

96.486 km

45°

111.133 km

78.847 km

60°

111.412 km

55.800 km

75°

111.618 km

28.902 km

90°

111.694 km

0.000 km

Расчеты

Приступаем к расчетам. Из открытых источников нам известно, что:

  • Средний радиус Земли R = 6371210 м.

  • Экваториальный радиус Земли RЭ = 6378,245 м.

  • Полярный радиус Земли RП = 6356,830 м.

Я для расчетов взял средний радиус. Естественно нужно помнить, что земля все-таки не идеальная сфера, поэтому погрешность есть и в этих расчетах, но для нашей задачи это допустимая погрешность.

Я написал небольшой код для проверки вычислений, и для того, чтобы я мог взять числа и проверить их на реальных данных.

Код для проверки

const EART_RADIUS = 6371210; //Радиус земли
const DISTANCE = 20000; //Интересующее нас расстояние

//https://en.wikipedia.org/wiki/Longitude#Length_of_a_degree_of_longitude
function computeDelta(degrees) {
  return Math.PI / 180 * EART_RADIUS * Math.cos(deg2rad(degrees));
}

function deg2rad(degrees) {
  return degrees * Math.PI / 180;
}

const latitude = 55.460531; //Интересующие нас координаты широты
const longitude = 37.210488; //Интересующие нас координаты долготы

const deltaLat = computeDelta(latitude); //Получаем дельту по широте
const deltaLon = computeDelta(longitude); // Дельту по долготе

const aroundLat = DISTANCE / deltaLat; // Вычисляем диапазон координат по широте
const aroundLon = DISTANCE / deltaLon; // Вычисляем диапазон координат по долготе

console.log(aroundLat, aroundLon);

В коде я сразу установил константу расстояния, радиуса земли и принял решение все считать в метрах, поскольку данные я нашел в метрах и лень было разделить их на 1000, да и в метрах казалось, что точность немного выше, чем в километрах с округлением.

Суть этого кода в следующем. Мы не забываем перевести градусы координат в радианы, поскольку формула из википедии рассчитана на радианы. Мы вычисляем дельту ширины и долготы одинаково по одной и той же формуле, поскольку мы условились что у нас идеальный шар и эта погрешность нам допустима. С помощью формулы мы узнаем сколько градусов у нас в одном километре, а дальше простая пропорция.

1 градус — 63046.689652997775 метров
X градусов — 200000 метров

Если 1 градус, соответствует 63046.689652997775 метров (для широты вычисленной из координаты), то 20000 метров соответсвует X. Дальше, как в школе учили, наискосок умножаем на оставшееся делим. И так как там у нас получается умножение на 1, то это действие можно упустить и записать как `DISTANCE / deltaLat`. Тоже самое проделываем для координаты долготы.

На этих конкретных координатах получаются числа 0.31722522007226484 и 0.22583381380662185. По сути это и есть числа, готовые прибавляться к координатам, чтобы получить тот самый заветный квадрат.

Теперь мы можем добавить эти числа в SQL запрос, чтобы посмотреть, что за выборка у нас получится:

select
	name,
	latitude,
	longitude
from
	villages
where
	latitude between 55.460531 - 0.31722522007226484 and 55.460531 + 0.31722522007226484
	and longitude between 37.210488 - 0.22583381380662185 and 37.210488 + 0.22583381380662185;

Ну и в моей выборке оказалось 7 объектов. Конечно я взял эту выборку и проверил координаты с помощью линейки на Яндекс Картах. В моем случае все попали в радиус обозначенных 20км. Но мы же помним, что взяли квадрат, а не окружность для вычисления?! Я там даже схему нарисовал в начале, что за квадрат. Итак, если сделать окружность, внутри этого квадрата, она как раз будет радиусом примерно те же 20 км.

Я добавил картинку для наглядности. Видно, что если высота квадрата 40 км, и в нем окружность, то радиус ее тоже будет соответствовать 20 км. Остаются лишние области — углы квадрата, которые я закрасил зеленым. Это то что у нас может попасть в выборку, но они уже не соответствуют именно радиусу в 20 км. Т.е. это лишние данные. И вот тут приходит на помощь та самая формула, о которой я говорил в начале — Расчет расстояния между координатами. С помощью этой формулы можно сравнить исходную точку с координатами из выборки и отсечь те, что будут превышать те самые 20 км, поставленные в задаче.

Итог

Задача решена. Алгоритм придуман. Осталось упаковать это в «красивый» и «чистый» код, чтобы все было по феншую. Надеюсь статья была полезной, потому что когда я искал решения задачи, я наткнулся на множество формул, на множество идей, но не нашел места, где это было бы собрано вот так, как попытался собрать я в рамках данной статьи.

Ссылки

Расчет длины градуса

Расчет дистанции между координатами на сфере

Статья, которая помогла вникнуть в тему расчетов

Тоже полезная статья, в ней есть данные для расчетов

UPD:

1. Изменил картинку с ошибочным радиусом и описание высоты квадрата

Я почему-то понял вопрос не правильно и подумал про N ближайших точек. Как уже ответил господин @AnT разбиение Вороного для этого намного лучше подойдет, собрал пример на d3.js в котором реализовано разбиение Вороного:

введите сюда описание изображения


Для поиска нескольких точек можно воспользоваться деревьями


Есть квадродерево алгоритмы на нем достаточно просты и есть готовые реализации.

Вот пример использования d3-quadtree от автора d3.js

желтые — узлы которые обошел алгоритм поиска

красные — найденные ближайшие узлы узлы

введите сюда описание изображения


Вот пример испольлзующий kd-дерево и поиск ближайших соседей из этого примера на d3.v3 под последнюю версию d3.v5

введите сюда описание изображения

I needed to do this rather heavily for the many nearest neighbors search in a real time environment, and hit on a better algorithm both in terms of simplicity and speed.

Take all your points and put a copy into d lists where d is the dimensionality of the space. In your case 3. Sort those three lists according to their dimension. This costs d(nlog(n)) time. And that’s it for the data structure.

We maintain these properly sorted lists in each dimension for all the points in question. The trick is that by definition the distance in one direction must be less than or equal to the euclidean distance. So if the distance in one direction is greater than our current closest distance of the closest known point then that point cannot be closer, and more importantly all points in that direction cannot be greater. Once this is true for the 2*d directions we have we by definition have the closest point.

For each particular element we can binarysearch into the sorted lists to find the nearest position where the required point could be in the two different dimensions. Mathematically we know that if the distance in the +x -x +y -y (other dimensions are easy to add) directions exceeds smallest known Euclidean distance to a point, that that point must exceed the distance, and since it’s a sorted array, by definition, when we exceed that distance in that direction, we know we can abort that direction, because there can be no better answer in that direction. But, as we expand out in these four directions we can reduce our value of m because it is equal to the euclidean distance of the closest point we found.

So we only need sorted lists for each axis sorted according to that axis. Which is pretty simple.

Then to query the list:

  • We binary search into each of the lists (dlog(n)).
  • We find our current minimum distance, m. (initially it can be infinity)
  • For each list, we travel in the positive and negative directions.
  • For each of the 2*d directions we have,
    • We transverse the lists, lowering m when we find closer points.
  • When a direction proves itself to be mathematically fruitless, we stop searching that way.
  • When no direction remains we have found our closest point.

We have sorted lists and need to find the point we are searching for in each direction in the list. We binary search in to keep our time complexity log(n). Then we have our best current distance (possibly infinity) and then we move in each direction we have available to us. As we find new points, we update the closest point so far we have. The trick is that we quit as soon as the distance in just that one direction is further than our current known closest point.

So if we have a point at a known closest distance of 13 then we can abort checking in the +x, -x, +y, -y, directions as soon as the distance in just that direction exceeds our closest known distance. Because if it is further +x than our current m, all the remaining values of +x can be mathematically be proven to be further away. As we get better and better closest points, the amount of space we need to search gets smaller and smaller.

If we run out of points in a direction, that direction is finished.
If the distance to a point along just that one dimension of the line is itself greater than m, that direction is finished.

The solution is m when all directions proven to only have points that must be farther than our best point so far.

— Since we progressively reduce m, the distance in each dimension needed as a whole drops quickly, though like all algorithms it drops off less quickly in higher dimensions. But, if the distance in just one dimension is greater than the best we have thus far, it must necessarily be the case that all the rest of those points, in that direction, can’t be better.

In time complexity seems on par with the better ones. But, in simplicity of the datastructures, this algorithm clearly wins. There’s a lot of other properties that make this algorithm a serious contender. When you update stuff, you can resort the lists with really good performance because you are very often sorting already sorted lists or nearly sorted lists. You are iterating arrays. In actual terms of real performance most datastructures suck. Generally because of caching and how memory is laid out, we are supposed to be agnostic about such things but it matters a lot. The data right next to your current relevant data is much faster to actually access. If we already know where the point we’re going to be looking for it in the lists, we can solve it even faster (since we wouldn’t have to find it with a binary search). And other permitted tricks reusing the information from the previous iteration here and there. And additional dimensions are basically free (save that then the value doesn’t converge faster, but that’s because there’s more randomly distributed points in a sphere than a circle of the same radius).


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

Update: With regard to, in the comments, the k-points problem. You will notice that very little changed. The only relevant thing was if the point v is found be be less than the current m (findingDistanceMaxSq), then that point is added to the heap, and the value for m is set to be equal to euclidean distance between the finding position and the kth element. The regular version of the algorithm can be seen as the case where k = 1. We search for the 1 element we want and we update m to equal the only (k=1) element when v is found to be closer.

Keep in mind, I only ever do distance comparatives in the distance squared form, since I only ever need to know if it’s farther away, and I don’t waste clock cycles on square root functions.

And I know there is a perfect data structure for storing the k-elements in a size limited heap. Obviously an array insertion is not optimal for that. But, other than too much java dependent apis there simply wasn’t one for that particular class though apparently Google Guava makes one. But, you won’t really notice at all given that odds are good your k is likely not that big. But, it does make the time complexity for an insertion in points stored in k-time. There are also things like caching the distance from the finding-point for the elements.

Finally, and likely most pressingly, the project I would use to test the code is in transition so I haven’t managed to test this out. But, it certainly shows how you do this: You store the k best results thus far, and make m equal to the distance to the the kth closest point. — All else remains the same.

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

0 / 0 / 0

Регистрация: 26.05.2015

Сообщений: 2

1

Поиск ближайшей точки в множестве к данной

26.05.2015, 14:07. Показов 10105. Ответов 10


Студворк — интернет-сервис помощи студентам

Доброго времени суток.
Есть у меня такая задача.
Дано множество точек N на плоскости. Оно постоянно и инициализируется в начале. Поэтому особых требований к времени начальной подготовки нет.

Далее на вход поступают случайные точки, и нужно найти для каждой ближайшую точку из нашего множества.
Вот этот процесс и надо оптимизировать.

Искал в интернете, часто наталкивался на советы посмотреть в сторону kd-деревьев и диаграммы Вороного.
Но я никак не могу понять, как они применимы к данной задаче, когда входная точка случайная, а не одна из множества N.

В какую сторону копать?

Заранее благодарю за помощь.



0



5537 / 4322 / 1383

Регистрация: 14.04.2014

Сообщений: 19,368

Записей в блоге: 19

26.05.2015, 14:59

2

зависит от количества точек наверняка
я делал очень просто по методу разбиения на квадраты
поиск идет только по 9 квадратам, далее перебор
вся карта содержит <10000 точек, разбита на 100 квадратов
скорость вполне достаточная



0



0 / 0 / 0

Регистрация: 26.05.2015

Сообщений: 2

26.05.2015, 15:06

 [ТС]

3

Т.е. по принципу: разбили на квадраты, определили, в какой квадрат попала наша точка, перебираем в нем?
Не прокатит. Запросто может быть ситуация, что ближайшая точка не в том квадрате, в который попала наша входная…



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

26.05.2015, 15:27

4

K-d дерево позволяет довольно быстро найти точки, ближайшие к заданной. Но, судя по картинкам для 2D варианта, прямым спуском ближайшую точку не получим, нужно будет проверить точки в соседних ветках дерева тоже.



0



5537 / 4322 / 1383

Регистрация: 14.04.2014

Сообщений: 19,368

Записей в блоге: 19

26.05.2015, 15:30

5

плохо прочитал
9 квадратов
это и есть дерево, только 1 уровень



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

26.05.2015, 15:41

6

Цитата
Сообщение от krapotkin
Посмотреть сообщение

плохо прочитал
9 квадратов
это и есть дерево, только 1 уровень

Пусть в 8-ми крайних квадратах все точки лежат почти на границах, а в центральном точнехонько в середине квадрата. Пусть наша точка попала в центральный квадрат, но почти возле его вершины. Получается, что ближайшая точка множества будет не в центральном квадрате, а в одном из крайних.



0



5537 / 4322 / 1383

Регистрация: 14.04.2014

Сообщений: 19,368

Записей в блоге: 19

26.05.2015, 16:30

7

правильно
но все точки, не лежащие в 9 квадратах, заведомо дальше, чем лежащие
отсюда мораль
при равномерном распределении точек мы сократили поиски на 91 процент



0



Модератор

2978 / 2131 / 451

Регистрация: 26.03.2015

Сообщений: 8,298

26.05.2015, 18:23

8

Цитата
Сообщение от krapotkin
Посмотреть сообщение

но все точки, не лежащие в 9 квадратах, заведомо дальше, чем лежащие

Не верно.
Если мы почти в вершине одного из квадратов стороной 1, то расстояние до самой дальней точки в нашем квадрате чуть меньше https://www.cyberforum.ru/cgi-bin/latex.cgi?sqrt{2} (а в одном из девяти — в 2 раза дальше может быть), а расстояние до самой ближайшей точки вне девяти квадратов, чуть больше 1.



0



5537 / 4322 / 1383

Регистрация: 14.04.2014

Сообщений: 19,368

Записей в блоге: 19

26.05.2015, 21:29

9

тоже согласен, но тут сильно выручает что при равномерном распределении вероятность такого случая крайне низка
можно свести этот случай к 0%, если заранее рассчитать максимальный минимум MM расстояния до соседней точки для всего множества, и задать разбиение с условием что x*sqrt(2) < MM. В этом случае вроде алгоритм должен сойтись

для точного разбиения можно использовать систему перекрывающихся на R/2 круглых зон
у точки будет более одной принадлежности к кругу, алгоритм будет чуть сложнее, но ненамного, и все равно позволит исключить довольно большое пространство



0



Модератор

2978 / 2131 / 451

Регистрация: 26.03.2015

Сообщений: 8,298

26.05.2015, 23:46

10

По-моему, нужно
0. Разбить всю «плоскость» на клетки квадратичным деревом (каждая клетка делится на четыре… и так далее рекурсивно).
1. искать все точки, которые могут быть не дальше R (квадрат самой мелкой сетки полностью или частично внутри круга)
1.1. если найдено приемлемое количество точек, то просто проверяем расстояния до всех
1.1.1. если расстояние R0 до ближайшей из них меньше R, то это ответ
1.1.2. иначе нужно повторить поиск с самого начала, взяв за R = R0
1.2. точек не найдено — повторяем с удвоенным расстоянием
1.3. точек найдено слишком много — уменьшаем расстояние

Добавлено через 2 минуты
начинать надо маленького расстояния
все эти холостые поиски с небольшим расстоянием в сумме будут весить, наверное, меньше, чем последний результативный



0



Igor3D

1741 / 661 / 87

Регистрация: 01.10.2012

Сообщений: 3,277

27.05.2015, 06:47

11

Цитата
Сообщение от Antipich
Посмотреть сообщение

Искал в интернете, часто наталкивался на советы посмотреть в сторону kd-деревьев и диаграммы Вороного.
Но я никак не могу понять, как они применимы к данной задаче, когда входная точка случайная, а не одна из множества N.

Прекрасно применимы, это общепринятый способ. Если надо расскажу как работает kd-tree, а если надо решение попроще и скорость поиска не особо волнует, то просто сортируете исходные точки по X или Y, а поиск выглядит так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int findNear( const Point & pt, const std::vector <Point> & vec )
{
  auto it = std::lower_bound(vec.begin(), vec.end(), pt);
  if (it == vec.end()) it = vec.begin();
 
  int index = it - vec.begin();
  int minIndex = index;
  float minD = distance2(pt, vec[index];  // квадрат расстояния
 
// просмотр вперед пока расстояние по X не превысит минимальное
  for (int i = index + 1; i < vec.size(); ++i) {
    float dx = vec[i].x - pt.x;
    if (dx * dx > minD) break;
    float curD = distance2(pt, vec[i]);
    if (curD >= minD) continue;
    minD = curD;
    minIndex = i;
  }
 
// просмотр назад
  for (int i = index - 1; i >= 0; --i) {
    float dx = vec[i].x - pt.x;
    if (dx * dx > minD) break;
    float curD = distance2(pt, vec[i]);
    if (curD >= minD) continue;
    minD = curD;
    minIndex = i;
  }
  return minIndex;
}



0



    msm.ru

    Нравится ресурс?

    Помоги проекту!

    !
    правила раздела Алгоритмы

    1. Помните, что название темы должно хоть как-то отражать ее содержимое (не создавайте темы с заголовком ПОМОГИТЕ, HELP и т.д.). Злоупотребление заглавными буквами в заголовках тем ЗАПРЕЩЕНО.
    2. При создании темы постарайтесь, как можно более точно описать проблему, а не ограничиваться общими понятиями и определениями.
    3. Приводимые фрагменты исходного кода старайтесь выделять тегами code…/code
    4. Помните, чем подробнее Вы опишете свою проблему, тем быстрее получите вразумительный совет
    5. Запрещено поднимать неактуальные темы (ПРИМЕР: запрещено отвечать на вопрос из серии «срочно надо», заданный в 2003 году)
    6. И не забывайте о кнопочках TRANSLIT и РУССКАЯ КЛАВИАТУРА, если не можете писать в русской раскладке :)

    >
    Поиск ближайшей точки?
    , подскажите формулу для нахождения ближайшей точки

    • Подписаться на тему
    • Сообщить другу
    • Скачать/распечатать тему

      


    Сообщ.
    #1

    ,
    08.01.09, 23:41

      Приветствую!

      Если не сложно подскажите формулу для вычисления ближайшей точки.
      Т.е. есть некоторое множество точек на плоскости, среди них нужно найти ближайшую к заданной точке.

      Входные параметры,
      N кол-во точек с координатами (x, y)
      Заданная точка S (x, y).
      Нужно найти ближайшую к S точку.

      Мне б формулу этого дела.
      А то искал в сети — ничего понятного не нашел, одни какие-то абстракции, теории…

      Спасибо!


      Der_Meister



      Сообщ.
      #2

      ,
      08.01.09, 23:50

        Имхо стоит упорядочить точки по параметру (x + y). Тогда искать нужно будет только в окрестностях данной точки.

        Возможно сам критерий (x + y) ошибочен, но извлечение квадратного корня использовать нельзя. Есть еще алгоритмы быстрой оценки расстояния…

        Сообщение отредактировано: Der_Meister — 08.01.09, 23:51


        albom



        Сообщ.
        #3

        ,
        09.01.09, 00:29

          Senior Member

          ****

          Рейтинг (т): 20

          Цитата Der_Meister @ 08.01.09, 23:50

          Имхо стоит упорядочить точки по параметру (x + y). Тогда искать нужно будет только в окрестностях данной точки.

          Что в окрестности искать надо — это точно, но вот только проблема раз: в метрике l1 ближайшая точка может не совпадать с ближайшей в метрике l2 (т.е. придется всё равно перебирать всё элементы множества заданных точек), и проблема два: только само по себе упорядочивание займет больше времени, чем поиск в лоб.

          Цитата Der_Meister @ 08.01.09, 23:50

          но извлечение квадратного корня использовать нельзя

          Кто сказал? :ph34r:

          Цитата @POLL @ 08.01.09, 23:41

          Мне б формулу этого дела.

          Если тебя интересует именно формула, то её можно записать, например, так (в TEX нотации):

          T = argmin_{x in P }{rho(x, S)} 

          P — множество заданных точек, T — ответ, искомая точка, а ρ — метрика, естественно.


          Der_Meister



          Сообщ.
          #4

          ,
          09.01.09, 00:32

            Цитата albom @ 09.01.09, 00:29

            Кто сказал?

            Я ретроград и привык, что операция извлечения квадратного корня крайне медленна. :)


            albom



            Сообщ.
            #5

            ,
            09.01.09, 00:37

              Senior Member

              ****

              Рейтинг (т): 20

              А я и не утверждаю обратное, но тем не менее мне совершенно непонятно как скорость извлечения корня может быть связана вот с этим утвержением:

              Цитата Der_Meister @ 08.01.09, 23:50

              извлечение квадратного корня использовать нельзя

              :angry:


              MBo



              Сообщ.
              #6

              ,
              09.01.09, 06:08

                >операция извлечения квадратного корня крайне медленна
                необязательно извлекать корень, при нахождении минимального евклидова расстояния достаточно сравнения суммы квадратов

                @POLL
                Операцию нужно выполнить один раз или много раз находить ближайшие к заданным точкам?


                @POLL



                Сообщ.
                #7

                ,
                09.01.09, 08:23

                  Цитата albom @ 09.01.09, 00:29

                  Цитата @POLL @ 08.01.09, 23:41

                  Мне б формулу этого дела.

                  Если тебя интересует именно формула, то её можно записать, например, так (в TEX нотации):

                  T = argmin_{x in P }{rho(x, S)} 

                  P — множество заданных точек, T — ответ, искомая точка, а ρ — метрика, естественно.

                  Ой, а можно эту формулу немного подробнее описать, как получить метрики каждой точки, что такое rho, argmin_ :wacko:
                  Я не особо был силен в высшей математике, а счас тем более мало что припомню.

                  -Added 09.01.09, 08:59

                  Цитата MBo @ 09.01.09, 06:08

                  @POLL
                  Операцию нужно выполнить один раз или много раз находить ближайшие к заданным точкам?

                  Один раз.

                  Вообще, я думал решить задачу как-то так.
                  Брать заданную точку (x, y) за центр/ось.
                  Вокруг этой оси «рисовать» окружность радиусом 1. И смотреть попадают в неё точки или нет.
                  Если попадает одна точка => это искомая ближайшая точка.
                  Если попадает несколько точек => считаем расстояние от каждой точки до оси, у точки которой меньшее расстояние — искомая.
                  Если в получившейся окружности точек нет, увеличиваем радиус окружности — 2, 3, 4 и т.д. И снова пробегаем по циклу.

                  Правда я не уверен, что такой цикл лучше/производительнее представленной выше формул с метриками.

                  Этот алгоритм мне нужен для моего веб-приложения прогноза погоды.
                  Там так. Пользователь заходит на сайт, при этом определяется его географическая привязка, координаты (широта/долгота т.е. x, y).
                  По этим координатам пользователю выдается прогноз погоды его города. При этом возможны 2 ситуации.
                  1) Для его координат есть прогноз
                  2) Для его координат прогноза нет.
                  Вот, во втором случае мне и нужно определить ближайшую точку. Есть точка пользователя (S) и есть точки с погодой (P). Среди P нужно найти ближайшую.

                  Вот, как-то так. :)

                  Profi

                  OpenGL



                  Сообщ.
                  #8

                  ,
                  09.01.09, 10:12

                    Посмотри здесь, в пятой главе то, что нужно.


                    @POLL



                    Сообщ.
                    #9

                    ,
                    09.01.09, 11:32

                      Цитата Konigsberg @ 09.01.09, 09:38

                      @POLL, это жесть…
                      Вам нужно написать один простой цикл пробегающий по всем точкам в котором из координат вашей точки вычесть координаты каждой точки в цикле, возвести их в квадрат, сложить и найти меньшее значение это величины

                      ExpandedWrap disabled

                        (x0, y0) //ваша точка

                        min = MAX_VALUE; // заведомо очень большое значение

                        (xs, ys) //искомая точка

                        for each ((x, y) in points) {

                            distance = (x0 — x)^2 + (y0 — y)^2;

                            if (distance < min) {

                                min = distance;

                                xs = x;

                                ys = y;

                            }

                        }

                      Ой, спасибо ОООгромное — как все просто и главное работает :)

                      Конечно, для производительности было б неплохо использовать какие-нить расчетные индексы, но это мне нужно теорию почитать

                      Вот для теста можно поюзать. $sx, $sy — заданная точка, $px и $py — искомая.

                      Цитата

                      <?php
                      $sx = 80;
                      $sy = -40;
                      $min = 1000;

                      $px = 0;
                      $py = 0;

                      $points = array(
                      array(0, 0),
                      array(-100, 50),
                      array(90, 100),
                      array(50, 50),
                      array(70, -30),
                      array(10, 10)
                      );

                      foreach ($points as $point) {
                      $d1 = pow(($sx — $point[0]), 2);
                      $d2 = pow(($sy — $point[1]), 2);
                      $distance = $d1 + $d2;
                      if ($distance < $min) {
                      $min = $distance;
                      $px = $point[0];
                      $py = $point[1];
                      }
                      }

                      echo ‘<hr>’ . $px . ‘ : ‘ . $py;
                      ?>
                      ?>

                      -Added 09.01.09, 11:47

                      Цитата OpenGL @ 09.01.09, 10:12

                      Посмотри здесь, в пятой главе то, что нужно.

                      Спасибо за ссылку!,
                      там интересные способы описаны,
                      правда там больше теоретическая часть и мало практических примеров.

                      Почитаю, мож чего пойму :)

                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)

                      0 пользователей:

                      • Предыдущая тема
                      • Алгоритмы
                      • Следующая тема

                      Рейтинг@Mail.ru

                      [ Script execution time: 0,0890 ]   [ 15 queries used ]   [ Generated: 26.05.23, 02:02 GMT ]  

                      Понравилась статья? Поделить с друзьями:
                    • Как составить program
                    • Как найти работу инженеру конструктору
                    • Как найти автора работы по картинке
                    • Экран разделился на 2 части по горизонтали как исправить
                    • Как составить словесный портрет своего друга примеры