алгоритм поиска перекрывающихся прямоугольников - PullRequest
11 голосов
/ 11 октября 2011

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

У меня есть еще один прямоугольник A с целочисленными координатами, координаты которого движутся (но вы можете предположить, что его размер постоянен)

Как наиболее эффективно определить, какие прямоугольники пересекаются (или внутри) A? Я не могу просто перебрать свой набор, так как он слишком большой. Спасибо

edit: все прямоугольники параллельны оси

Ответы [ 12 ]

9 голосов
/ 11 октября 2011

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

4 голосов
/ 11 октября 2011

Лично я бы решил это с KD-Tree или BIH-Tree. Они обе являются адаптивными структурами пространственных данных, которые имеют время поиска по log (n). У меня есть реализация обоих для моего Ray Tracer, и они кричат.

- ОБНОВЛЕНИЕ -

Храните все свои фиксированные прямоугольники в KD-Tree. Когда вы тестируете пересечения, выполните итерацию по KD-Tree следующим образом:

function FindRects(KDNode node, Rect searchRect, List<Rect> intersectionRects)

// searchRect is the rectangle you want to test intersections with
// node is the current node. This is a recursive function, so the first call
//    is the root node
// intersectionRects contains the list of rectangles intersected

int axis = node.Axis;

// Only child nodes actually have rects in them
if (node is child)
{
    // Test for intersections with each rectangle the node owns
    for each (Rect nRect in node.Rects)
    {
        if (nRect.Intersects(searchRect))
              intersectionRects.Add(nRect);
    }
}
else
{
    // If the searchRect's boundary extends into the left bi-section of the node
    // we need to search the left sub-tree for intersections
    if (searchRect[axis].Min  // Min would be the Rect.Left if axis == 0, 
                              // Rect.Top if axis == 1
                < node.Plane) // The absolute coordinate of the split plane
    {
        FindRects(node.LeftChild, searchRect, intersectionRects);
    }

    // If the searchRect's boundary extends into the right bi-section of the node
    // we need to search the right sub-tree for intersections
    if (searchRect[axis].Max  // Max would be the Rect.Right if axis == 0
                              // Rect.Bottom if axis == 1
                > node.Plane) // The absolute coordinate of the split plane
    {
        FindRects(node.RightChild, searchRect, intersectionRects);
    }
}

Эта функция должна работать после преобразования из псевдокода, но алгоритм верный. Это алгоритм поиска по журналу (n) и, возможно, самая медленная его реализация (преобразование из рекурсивного в стек).

- ОБНОВЛЕНИЕ - Добавлен простой алгоритм построения дерева KD

Простейшая форма дерева KD, содержащая фигуры области / объема, выглядит следующим образом:

Rect bounds = ...; // Calculate the bounding area of all shapes you want to 
              // store in the tree
int plane = 0; // Start by splitting on the x axis

BuildTree(_root, plane, bounds, insertRects);

function BuildTree(KDNode node, int plane, Rect nodeBds, List<Rect> insertRects)

if (insertRects.size() < THRESHOLD /* Stop splitting when there are less than some
                                      number of rects. Experiment with this, but 3
                                      is usually a decent number */)
{
     AddRectsToNode(node, insertRects);
     node.IsLeaf = true;
     return;
}

float splitPos = nodeBds[plane].Min + (nodeBds[plane].Max - nodeBds[plane].Min) / 2;

// Once you have a split plane calculated, you want to split the insertRects list
// into a list of rectangles that have area left of the split plane, and a list of
// rects that have area to the right of the split plane.
// If a rect overlaps the split plane, add it to both lists
List<Rect> leftRects, rightRects;
FillLists(insertRects, splitPos, plane, leftRects, rightRects); 

Rect leftBds, rightBds; // Split the nodeBds rect into 2 rects along the split plane

KDNode leftChild, rightChild; // Initialize these
// Build out the left sub-tree
BuildTree(leftChild, (plane + 1) % NUM_DIMS, // 2 for a 2d tree
          leftBds, leftRects);
// Build out the right sub-tree
BuildTree(rightChild, (plane + 1) % NUM_DIMS,
          rightBds, rightRects);

node.LeftChild = leftChild;
node.RightChild = rightChild;

Здесь есть куча очевидных оптимизаций, но время сборки обычно не так важно, как время поиска. Тем не менее, хорошо построенное дерево - это то, что делает поиск быстрым. Посмотрите SA-KD-Tree, если вы хотите узнать, как построить быстрое KD-дерево.

2 голосов
/ 11 октября 2011

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

1 голос
/ 07 октября 2012

Деревья интервалов: BST спроектированы с использованием значения 'lo' в качестве ключа в интервале.Так, например, если мы хотим вставить (23, 46) в дерево, мы вставим его, используя '23' в BST.

Кроме того, с деревьями интервалов в каждом узле мы сохраняем максимумконечная точка (значение hi) поддерева с корнем в этом узле.

