Ответ 1
Вы можете попробовать Bentley-Ottmann со следующим псевдокодом из Эта страница
Алгоритм Бентли-Оттмана
Вход для алгоритма Бентли-Оттмана представляет собой набор OMEGA = {Li} отрезков Ли, а его выход будет множеством LAMBDA = {Ij} точек пересечения. Этот алгоритм называется "алгоритмом линии развертки", потому что его можно визуализировать как имеющую другую линию, "линию развертки" SL, подметающую коллекцию OMEGA и собирающую информацию, когда она проходит по отдельным сегментам Li. Информация, собранная для каждой позиции SL, представляет собой упорядоченный список всех сегментов OMEGA, которые в настоящее время пересекаются SL. Структура данных, поддерживающая эту информацию, часто также называется "строкой развертки". Эта структура классов также обнаруживает и выводит пересечения, когда она обнаруживает их. Процесс, посредством которого он обнаруживает пересечения, является сердцем алгоритма и его эффективности.
Чтобы реализовать логику развертки, мы должны сначала линейно упорядочить сегменты OMEGA, чтобы определить последовательность, в которой SL сталкивается с ними. То есть нам нужно упорядочить конечные точки {Ei0, Ei1} я = 1, n всех сегментов Li, чтобы мы могли обнаружить, когда SL начинается и останавливается, пересекая каждый сегмент OMEGA. Традиционно конечные точки упорядочиваются путем увеличения x сначала, а затем увеличения значений y-координат, но любой линейный порядок будет выполняться (некоторые авторы предпочитают сначала уменьшать y, а затем увеличивать x). При традиционном порядке линия развертки вертикальна и перемещается слева направо, когда она встречает каждый сегмент, как показано на диаграмме:
Pic-sweepline
В любой точке алгоритма линия развертки SL пересекает только те сегменты с одной конечной точкой слева от (или на) ее и другой конечной точкой справа от нее. Структура данных SL хранит динамический список этих сегментов: (1) добавление сегмента, когда встречается его крайняя левая конечная точка, и (2) удаление сегмента, когда встречается его самая правая конечная точка. Далее, SL упорядочивает список сегментов с отношением "выше-ниже". Таким образом, чтобы добавить или удалить сегмент, необходимо определить его позицию в списке, что может быть выполнено с помощью двоичного поиска O (log-n) в верхнем регистре текущих сегментов в списке. Кроме того, помимо добавления или удаления сегментов, существует другое событие, которое изменяет структуру списка; а именно, когда два сегмента пересекаются, то их позиции в упорядоченном списке должны быть заменены. Учитывая два сегмента, которые должны быть соседями в списке, этот обмен является операцией O (log-n).
Чтобы организовать все это, алгоритм поддерживает упорядоченную "очередь событий" EQ, элементы которой вызывают изменение в списке сегментов SL. Изначально EQ устанавливается в список упорядоченных списков всех конечных точек сегмента. Но поскольку пересечения между сегментами найдены, то они также добавляются в EQ в том же порядке развертки, что и для конечных точек. Однако нужно проверить, чтобы избежать вставки повторяющихся пересечений в очередь событий. Пример на приведенной выше диаграмме показывает, как это может произойти. На этапе 2 сегменты S1 и S2 вызывают пересечение I12, которое должно быть вычислено и помещено в очередь. Затем, на этапе 3, сегмент S3 находится между и разделяет S1 и S2. Затем, на событии 4, S1 и S3 меняются местами на линии развертки, а S1 снова приближается к S2, заставляя I12 снова вычисляться. Но для каждого пересечения может быть только одно событие, а I12 не может быть помещено в очередь дважды. Итак, когда пересечение помещается в очередь, мы должны найти его потенциальное x-отсортированное местоположение в очереди и проверить, что он еще не существует. Так как для любых двух сегментов имеется не более одной точки пересечения, то для однозначной идентификации достаточно обозначить пересечение с идентификаторами для отрезков. В результате всего этого максимальный размер очереди событий = 2n + k.le.2n + n2, и любая вставка или удаление может быть выполнена с помощью O (log (2n + n2)) = O (log-n ) двоичный поиск.
Но что все это связано с эффективным поиском полного набора пересечений сегментов? Ну, поскольку сегменты последовательно добавляются в список сегментов SL, определяются их возможные пересечения с другими подходящими сегментами. Когда действительное пересечение найдено, оно вставляется в очередь событий. Кроме того, когда событие пересечения на EQ обрабатывается во время развертки, оно вызывает переупорядочение списка SL, а пересечение также добавляется в выходной список LAMBDA. В конце, когда все события были обработаны, LAMBDA будет содержать полный упорядоченный набор всех пересечений.
Однако есть одна критическая деталь, суть алгоритма, которую нам еще нужно описать; а именно, как вычислить действительное пересечение? Ясно, что два сегмента могут пересекаться, если они происходят одновременно на линии развертки в какое-то время. Но этого само по себе недостаточно, чтобы сделать алгоритм эффективным. Важное замечание состоит в том, что два пересекающихся сегмента должны быть непосредственно выше соседей по линии развертки. Таким образом, существует только несколько ограниченных случаев, для которых необходимо вычислить возможные пересечения:
Когда сегмент добавляется в список SL, определите, пересекается ли он со своими соседями и выше.
Когда сегмент удаляется из списка SL, его предыдущие выше и ниже соседи объединяются как новые соседи. Поэтому их возможное пересечение необходимо определить.
В событии пересечения должно быть определено положение двух сегментов в списке SL и их пересечение с их новыми соседями (по одному для каждого). Это означает, что для обработки любого одного события (конечной точки или пересечения) EQ существует не более двух определений пересечений, которые необходимо выполнить.
Остается одна деталь, а именно время, необходимое для добавления, поиска, замены и удаления сегментов из структуры SL. Для этого SL можно реализовать как сбалансированное двоичное дерево (например, AVL, 2-3 или красно-черное дерево), что гарантирует, что эти операции будут занимать не более O (log-n), так как n - максимальный размер списка SL. Таким образом, каждый из (2n + k) событий имеет наихудшую обработку O (log-n). Добавляя начальную сортировку и обработку событий, эффективность алгоритма: O (nlog-n) + O ((2n + k) log-n) = O ((n + k) log-n).
Псевдокод: алгоритм Бентли-Оттмана
Объединяя все это вместе, логика верхнего уровня для реализации алгоритма Бентли-Оттмана задается следующим псевдокодом:
Initialize event queue EQ = all segment endpoints;
Sort EQ by increasing x and y;
Initialize sweep line SL to be empty;
Initialize output intersection list IL to be empty;
While (EQ is nonempty) {
Let E = the next event from EQ;
If (E is a left endpoint) {
Let segE = E segment;
Add segE to SL;
Let segA = the segment Above segE in SL;
Let segB = the segment Below segE in SL;
If (I = Intersect( segE with segA) exists)
Insert I into EQ;
If (I = Intersect( segE with segB) exists)
Insert I into EQ;
}
Else If (E is a right endpoint) {
Let segE = E segment;
Let segA = the segment Above segE in SL;
Let segB = the segment Below segE in SL;
Delete segE from SL;
If (I = Intersect( segA with segB) exists)
If (I is not in EQ already)
Insert I into EQ;
}
Else { // E is an intersection event
Add E’s intersect point to the output list IL;
Let segE1 above segE2 be E intersecting segments in SL;
Swap their positions so that segE2 is now above segE1;
Let segA = the segment above segE2 in SL;
Let segB = the segment below segE1 in SL;
If (I = Intersect(segE2 with segA) exists)
If (I is not in EQ already)
Insert I into EQ;
If (I = Intersect(segE1 with segB) exists)
If (I is not in EQ already)
Insert I into EQ;
}
remove E from EQ;
}
return IL;
}
Эта процедура выводит полный упорядоченный список всех точек пересечения.