При поиске количества точек внутри некоторой фигуры с помощью KD-Tree нужно ли нам проверять пересечение областей или просто сравнивать свойства, соответствующие глубине?

Допустим, нам нужно найти количество точек внутри некоторой фигуры в 2D-плоскости.

Для решения этой проблемы мы можем использовать KD-Tree. KD-дерево разделит 2D-плоскость на прямоугольники, а затем мы сможем проверить пересечения границ некоторых узлов (которые выглядят как прямоугольник) и формы, внутри которой мы хотим подсчитать точки.

Это кажется логичным. Я написал код для решения подобной проблемы, где моя форма — круг.

static bool is_point_inside_circle(int x, int y, int cx, int cy, int cr) {
    return (x - cx) * (x - cx) + (y - cy) * (y - cy) <= cr * cr;
}

struct RectBoundary {
    int top;
    int right;
    int bottom;
    int left;

    RectBoundary() : top(500), right(500), bottom(-500), left(-500) { }
    RectBoundary(int t, int r, int b, int l) : top(t), right(r), bottom(b), left(l) { }

    bool is_inside_circle(int cx, int cy, int cr) const {
        return is_point_inside_circle(left, top, cx, cy, cr)
            && is_point_inside_circle(right, top, cx, cy, cr)
            && is_point_inside_circle(right, bottom, cx, cy, cr)
            && is_point_inside_circle(left, bottom, cx, cy, cr);
    }

    bool intersects_with_circle(int cx, int cy, int cr) const {
        return cx - cr <= left
            || cx + cr >= right
            || cy - cr <= bottom
            || cy + cr >= top
            || (cx >= left && cx <= right && cy <= top && cy >= bottom);
    }
};

struct KDNode {
    int x, y;
    int depth;
    RectBoundary boundary;

    KDNode* left = nullptr;
    KDNode* right = nullptr;

    bool is_leaf() {
        return left == nullptr && right == nullptr;
    }
};

KDNode* build(vector<vector<int>>& points, int l, int r, int depth) {
    if (l > r)
        return nullptr;

    if (depth % 2 == 0) {
        sort(points.begin() + l, points.begin() + r + 1);
    }
    else {
        sort(points.begin() + l, points.begin() + r + 1, [](const vector<int>& p1, const vector<int>& p2)
            {
                return p1[1] < p2[1];
            });
    }

    const int mid = (l + r) / 2;

    KDNode* node = new KDNode();
    node->depth = depth;
    node->x = points[mid][0];
    node->y = points[mid][1];

    node->left = build(points, l, mid - 1, depth + 1);
    node->right = build(points, mid + 1, r, depth + 1);

    return node;
}

void set_boundaries(KDNode* node, int depth) {
    if (node == nullptr)
        return;

    if (depth % 2 == 0) {
        if (node->left) {
            node->left->boundary = node->boundary;
            node->left->boundary.right = node->x;
        }

        if (node->right) {
            node->right->boundary = node->boundary;
            node->right->boundary.left = node->x;
        }
    }
    else {
        if (node->left) {
            node->left->boundary = node->boundary;
            node->left->boundary.top = node->y;
        }

        if (node->right) {
            node->right->boundary = node->boundary;
            node->right->boundary.bottom = node->y;
        }
    }

    set_boundaries(node->left, depth + 1);
    set_boundaries(node->right, depth + 1);
}

int traverse(KDNode* node) {
    if (node == nullptr)
        return 0;

    return 1 + traverse(node->left) + traverse(node->right);
}

int count_points_inside_circle(KDNode* node, int cx, int cy, int cr) {
    if (node->is_leaf()) {
        return is_point_inside_circle(node->x, node->y, cx, cy, cr);
    }

    int count = is_point_inside_circle(node->x, node->y, cx, cy, cr);

    if (node->left) {
        if (node->boundary.is_inside_circle(cx, cy, cr))
            count += traverse(node->left);
        else if (node->boundary.intersects_with_circle(cx, cy, cr))
            count += count_points_inside_circle(node->left, cx, cy, cr);
    }

    if (node->right) {
        if (node->boundary.is_inside_circle(cx, cy, cr))
            count += traverse(node->right);
        else if (node->boundary.intersects_with_circle(cx, cy, cr))
            count += count_points_inside_circle(node->right, cx, cy, cr);
    }

    return count;
}