Этот порядок вставки позволяет нам искать все пересечения 'R' за время R (logN).[Мы ищем первое пересечение во времени logN и все R во времени RlogN] Пожалуйста, обратитесь к документации по деревьям интервалов, чтобы узнать, как выполняется вставка, поиск и детали сложности.

Теперь для этой проблемы мы используем известный алгоритмкак алгоритм стреловидности.Представьте, что у нас есть вертикальная линия (параллельная оси Y), которая охватывает пространство 2D и в этом процессе пересекается с прямоугольниками.

1) Расположите прямоугольники в порядке возрастания x-cordinates (по левому краю)) либо через приоритетную очередь, либо через сортировку.Сложность NlogN, если N прямоугольников.

2) Поскольку эта линия проходит слева направо, следующие случаи пересечения:

  • Если линия пересекает левую сторону прямоугольниканикогда не видели, добавьте координаты y стороны прямоугольника к дереву интервалов.[скажем, (x1, y1) и (x1, y2) - координаты левого края прямоугольника, который добавляет интервал (y1, y2) к дереву интервалов] ---> (NlogN) * ​​1015 *

  • Выполнить поиск диапазона в дереве интервалов.[скажем (x1, y1) и (x1, y2) являются координатами левого края прямоугольника, берут интервал (y1, y2) и выполняют запрос на пересечение интервалов в дереве, чтобы найти все пересечения] ---> RlogN(на практике)

  • Если линия пересекает правую сторону прямоугольника, удалите ее y-координаты из дерева интервалов, поскольку прямоугольник теперь полностью обрабатывается.----> NlogN

Общая сложность: NlogN + RlogN

1 голос
/ 24 сентября 2012

Topcoder предоставляет способ определить, находится ли точка внутри прямоугольника.Это говорит о том, что у нас есть точка x1, y1 и прямоугольник.Мы должны выбрать случайную точку очень далеко от текущего местоположения в прямоугольной системе координат, скажем, x2, y2.

Теперь мы должны сделать отрезок с точками x1, y1 и x2, y2.Если этот отрезок линии пересекает нечетное число сторон данного прямоугольника (в нашем случае это будет 1, этот метод можно распространить и на общие многоугольники), то точка x1, y1 находится внутри прямоугольника, и если она пересекает четное числоиз сторон он лежит за пределами прямоугольника.

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

1 голос
/ 15 октября 2011

Поскольку они не перекрываются, я бы предложил подход, аналогичный (но не равный) Джейсону Муру (B). Сортировать ваш массив по х левого верхнего угла. И отсортировать копию по y левого верхнего угла. (конечно, вы бы просто сортировали указатели на них, чтобы сэкономить память).

Теперь вы однажды создаете два набора Sliding_Window_X и Sliding_Window_Y.

Вы выполняете бинарный поиск, как только ваша x-координата (вверху слева) для вашего окна A в массиве x-sort и вашей y-координаты. Вы помещаете свои результаты в corrospondng Sliding_Window_Set. Теперь вы добавляете все следующие прямоугольники в упорядоченный массив, которые имеют более низкую координату x (y) (на этот раз справа внизу), чем нижний правый угол A.

В результате у вас в Sliding_Window устанавливаются окна, которые перекрываются с вашей A по одной координате. Перекрытие A - это пересечение Sliding_Window_X и _Y.

Наборы Sliding_Window могут быть легко представлены всего двумя числами (начальный и конечный индексы сопоставленного отсортированного массива).

Поскольку вы говорите, что ходите A, теперь действительно легко пересчитать перекрытие. В зависимости от направления вы можете теперь добавлять / удалять элементы в набор Sliding_Window. То есть вы берете только следующий элемент из отсортированного массива в начале / конце набора и, возможно, удаляете в конце.

1 голос
/ 11 октября 2011

Создайте матрицу, содержащую элементы "квадранта", где каждый квадрант представляет пространство N * M в вашей системе, где N и M - ширина и высота самого широкого и самого высокого прямоугольников соответственно. Каждый прямоугольник будет помещен в элемент квадранта на основе его верхнего левого угла (таким образом, каждый прямоугольник будет находиться в одном квадранте). Для заданного прямоугольника A проверьте наличие столкновений между прямоугольниками в собственном квадранте A и 8 смежных квадрантах.

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

1 голос
/ 11 октября 2011

Вы можете сделать случайный алгоритм "прогулки" ... в основном, создать список соседей для всех ваших прямоугольников фиксированной позиции.Затем случайным образом выберите один из прямоугольников с фиксированным положением и проверьте, где находится целевой прямоугольник по сравнению с текущим прямоугольником с фиксированным положением.Если он не находится внутри прямоугольника, который вы случайно выбрали в качестве начальной точки, то он будет находиться в одном из восьми направлений, которые соответствуют данному соседу вашего текущего прямоугольника с фиксированной позицией (т. Е. Для любого данного прямоугольника в прямоугольнике будетN, NE, E, SE, S, SW, W, NW направления).Выберите соседний прямоугольник в ближайшем заданном направлении к целевому прямоугольнику и повторите тест.По сути, это рандомизированный алгоритм инкрементного построения, и его производительность, как правило, очень хороша для геометрических задач (обычно логарифмическая для отдельной итерации и O (n log n) для повторных итераций).

