Есть ли эффективный алгоритм для "верхнего корпуса"? - PullRequest
0 голосов
/ 06 марта 2019

например, когда точки заданы в виде (x-left, x-right, y) в координатах x, y, (1, 5, 3), (2, 4, 5), возвращается (1,2, 3), (2, 4, 5), (4,5,3)

Ответы [ 3 ]

4 голосов
/ 06 марта 2019

Простой жадный алгоритм решает это аккуратно.Сортируйте ваши сегменты по y-координате по убыванию;ча ;;этот список seg.Теперь ...

top_hull = [empty set]
while seg is not empty
    head = seg.pop()    // pop off the highest segment.
    top_hull += head
    for each segment in seg
        remove the interval (head.xleft, head.y-left) from segment

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

`head`'s interval does not overlap `segment`
    no action
`head`'s interval contains `segment`
    remove segment
`segments`'s interval contains `head` (both ends stick out
    split segment into the two remaining pieces.
`head`'s interval overlaps one end of `segment`
    truncate segment

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

2 голосов
/ 07 марта 2019

Самый простой эффективный (O (N log N)) способ решить эту проблему - использовать алгоритм "развертки строки".

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

Обратите внимание, что эти важные события могут происходить только в тех положениях х, где начинается или заканчивается один из входных сегментов. Поэтому вместо плавного перемещения линии нам нужно только рассмотреть, что происходит в этих положениях x. Итак:

  1. Создание списка всех начальных и конечных событий для сегментов. Например, для сегмента {1, 2, 5} будут генерироваться события {1, начало 5}, {2, конец 5}
  2. Создайте максимальную кучу для отслеживания самого верхнего сегмента.
  3. Сортировка всех событий по позиции x и обработка их по порядку. Для событий с одинаковой позицией x сначала выполните стартовые события. Это гарантирует, что вы обрабатываете событие начала каждого сегмента до его конечного события.
  4. Для каждой позиции x в списке обработайте все события с этой позицией x как единое целое. Для каждого события запуска {x, start y} добавьте y в кучу. Для каждого события {x, end y} удалите y из кучи.
  5. Когда вы закончите обработку событий для позиции x, самый большой элемент в куче - это позиция y верхнего корпуса до следующей позиции x в списке.
2 голосов
/ 07 марта 2019

Ответ на чернослив имеет правильную идею, но я чувствую, что нет смысла объяснять, как проверить перекрытие интервалов.Фактически, эта часть алгоритма выполняется за квадратичное время O(n^2), поскольку в какой-то момент она формирует все пары n^2, что не является необходимым.Что бы я сделал -

Очередь

Сначала создайте max-heap из вашего списка сегментов с координатой y в качестве ключа.Вы можете извлечь и удалить максимум за O(logn) время, так что это похоже на ту же сложность времени, что и сортировка, только со встроенным сованием.

heap = max_heap(segement_list)
output = []
while heap is not empty
    segment = heap.pop() # max / highest

    # trim / split segment
    # append trimmed segment(s)

Пересечения

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

Ради того, чтобы следующие абзацы были менее раздутыми с техническими подробностямиПозволяет просто получить подробности реализации двух ключевых сравнений.Предположим, что сегмент a имеет меньшее значение lower_x, чем b (потому что в следующем параграфе мы всегда будем знать, что меньше).

# boolean- do `a` and `b` intersect
function intersects(a, b)
    return a.upper_x >= b.lower_x

# boolean- is `b` a subsegment of `a`
function is_subsegment(a, b)
    return a.upper_x >= b.upper_x

Нам также понадобятся три преобразования,используя те же a и b definition-

function merge(a, b)
    a.upper_x = b.upper_x

function trim_left(a, b)
    a.upper_x = b.lower_x

function trim_right(a, b)
    b.lower_x = a.upper_x

Возвращаясь к идее запроса BST-дубля left_segment, полученного при запросе для нашего сегмента segment, и посмотрите, пересекаются ли они.Если они пересекаются, проверьте, если segment is_subsegment из left_segment.Если это так, прервите и continue перейдите к следующему сегменту в куче.В противном случае, если они пересекаются, нам нужно будет trim_right segment.Независимо от пересечения или нет, мы будем обрабатывать любые правосторонние пересечения.После этого мы можем merge модифицированные segment (на самом деле subsegment, как вы увидите) с left_segment, если они перекрываются.

left_segment - единственный сегмент, который может перекрываться слева, потому что мы объединяем все перекрывающиеся сегменты, когда они вставляются в BST.Однако, это не относится к правой стороне, так как наш segment еще должен быть обрезан справа.Нам нужно обрабатывать обрезку постепенно.

Примите right_segment как следующий сегмент после left_segment в дереве (обход в порядке).Сделайте копию segment под названием subsegment.Если subsegment пересекает right_segment, обрежьте его, вставьте subsegment в выходной массив, объедините два сегмента и удалите right_segment из BST.В противном случае просто вставьте subsegment в массив.Теперь мы можем объединить subsegment с left_segment, если они перекрываются.Если они этого не сделали, вставьте subsegment в BST и присвойте ему переменную left_segment.

Теперь мы повторяем этот процесс, пока не выйдем из segment, являющегося is_subsegment из left_segment.После этого мы повторяем весь процесс со следующим сегментом из кучи.

Анализ

Мы знаем, что формируем нашу максимальную кучу и выталкиваем максимум n разприведет к O(nlogn) сложности времени.Сложная задача - выяснить сложность времени для обработки пересечений.Заметьте, что для каждого сегмента, который мы обрабатываем, после того, как все subsegment обработаны и объединены, мы увеличим размер нашего BST не более чем на единицу.Это потому, что все наши subsegment объединяются вместе на каждой итерации, поэтому они в конечном итоге образуют один большой сегмент.Это означает, что наш BST не больше n, поэтому запрос и удаление / вставка в BST занимает O(logn) время.

Также обратите внимание, что для каждого сегмента, вставленного в BST, он будет пересекать только один сегмент только один раз, потому что, когда это произойдет, два (или более) будут объединены в один новый сегмент. Исключением является случай, когда сегмент является подсегментом left_segment, но в этом случае мы прекращаем работу, не создавая новых сегментов в любом случае, поэтому его +0 чистое изменение размера в любом случае. Используя эти знания в сочетании с предварительным наблюдением, что каждый сегмент в конечном итоге вносит не более одного нового сегмента в BST, мы можем заключить, что будет не более O(n) пересечений и, следовательно, вставок / удалений. Так что O(nlogn) время, чтобы поддерживать наш BST.

Учитывая, что остальные операции выполняются с постоянным временем, наша общая сложность по времени составляет O(nlogn) вместо O(n^2) при грубом форсировании для пересечений и обрезки.

...