Поиск ближайшего соседа в постоянно меняющемся наборе отрезков линии

У меня есть набор отрезков. Я хочу выполнить над ними следующие операции:

  1. Вставьте новый сегмент линии.
  2. Найдите все отрезки прямой в радиусе R от заданной точки.
  3. Найдите все точки в радиусе R1 от данной точки.
  4. Учитывая отрезок прямой, найдите, пересекает ли он какой-либо из существующих отрезков прямой. Точная точка пересечения не требуется (хотя это, вероятно, ничего не упрощает).

Проблема таких алгоритмов, как дерево kd/bd или деревья BSP, заключается в том, что они предполагают статический набор точек, и в моем случае точки будут постоянно дополняться новыми точками, что требует перестройки дерева.

Какая структура данных/алгоритмы наиболее подходят для этой ситуации?

Изменить: принял ответ, который является самым простым и решает мою проблему. Всем спасибо!


person Sid Datta    schedule 26.01.2012    source источник
comment
Для пункта 3: откуда берутся баллы? Являются ли они статичными или как-то определяются сегментами линий?   -  person Erik P.    schedule 26.01.2012
comment
Использование термина «ближайший сосед» в вашем вопросе может ввести в заблуждение, поскольку это не то же самое, что «все точки в пределах заданного расстояния от линии» или «все точки в пределах заданного радиуса от другой точки». Ближайший сосед обычно подразумевает один результат и не приравнивается к «в пределах заданного расстояния от точки до точки или точки до линии». Разница здесь может сделать недействительными некоторые из других надежных ответов, таких как Давидс.   -  person SmacL    schedule 26.01.2012
comment
@ Эрик П. Точки - это концы отрезков. Сегменты линий могут иметь общие конечные точки.   -  person Sid Datta    schedule 26.01.2012
comment
@Shane Позже я понял, что все точки в радиусе - более простая проблема, чем k ближайших соседей. Извинения. Но ответы тем не менее интересны.   -  person Sid Datta    schedule 26.01.2012


Ответы (4)


Одна из возможностей — разделить ваше пространство на сетку блоков — возможно, 10 по оси Y и 10 по оси X, всего 100.

Храните эти блоки в массиве, чтобы очень легко/быстро определить соседние блоки. Каждый блок будет содержать вектор списка сегментов линий, которые находятся в этом блоке.

Когда вы вычисляете сегменты линии в пределах R одного сегмента, вы можете отметить только соответствующие соседние поля.

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

  • обновление позиций сегментов дешево: просто целочисленное деление на размеры прямоугольника в направлениях x и y. Например, если размер коробки равен 20 в обоих направлениях, а ваша новая координата — 145,30. 145/20 == 7 и 30/20 == 1, поэтому он помещается в поле (7,1) для системы на основе 0.
person kfmfe04    schedule 26.01.2012
comment
Размер коробки может быть max(R, R1). Теперь единственной дорогостоящей частью является обновление каждого поля при добавлении сегмента линии. Брезенхэм может помочь с этим. Интересный ответ. - person Sid Datta; 26.01.2012
comment
обновления дешевы - используя целочисленное деление в направлениях x и y (по размеру поля в направлениях x и y), вы можете индексировать непосредственно в нужное поле (не нужно искать) - person kfmfe04; 26.01.2012
comment
@ kfme04, очень сильно зависит от того, насколько регулярны данные с точки зрения интервалов и длины строки. Хорошо работает для данных с достаточно регулярными интервалами. Если у вас много комков с большими промежутками между ними и линиями очень разных размеров, вы можете столкнуться с проблемами. - person SmacL; 26.01.2012

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

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

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

Для вашей конкретной проблемы:

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

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

  • Чтобы найти все точки/линии внутри указанного радиального ореола, вам сначала нужно найти все листья в этой области. Опять же, это можно сделать, пройдя подмножество дерева от корня, который заключен или пересекается с ореолом. Поскольку возможно некоторое перекрытие, вам нужно проверить, что объекты, связанные с этим набором листовых элементов, действительно находятся внутри ореола.

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

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

function insert(x, y)

find the tree leaf element enclosing the new entitiy at (x,y) based on whatever 
fast search algorithm your tree supports

if (number of entities per leaf > max allowable) do

    refine current leaf element (would typically either be a bisection 
    or quadrisection based on a 2D tree type)

    push all entities from the old leaf element list onto the new child element
    lists, based on the enclosing child element

else

    push new entity onto list for leaf element

endif

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

Я несколько раз использовал этот тип подхода с quadtrees/octrees, и на данном этапе я не могу понять, почему аналогичный подход не будет работать с kd-деревьями и т. д.

Надеюсь это поможет.

