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

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

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

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

Ответ 1

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

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

Ответ 2

Создайте 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

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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

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

Три вещи spring, которые могут ускорить это:

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

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

вместо

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

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

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

Я использовал код Андреаса, но он содержит ошибку. Например, у меня было две точки на плоскости [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);
}
}

Различие заключается в том, что Andreas проверяет теперь детей только с (если корень → больше), который не является полным. Я, с другой стороны, не делаю эту проверку, я просто проверяю, является ли корень действительным. Дайте мне знать, если найдете ошибку.