Посчитать количество точек внутри круга быстро
учитывая набор из 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), который не является полным. Я, с другой стороны, не делаю эту проверку, я просто проверяю, действителен ли корень. Дай мне знать, если найдешь жучок.