Посчитать количество точек внутри круга быстро

учитывая набор из n точек на плоскости, я хочу предварительно обработать эти точки как-то быстрее, чем o(n^2) (O(nlog(n)) предпочтительно), а затем иметь возможность ответить на запросы следующего рода: "сколько из n точек лежат внутри круга с заданным центром и радиусом?"быстрее, чем O(n) (O(log(n) предпочтительно).

можете ли вы предложить некоторую структуру данных или алгоритм я могу использовать для этой проблемы?

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

6 ответов


создайте пространственную структуру подразделения, такую как дерева квадрантов или KD-tree точек. На каждом узле храните количество точек, охватываемых этим узлом. Затем, когда вам нужно подсчитать точки, покрытые кругом поиска, пересечь дерево и для каждого подразделения в узле проверить, полностью ли оно находится вне круга, затем игнорировать его, если оно полностью внутри круга, затем добавить его количество к общему, если оно пересекается с кругом, рекурсивно, когда вы доберетесь до круга. лист, проверьте точку(ы) внутри листа для удержания.

Это все еще o(n) худший случай (например, если все точки лежат на периметре круга), но средний случай-O(log(n)).


построить KD-tree точек, это должно дать вам гораздо лучшую сложность, чем O(n), в среднем O(log(n)) я думаю.

вы можете использовать 2D-дерево, так как точки ограничены плоскостью.

предполагая, что мы преобразовали проблему в 2D, у нас будет что-то вроде этого для точек:

 struct Node {
     Pos2 point;
     enum {
        X,
        Y
     } splitaxis;
     Node* greater;
     Node* less;
 };

greater и less содержит точки с большими и меньшими координатами соответственно вдоль сплитаксис.

 void
 findPoints(Node* node, std::vector<Pos2>& result, const Pos2& origin, float radius) {
     if (squareDist(origin - node->point) < radius * radius) {
         result.push_back(node->point);
     }
     if (!node->greater) { //No children
          return;
     }
     if (node->splitaxis == X) {
         if (node->point.x - origin.x > radius) {
             findPoints(node->greater, result, origin radius);
             return;
         }
         if (node->point.x - origin.x < -radius) {
             findPoints(node->less, result, origin radius);
             return;
         }
         findPoints(node->greater, result, origin radius);
         findPoints(node->less, result, origin radius);
     } else {
         //Same for Y
     }
 }

затем вы вызываете эту функцию с корнем KD-дерево


Если моя цель-скорость, а количество очков не было огромным (миллионы), я бы сосредоточился на объеме памяти столько же, сколько на алгоритмической сложности.

несбалансированное дерево k-d лучше всего на бумаге, но оно требует указателей, которые могут расширить объем памяти на 3x+, поэтому оно отсутствует.

сбалансированное дерево k-d не требует хранения, кроме как для массива с одним скаляром для каждой точки. Но у него тоже есть недостаток: скаляры нельзя квантовать - они должны быть такими же 32-битными поплавками, как в исходных пунктах. Если они квантованы, больше невозможно гарантировать, что точка, которая появляется раньше в массиве, находится либо на плоскости расщепления, либо слева от нее, и что точка, которая появляется позже в массиве, находится либо на плоскости расщепления, либо справа от нее.

существует структура данных, которую я разработал, которая решает эту проблему. The синергетика люди говорят нам, что объем эмпирически четырехскатные. Предположим, что самолет аналогичен эмпирически трех направлениях.

мы привыкли пересекать плоскость по четырем направлениям-x, +x, - y и +y, но проще использовать три направления a, b и c, которые указывают на вершины равностороннего треугольника.

при построении сбалансированного дерева k-d проецируйте каждую точку на оси a, b и C. Отсортировать точки по возрастанию а. Для медианы, округлить, квантования и хранить. Затем, для суб-массивы слева и справа от медианы, Сортировать по возрастанию параметра B, а для срединной точки, округление, квантования и магазинов. Повторить и повторять до каждой точки хранится значение.

затем, при тестировании круга (или что-то еще) против структуры, сначала вычислите максимум а, B и C координаты круга. Это описывает треугольник. В структуре данных, которую мы сделали в последнем абзаце, сравните координату медианной точки с максимальной координатой окружности. Если точка a больше, чем круг а, мы можем дисквалифицировать все точки после медианы. Затем для суб-массивов слева и справа (если они не дисквалифицированы) медианы сравните окружность b с координатой B медианной точки. Повторяйте и повторяйте, пока не будет больше точек для посещения.

Это похоже на тему структура данных BIH, но не требует интервалов-x и +x и-y и +y, потому что a, b и c так же хорошо пересекают плоскость и требуют одного меньше направление сделать это.


предполагая, что у вас есть набор точек S в декартовой плоскости с координатами (xЯ, yЯ), учитывая произвольный круг с центром (xc, yc) и радиус r вы хотите найти все точки, содержащиеся в этом круге.

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

три вещи приходят на ум, что может скорость это:

во-первых, вы можете увидеть:

(xi-xc)^2 + (yi-yc)^2 <= r^2

вместо

sqrt((xi-xc)^2 + (yi-yc)^2) <= r

во-вторых, вы можете отбраковать список точек, помня, что точка может быть только внутри круга, если:

  • xЯ находится в диапазоне [xc - r, xc+r]; и
  • yЯ находится в диапазоне [yc - r, yc+r]; и

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

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


в зависимости от того, сколько времени у вас есть, вы можете построить такое дерево:

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

когда вы хотите вычислить точки в x, y,r: пройдите через дерево и спуститесь по ветке, которая соответствует вашим значениям x, y ближе всего. когда вы спуститесь до корневого уровня, вам нужно сделать небольшое совпадение (постоянное время), но вы может найти радиус такой, что все точки в этом круге (определенные путем в дереве) находятся внутри круга,указанного x,y, r,и другой круг (тот же x_tree, y_tree в дереве,как и раньше,но другой r_tree), так что все точки в исходном круге (заданные x, y, r) находятся в этом круге.

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

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


я использовал код Андреаса, но он содержит ошибку. Например у меня было две точки на плоскости [13, 2], [13, -1] и моя точка происхождения [0, 0] с радиусом 100. Он находит только 1 очко. Это мое исправление:

void findPoints(Node * root, vector<Node*> & result, Node * origin, double radius, int currAxis = 0) {
if (root) {
    if (pow((root->coords[0] - origin->coords[0]), 2.0) + pow((root->coords[1] - origin->coords[1]), 2.0) < radius * radius) {
        result.push_back(root);
    }

    if (root->coords[currAxis] - origin->coords[currAxis] > radius) {
        findPoints(root->right, result, origin, radius, (currAxis + 1) % 2);
        return;
    }
    if (origin->coords[currAxis] - root->coords[currAxis] > radius) {
        findPoints(root->left, result, origin, radius, (currAxis + 1) % 2);
        return;
    }
    findPoints(root->right, result, origin, radius, (currAxis + 1) % 2);
    findPoints(root->left, result, origin, radius, (currAxis + 1) % 2);
}
}

разница в том, что Андреас проверил сейчас детей только с if (!root - >greater), который не является полным. Я, с другой стороны, не делаю эту проверку, я просто проверяю, действителен ли корень. Дай мне знать, если найдешь жучок.