0 голосов
/ 15 октября 2011

Используйте дерево R +, которое, скорее всего, именно та структура дерева, которую вы ищете. Деревья R + явно не допускают перекрытия во внутренней (неконечной) структуре в обмен на скорость. Пока объект не существует в нескольких листьях одновременно, перекрытия нет. В вашей реализации, вместо поддержки перекрытия, всякий раз, когда объект должен быть добавлен к нескольким листьям, просто вместо этого возвращайте true.

Вот подробное описание структуры данных, включая способы управления прямоугольниками: Дерево R +: динамический индекс для многомерных объектов

0 голосов
/ 15 октября 2011

Метод (A)

Вы можете использовать дерево интервалов или дерево сегментов .Если деревья были созданы так, чтобы они были сбалансированы, это дало бы вам время выполнения O (log n).Я предполагаю, что этот тип предварительной обработки является практичным, потому что это произойдет только один раз (кажется, что вы больше беспокоитесь о времени выполнения, когда прямоугольник начинает двигаться, чем о величине начальной предварительной обработки в первый раз).Объем пространства будет O (n) или O (n log n) в зависимости от вашего выбора выше.

Метод (B)

Учитывая, что ваш большой наборПрямоугольники имеют фиксированный размер и никогда не меняют свои координаты и что они не перекрываются, вы можете попробовать несколько иной стиль алгоритма / эвристики, чем предложенный здесь другими (при условии, что вы можете жить с единовременной платой за предварительную обработку).

Алгоритм предварительной обработки [O (n log n) или O (n ^ 2) время выполнения {выполняется только один раз}, O (n) пробел]

  1. Сортировка прямоугольников поих горизонтальные координаты, используя ваш любимый алгоритм сортировки (я предполагаю, O (n log n) время выполнения).
  2. Сортируйте прямоугольники по их вертикальным координатам, используя ваш любимый алгоритм сортировки (я предполагаю O (n log n)время выполнения)
  3. Вычислить функцию распределения вероятностей и совокупную функцию распределения горизонтальных координат.(Время выполнения от O (1) до O (n ^ 2) в зависимости от используемого метода и типа распределения ваших данных)

    a) Если горизонтальные координаты ваших прямоугольников следуют некоторому естественному процессу, то вы, вероятно, можетеоцените их функцию распределения, используя известное распределение (например: нормальное , экспоненциальное , равномерное и т. д.).

    b) Если вашГоризонтальные координаты прямоугольников не соответствуют известному распределению, поэтому вы можете рассчитать пользовательское / оценочное распределение, создав гистограмму .

  4. Вычислить распределение вероятностейфункция и совокупная функция распределения вертикальных координат.

    a) Если вертикальные координаты ваших прямоугольников следуют некоторому естественному процессу, то вы, вероятно, можете оценить их функцию распределения, используяизвестное распределение (например: нормальное , экспоненциальное , равномерное и т. д.).

    b) Если ваш прямойВертикальные координаты gles не соответствуют известному распределению, поэтому вы можете рассчитать пользовательское / оценочное распределение, создав гистограмму .

Алгоритм поиска пересечений в реальном времени [В любом месте от O (1) до O (log n) - O (n) {примечание: если O (n), то постоянная перед n будет очень мала} время выполнения в зависимости от того, насколько хорошо функции распределения соответствуют набору данных]

  1. Взяв горизонтальную координату вашего движущегося прямоугольника и вставьте его в функцию накопленной плотности для горизонтальных координат множества прямоугольников.Это выведет вероятность (значение между 0 и 1).Умножьте это значение на число n (где n - количество имеющихся у вас прямоугольников).Это значение будет индексом массива для проверки в отсортированном списке прямоугольников.Если прямоугольник этого индекса массива пересекается, то все готово и вы можете перейти к следующему шагу.В противном случае вам придется сканировать окружающих соседей, чтобы определить, пересекаются ли соседи с движущимся прямоугольником.Вы можете атаковать эту часть проблемы несколькими способами:

    a) выполнять линейное сканирование, пока не найдете пересекающийся прямоугольник или не найдете прямоугольник на другой стороне движущегося прямоугольника

    b) вычислите доверительный интервал , используя функции плотности вероятности, которые вы рассчитали, чтобы дать вам наилучшее предположение о потенциальных границах (то есть интервал, в котором должно находиться пересечение).Затем выполните бинарный поиск на этом небольшом интервале.Если двоичный поиск не удался, вернитесь к линейному поиску в части (а).

  2. Сделайте то же самое, что и в шаге 1, но сделайте это для вертикальных частей, а не для горизонтальных частей.

  3. Если на шаге 1 получено пересечение, а на шаге 2 - пересечение, а пересекающийся прямоугольник на шаге 1 был тем же прямоугольником, что и на шаге 2, то этот прямоугольник должен пересекаться с движущимся прямоугольником.,В противном случае пересечения нет.

...