person Darren Engwirda    schedule 26.01.2012
comment
Как бы вы сбалансировали это дерево? Дерево может стать однобоким (хотя и в худшем случае), и это резко снизит производительность. - person Sid Datta; 26.01.2012
comment
Я полагаю, вы указали мне на R-деревья (en.wikipedia.org/wiki/R-tree< /а>). Спасибо! - person Sid Datta; 27.01.2012
comment
Уровень дисбаланса в дереве зависит от ваших данных. Часто утверждается, что реальные данные достаточно хорошо распределены, что делает поведение в худшем случае маловероятным - я бы сначала попробовал и посмотрел, есть ли у вас проблема, прежде чем беспокоиться об операциях повторной балансировки. Также обратите внимание, что только kd-подобные деревья, которые разделены в соответствии с медианным критерием, могут зависеть от порядка добавления точек и, следовательно, от повторной балансировки. Деревья с фиксированным геометрическим уточнением (quadtrees/octrees и т. д.) дадут вам одинаковый результат независимо от порядка вставки. - person Darren Engwirda; 27.01.2012
comment
Если вам действительно нужна повторная балансировка, вы можете посмотреть на перестроение подмножества дерева, на которое влияет вставка/удаление. Во многих случаях это намного быстрее, чем перестроение всего дерева. - person Darren Engwirda; 27.01.2012
comment
Хорошие моменты. Я рассматриваю возможность создания фиктивных дорожных сетей, поэтому их следует разумно распределять. Спасибо! - person Sid Datta; 27.01.2012
comment
+1 Интересный ответ, может быть интересно посмотреть, будет ли подход на основе TIN или дерева быстрее для этого приложения. Если это дорожные данные с проксимальными точками, представленными по порядку, я бы выбрал ограниченный TIN, поскольку вы также получаете полную модель поверхности, которую можно визуализировать как побочный продукт. - person SmacL; 27.01.2012
comment
@ShaneMacLaughlin: Было бы интересно посмотреть, как сложилось время выполнения - может быть, вам нужен проект кодирования на выходных;). Как правило, мне нравятся возможности быстрого рандомизированного поиска древовидных методов, но, как вы сказали, если данные представлены последовательно, метод обхода треугольника может работать хорошо. Мне также нравятся методы многосеточного треугольника, которые вы упоминаете в другом комментарии. - person Darren Engwirda; 27.01.2012
comment
@ Даррен, я немного предвзято отношусь к TIN, потратив последние 20 лет или около того на разработку программного обеспечения для моделирования поверхностей и очень мало на деревья. Вы можете очень быстро выполнять пространственный поиск по TIN, где время поиска зависит от количества треугольников, пройденных от начальной точки, которая обычно является смежным треугольником, например. O(1) для многих поисковых запросов, которые происходят в аналогичной близости. Накладные расходы выше, ~2 треугольника на точку, 6 индексов (точки+соседи) на треугольник. Случайный поиск будет медленнее, обычно O(sqrt(n)), и зависит от формы данных. - person SmacL; 27.01.2012

В то время как пункты 2 и 3 относительно просты, используя простой линейный поиск с проверкой расстояния при вставке каждой строки, пункт 4 немного сложнее.

Я бы предпочел использовать триангуляцию с ограничениями, чтобы решить эту проблему, где все входные линии рассматриваются как ограничения, а триангуляция уравновешивается с использованием ближайшего соседа, а не критерия Делоне. Это достаточно подробно описано в Триангуляции и приложения Ойвинда Хьелле, Мортена Далена. и Вычислительная геометрия Джозефа О'Руркса на C Обе программы имеют доступный исходный код, включая получение наборов всех пересечений.

Подход, который я использовал для динамического выполнения этого в прошлом, следующий:

  • Создайте произвольную триангуляцию (TIN), состоящую из двух треугольников, окружающих экстенты (текущее + будущее) ваших данных.

  • Для каждой новой строки

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

  • Вырежьте сечение через TIN на основе двух конечных точек, сохраняя список точек, в которых сечение пересекает любую ранее вставленную линию. Добавьте сведения о точке пересечения в список, хранящийся для обеих линий, и вставьте их в TIN.

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

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

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

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

person SmacL    schedule 26.01.2012
comment
+1, это хорошо, хотя поиск охватывающих треугольников путем обхода в целом не обеспечивает быстрого O(log(N)) типа производительности, который вы получаете от запросов к древовидной структуре данных. Тем не менее, это все еще быстро, намного лучше, чем O(N). - person Darren Engwirda; 26.01.2012
comment
@ Даррен, во многом зависит от организации данных. Для случайно организованных данных победят деревья. Многие данные, собранные с помощью съемки и других методов наблюдения, имеют тенденцию представлять точки близко друг к другу, и если вы начнете обход TIN с последней вставленной точки, обход будет чрезвычайно быстрым. то есть на основе количества пересеченных треугольников, которое очень часто равно 0 или 1. Для случайных данных вы можете достичь больших скоростей, используя пирамиды (CDP) для создания TIN с несколькими разрешениями. - person SmacL; 26.01.2012

Вместо вставки и удаления в дереве можно рассчитать кривую, которая полностью заполняет плоскость. Такая кривая уменьшает сложность 2d до сложности 1d, и вы сможете найти ближайшего соседа. Вы можете найти пример, например, кривую z и кривую Гильберта. Вот лучшее описание моей проблемы http://en.wikipedia.org/wiki/Closest_pair_of_points_problem .

person Gigamegs    schedule 26.01.2012
comment
Как вы храните сегменты линии в кривой z? - person Sid Datta; 26.01.2012
comment
@SidDatta: я не думаю, что это сработает, но вы можете мгновенно рассчитать кривую z. Это просто еще один способ увидеть дерево квадрантов. КСТАТИ. Когда вы микрооптимизируете, что само по себе является плохой практикой, то есть избегаете дерева, я не думаю, что есть более быстрый метод, чем мой метод. - person Gigamegs; 26.01.2012
comment
@ Дэвид, интересный ответ, но чтение вопроса предполагает, что Сид на самом деле не ищет ближайших соседей. - person SmacL; 26.01.2012
comment
@ShaneMacLaughlin: я имею в виду эту проблему: en.wikipedia.org/wiki/Closest_pair_of_points_problem - person Gigamegs; 28.01.2012