vector<int> countPoints(vector<vector<int>>& points, vector<vector<int>>& queries) {
    KDNode* root = build(points, 0, points.size() - 1, 0);
    set_boundaries(root, 0);

    vector<int> result(queries.size());
    for (int i = 0; i < queries.size(); ++i)
        result[i] = count_points_inside_circle(root, queries[i][0], queries[i][1], queries[i][2]);

    delete root;
    return result;
}

Написав это, я понял, что мой intersects_with_circle, скорее всего, неправильный, поскольку предполагает фиксированное относительное положение прямоугольника и круга. Я до сих пор не могу найти подходящую формулу для проверки пересечения круга и прямоугольника.

Но что мне пришло в голову: действительно ли нам нужно сравнивать всю площадь фигур?

Я имею в виду, что если взять прямоугольник и любую форму, что если мы сравним только те свойства формы, которые подходят для данной глубины KD-дерева? Допустим, у нас есть 2D-плоскость и текущая глубина 6. Равномерно, поэтому воспользуемся свойством x. Можем ли мы просто получить среднюю точку фигуры, внутри которой мы хотим посчитать точки, проверить ориентацию x относительно этой средней точки с помощью векторного произведения, а затем получить самую левую или самую правую точку фигуры (зависит от ориентации) и проверить, есть ли это свойство «самой точки» x меньше или больше (также зависит от ориентации), чем x нашего прямоугольника?

Может ли это сработать? Можем ли мы в данный момент выделить один объект вместо сравнения целых территорий? Я могу себе представить, что заданное свойство «самая точка» x данной формы может быть недействительным и без проверки y, но тогда мы в конечном итоге проверим также свойство y и, при необходимости, отклоним некоторую конкретную область (еще один дополнительный шаг, от которого мы могли бы избавиться ранее), поскольку свойства чередуются в зависимости от глубины KD-дерева.

Я не уверен, правильно ли я понимаю KD-Tree, и возможно, этот вопрос не имеет никакого смысла. Простите за это

Ваш код немного сбивает с толку. Для чего предназначен traverse()? Для чего он использует cx,cy,cr?

Sneftel 01.07.2024 15:15

@Sneftel, извини, я забыл удалить ненужные аргументы. Функция traverse() рекурсивно подсчитывает количество точек в KD-дереве, начиная с заданного KDNode. Он используется, когда мы знаем, что наш прямоугольник полностью находится внутри круга, поэтому мы можем посчитать каждое очко в нашем ответе. Обычно, когда я использую cx, cy,cr, это означает круг X, круг Y, радиус круга, где X и Y — средняя точка круга.

Szyszka947 01.07.2024 15:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы, конечно, можете, и в целом так и делаете.

Обычный способ запроса всех точек внутри заданной фигуры — использовать kD-дерево в качестве широкой фазы для тестирования AABB, за которым следует узкофазная проверка самой формы.

Итак, первое, что вам нужно сделать, это определить размеры прямоугольника, содержащего фигуру. В случае с кругом это, очевидно, cx-cr < x < cx+cr и cy-cr < y < cy+cr. На четном уровне (x-сравнения) нет необходимости проверять экстенты y, так что есть ваше «свойство, соответствующее глубине». Конечно, сама узловая точка требует полного сравнения; к счастью, вам нужно это сделать только в том случае, если обе ветви пройдены. Так что это что-то вроде

// even level
bool leftPasses = node.x > xmin;
bool rightPasses = node.x < xmax;

if (leftPasses && rightPasses && narrowPhase(node.x, node.y)) count++;
if (leftPasses) recurse(node.left);
if (rightPasses) recurse(node.right);

Обратите внимание, что я ничего не делал с границами узлов. Для чего-то подобного нет смысла хранить их: если вы выполняете только обход сверху вниз, вы можете отслеживать их по ходу дела. Также обратите внимание на отсутствие стратегии «проверить наличие и подсчитать все поддерево», которая бесполезна с точки зрения асимптотической производительности.

Поэтому я просто прохожу KDTree и перехожу к левому или правому дочернему элементу, не сравнивая целые области, и в каждом узле KDTree проверяю, является ли он частью круга. Если да, то я просто увеличиваю свой счетчик на 1. Кроме того, в каждом узле мне нужно просто сохранить одну переменную, которая укажет мне точку разреза X или Y (я решу, какую именно, в зависимости от глубины). Таким образом, мне никогда не придется сравнивать площади всей фигуры? Кажется слишком простым... Это правда?

Szyszka947 01.07.2024 20:08

Да, это правильно. Хотя вам нужно хранить как X, так и Y на каждом узле, поскольку это именно тот момент, который вы проверяете.

Sneftel 01.07.2024 22:33

Другие вопросы по теме