Ивановский Сергей Алексеевич, Преображенский Алексей Семенович, Симончик Сергей Константинович
АЛГОРИТМЫ ВЫЧИСЛИТЕЛЬНОЙ ГЕОМЕТРИИ. ВЫПУКЛЫЕ ОБОЛОЧКИ: ПРОСТЫЕ АЛГОРИТМЫ
1. ВВЕДЕНИЕ
Вычислительная геометрия - это очень интересная и сравнительно молодая область компьютерной науки, находящаяся на стыке информатики и геометрии и сочетающая геометрические понятия, идеи и модели с понятиями, идеями и методами разработки алгоритмов и структур данных.
Со времен Евклида (III век до нашей эры) геометрические построения были существенной частью и эффективным средством как геометрических исследований, так и геометрической педагогики. Классический пример представляют собой задачи на построение: построить треугольник по трем данным его сторонам, разделить данный угол пополам, из данной точки опустить перпендикуляр на данную прямую, разделить пополам данный отрезок [4].
Геометрические построения, по сути дела, являются зачатками алгоритмической науки. Действительно, в задачах на построение по заданным геометрическим объектам (точкам, прямым, отрезкам и окружностям), то есть по исходныш данныш, требуется построить другие геометрические объекты, то есть вы1ходны1е данныье, используя для этого фиксированный набор инструментов (базовых операций или примитивов), кото-
рыми могут быть, например, циркуль и линейка, или только циркуль [3], или только линейка. У геометрических построений существует также понятие сложности, введенное Эмилем Лемуаном в 1902 году и означающее минимальное количество применений базовых операций, необходимое для данного построения.
Появление компьютеров и затем бурное развитие компьютерной науки (информатики) открыло возможность симбиоза чисто геометрической природы задач на построение с аналитическим (вычислительным) подходом к их решению, что, в свою очередь, привело к возможности решения нового класса задач, которыми и занимается вычислительная геометрия.
В этих задачах исходными данными являются геометрические объекты (например, множество точек, набор отрезков, многоугольник), а результатом может являться:
- ответ на вопрос о некоторых отношениях между заданными объектами (например, пересекаются ли заданные отрезки);
- перечисление фактов относительно этих отношений (например, перечисление всех пар пересекающихся отрезков);
- построение нового геометрического объекта (например, наименьшего выпуклого многоугольника, содержащего заданные точки).
Причем важно, что как размер исходных данных (число заданных геометрических объектов), так и размер полученного ответа (перечисления или нового геометрического объекта) могут быть произвольными и, как правило, достаточно большими.
Вычислительная геометрия находит применение во многих областях науки и техники, таких как [5]:
- компьютерная графика и визуализация;
- компьютерное зрение и робототехника;
- географические информационные системы (ГИС);
- системы автоматизированного проектирования (САПР);
- компьютерный анализ и интерпретация данных (например, компьютерная томография);
- молекулярная биология;
- астрофизика.
2.1. ВЫПУКЛАЯ ОБОЛОЧКА И ВЫПУКЛОЕ МНОЖЕСТВО
О®
Задача построения выпуклой оболочки не только является центральной в целом ряде приложений, но и позволяет разрешить ряд вопросов вычислительной геометрии, на первый взгляд не связанных с ней. Построение выпуклой оболочки конечного множества точек, особенно в случае точек на плоскости, уже довольно широко и глубоко исследовано и имеет приложения, например в распознавании образов, обработке изображений, а также при раскрое и компоновке материала. Мы будем рассматривать эту задачу на плоскости.
Если нам задано множество точек Q, то его выпуклая оболочка СН(О) - это наименьшее выпуклое множество, включающее в себя все эти точки. Вытуклыш называется множество точек, в котором выполняется следующее правило: если какие-то две точки принадлежат множеству, то и соединяющий их отрезок принадлежит этому же множеству (рис. 2.1).
Чтобы наглядно представить это понятие в случае, когда Q - конечное множество точек на плоскости, предположим, что это множество охвачено большой растянутой резиновой лентой. Когда лента освобождается, то она принимает форму границы выпуклой оболочки.
Еще одним понятием, которое нам понадобится, является понятие крайней точки. Точка р выпуклого множества Я называется крайней, если не существует пары точек а, Ь е Я таких, что р лежит на открытом отрезке (а, Ь). Несложно догадаться, что выпуклая оболочка конечного множества точек на плоскости является выпуклым многоугольником, все вершины которого принадлежат исходному множеству точек (и являются крайними точками выпуклой оболочки). В свою очередь, стороны этого многоугольника (называемые также ребрами многоугольника) образуют границу выпуклой оболочки (рис. 2.2).
Заметим, что для любого ребра выпуклой оболочки (многоугольника) часть точек исходного множества будет лежать на данном ребре (например, концы этого ребра), а оставшиеся точки по одну сторону от содержащей ребро прямой (попробуйте доказать это утверждение самостоятельно).
Верно и обратное утверждение если для отрезка, соединяющего две точки исходного множества, часть точек исходного множества лежит на нем, а оставшиеся точки по одну сторону от содержащей отрезок прямой, то данный отрезок является ребром выпуклой оболочки (рис. 2.3).
Выпуклые многоугольники принято задавать последовательностью вершин в порядке обхода по часовой стрелке или про-
Рис. 2.1. Пример выпуклого (а) и невыпуклого (б) множества
а)
б)
не крайняя точка
крайняя точка
граница выпуклой оболочки (выпуклый многоугольник)
Рис. 2.2. Исходное множество точек (а) и его выпуклая оболочка (б)
8
тив часовой стрелки. Например, для выпуклого многоугольника, приведенного на рисунке 2.4, последовательность вершин ЕЕБСБА является его обходом по часовой стрелке, а последовательность АБСБЕЕ обходом против часовой стрелки.
Далее будет рассмотрено несколько алгоритмов построения выпуклой оболочки. Для всех этих алгоритмов искомой выпуклой оболочкой будем считать выпуклый многоугольник, заданный последовательностью своих вершин в порядке обхода против часовой стрелки.
2.2. АЛГОРИТМ ЗАВОРАЧИВАНИЯ ПОДАРКА
В некотором царстве, в некотором государстве король приказал своему садовнику оградить свой сад от посяганий учеников близлежащей школы. У садовника есть до-
а)
Рис. 2.3. Свойство ребер выпуклой оболочки
статочно длинная веревка, и он намеревается использовать ее в качестве ограждения. Садовнику нужно построить изгородь так, чтобы как можно большую площадь огородить и как можно меньше веревки при этом потратить. Веревку можно привязывать только к стволам деревьев сада.
Несложно догадаться, что садовник должен построить не что иное как выпуклую оболочку множества точек. Точками в данном случае являются деревья (если смотреть на них с высоты птичьего полета).
Чтобы решить эту задачу, садовник будет действовать в соответствии со следующей стратегией: сначала он найдет такое дерево, которое точно будет включено в изгородь (оно будет являться крайней точкой выпуклой оболочки), и закрепит на нем веревку, а затем начнет обходить сад кругом (например, против часовой стрелки), держа в руках натянутую веревку. Когда он сделает полный круг, сад уже будет огорожен. Этот процесс проиллюстрирован на рисунке 2.5.
Оказывается, идея, использованная садовником для решения данной задачи, лежит в основе алгоритма заворачивания подарка, или обхода Джарвиса [2] (алгоритм называется так в честь своего автора Рэя Джарвиса, который придумал его в 1973 году).
На самом деле, для того чтобы описанный способ действия стал алгоритмом, его надо формализовать, то есть записать так, чтобы его мог выполнить робот, а не только человек.
Пусть в памяти робота задана карта сада, где деревья обозначены точками на плоскости и каждая точка имеет пару координат. Обозначим общее количество деревьев за п, тогда координатами дерева с номером г будет пара чисел = (хг, у), где х1 - абсцисса точки, соответствующей дереву, а у1 - ордината этой точки, при этом г может принимать значения от 1 до п.
Тогда наш (пока еще неформальный) алгоритм выглядит так (см. листинг 1).
по часовой стрелке: i
против часовой стрелки: (
■C ^-В -
1 • F
Рис. 2.4. Обходы выпуклого многоугольника
E
A
F
C
B
Листинг 1. algorithm JARVIS-MARCH(Q) ® CH(Q) Вход. Исходное множество точек Q = {pi I p. = (xi, y), i e [1..n]}. Выход. Выпуклая оболочка множества точек CH(Q), заданная последовательностью вершин (перечисленных в порядке обхода против часовой стрелки).
1 Инициализирировать выпуклую оболочку пустой последовательностью точек
2 Выбрать первую точку, которая будет являться крайней точкой выпуклой оболочки
3 Сделать полный круг вокруг множества точек, «заворачивая» вокруг него ребра выпуклой оболочки
Рис. 2.5. Процесс «заворачивания»
Q
Листинг 2.
2.1 2.2
2.3
2.4
2.5
2.6
Pstart - Pi
for "pi = (х, yi) е Q do
if P < ystap) or = ystart) and Ц <
Pstart - Pi
end-if end-do
Xstart)) then
Pstart
Рис. 2.6. Начальная точка p
Рассмотрим вторую строку алгоритма .ГЛИУК-МЛИСН. В ней говорится, что мы должны выбрать первую точку так, чтобы она являлась вершиной выпуклой оболочки. Оказывается, что точка, имеющая наименьшую абсциссу из всех точек, которые имеют наименьшую ординату, является крайней точкой выпуклой оболочки (попробуйте доказать это утверждение самостоятельно) (рис. 2.6).
Учитывая этот факт, можно детализировать вторую строку алгоритма .ГЛИУК-МЛИСН следующим образом (листинг 2).
Теперь детализируем процесс «заворачивания» ребер выпуклой оболочки вокруг множества точек. Будем использовать запись [р., р.] для обозначения ориентированного отрезка, направленного из точки (называемой началом ориентированного отрезка) в точку р. (называемую концом ориентированного отрезка) (листинг 3).
Здесь рсиггеп1 - текущая (последняя найденная) вершина выпуклой оболочки, а рпех1 -следующая за ней вершина выпуклой обо-
лочки (в направлении обхода против часовой стрелки). Запись ADDVERTEX(p current) добавляет точку p . = (x ,, y ,) в
current current current
конец последовательности точек, представляющей собой обход выпуклой оболочки против часовой стрелки.
Пусть pcurrent является последней найденной вершиной выпуклой оболочки, а pprevious - предпоследней найденной. Рассмотрим луч l, исходящий из вершины pcurrent в том же направлении, что и ориентированный отрезок [p , p .] (в случае,
f кг previous' * currentJ 4 J '
когда мы находимся на первом шаге алгоритма, рассмотрим в качестве l горизонтальный луч, исходящий из вершины pstart и направленный в сторону увеличения абсцисс). Заметим, что все остальные точки лежат либо на прямой, содержащей луч, либо по левую сторону от него. Зафиксируем произвольную точку t на этом луче, не совпадающую с pcurrent. Тогда любой точке pt множества Q \ ipcurrent] будет соответствовать угол Zpipcurrentt, лежащий в диапазоне (0; р]. Этот угол называется полярным углом точки pi относительно луча l.
Теперь поставленную перед нами задача в строке 3.4 алгоритма JARVIS-MARCH можно переформулировать следующим образом: Найти такую точку pnext, чтобы1 угол Z.p ,p J быт минимальным.
next current
Листинг 3.
3.1
3.2
3.3
3.4
3.5
3.6
Pcurrent - Pstart
do
ADDVERTEX(Pcurrent)
Найти такую вершину pnext, чтобы она являлась следующей после pcurrent вершиной выпуклой оболочки (в направлении обхода против часовой стрелки), то есть любая точка исходного множества лежала бы по левую сторону от ориентированного отрезка [p ., p .] или на нем
current next
Pcurrent - Pnext
While Pcurrent * Pstart
Рис. 2.7. Несколько шагов алгоритма 1аяув-Маясн
Несколько шагов описанного алгоритма проиллюстрированы на рисунке 2.7.
Можно реализовывать данный алгоритм, используя непосредственное вычисление указанных углов с целью их последующего сравнения для нахождения наименьшего. Однако непосредственное вычисление углов сразу же подразумевает использование вещественных чисел и обратных тригонометрических операций. На самом деле, эту задачу можно решить, используя только операции умножения, сложения и сравнения. При этом, если исходные данные задачи целочисленные, все производимые вычисления не выведут за рамки операций с целыми числами. Для этого, нужно использовать понятие ориентированной площади треугольника. Рассмотрим следующую подзадачу:
Для ориентированного отрезка [ра, рь ] и точки рс определить, лежит ли точка рс на прямой, содержащей данный отрезок, и если нет, то по левую или по правую сторону от него она лежит.
Чтобы ее решить, рассмотрим выражение:
^ (Ра,Рь,Рс ) = ^[(хЪ - Ха )(Ус - Уа ) -
- (УЬ - Уа )(Хс - Ха )].
В дальнейшем от данного выражения потребуется только его знак, поэтому множитель 1/2 можно опустить (рис. 2.8).
Оказывается (см. приложение), выражение 5 (р , рЬ, рс) представляет собой так называемую ориентированную площадь треугольника Арарърс. Абсолютная величина 5 (ра, ръ, рс) равна площади треугольника Ара ръ рс . В случае, когда точка рс лежит по левую сторону от ориентированного отрезка [ра, ръ ], площадь имеет положительный знак, в случае, когда точка рс лежит по правую сторону от ориентированного отрезка [ра, ръ ], - знак площади отрицательный, если же точки ра , ръ и рс лежат на одной прямой, площадь равна нулю.
Для того чтобы в строке 3.4 алгоритма Таиук-Маисн найти точку рпех1, имеющую минимальный угол, то есть точку, которая бы являлась вершиной выпуклой оболочки, и любая точка исходного множества лежала по левую сторону от ориентированного
отрезка [р^п!, рпех1 ] или на не^ будем действовать в соответствии со следующим алгоритмом.
Рь (хь,Уь)
.Рс (Хс, Ус)
Ра (ха, Уа )
Рис. 2.8. Для такого случая 5(ра, ръ, рс) будет иметь отрицательный знак
Будем постепенно подбирать искомую точку pnext из точек исходного множества Q, отбрасывая на каждом шаге один заведомо неподходящий для нее вариант. Сначала положим pnext равной любой точке исходного множества. Будем перебирать все точки исходного множества. Пусть на каком-то шаге перебора мы рассматриваем точку p.. Тогда должно выполниться одно из трех условий:
1) Pi лежит левее чем [pcurrent, Pnext]. Это
означает, что точка pt заведомо не является той точкой, которую мы ищем.
2) p. лежит правее чем ^c^, pnext]
Это означает, что текущая точка pnext на самом деле не является той точкой, которую мы ищем. Однако мы пока не можем сказать такого о pt, поскольку все рассмотренные ранее точки лежат или левее ориентированного отрезка [pcurrent, pt ], или на нем. Поэтому на этом шаге положим pnext — pt.
3) ТРи точки pcurrent, pi и pnext лежат на одной прямой. Причем точки p. и pnext гарантированно лежат по одну сторону от точки p на этой прямой, поскольку в
current
противном случае она не была бы крайней точкой выпуклой оболочки. Тогда выполняется одно из двух:
Рис. 2.9. Один из худших случаев для алгоритма Джарвиса
а) точка pi лежит на открытом отрезке (p ,, p ,) и, следовательно, она не мо-
current next
жет быть крайней точкой выпуклой оболочки;
б) точка pnext лежит на открытом отрезке (pcurrent, pi). Это означает, что текущая точка pnext на самом деле не является той точкой, которую мы ищем. Однако мы пока не можем сказать такого о pi , поскольку все рассмотренные ранее точки лежат или левее ориентированного отрезка [pcurrent, pi ], или на нем. Поэтому на этом шаге положим pnext — p..
После того как в качестве pi мы переберем все возможные точки с помощью приведенного алгоритма, мы обнаружим pnext следующую за pcurrent вершину выпуклой оболочки (в направлении обхода против часовой стрелки). Алгоритмическая запись этого процесса приведена в листинге 4.
Запись AREA(pcurrent, pv p.) означает вычисление ориентированной площади треугольника Apcurrentpipj.
Запись LENGTH(pi, pcurrent) означает вычисление длины отрезка [p. , pcurrent ].
Теперь проанализируем сложность работы алгоритма Джарвиса. Обозначим количество точек исходного множества Q, являющихся вершинами выпуклой оболочки, через h. Заметим, что цикл while в строках 3.2-3.6 алгоритма JARVIS-MARCH выполняется в точности h раз, а одна итерация такого цикла требует O(n) операций. Это означает, что время выполнения алгоритма Джарвиса равно O(hn). Любопытный факт заключается в том, что время выполнения данного алгоритма зависит не только от n (размера входных данных), но и от h (размера выходных данных). Если мы заранее знаем, что число h невелико, то этот алгоритм будет очень эффективен (рис. 2.9).
Листинг 4.
3.4.1 Pnext - Pcurrent
3.4.2 for Vp. € Q 1 i € [l..n] do
3.4.3 if (Area(p t, p., p t) > 0) or current i next ((Area(p t, p., p t) = 0) and (Length(p t, p t) < Length(p t, p.))) then current i next current next current i
3.4.4 Pnext - Pi
3.4.5 end-if
3.4.6 end-do
Но так как в некоторых случаях все п точек исходного множества могут быть вершинами выпуклой оболочки (Н = п), то время выполнения данного алгоритма в худшем случае равно 0(п2). Особо стоит отметить, что существуют алгоритмы для построения выпуклой оболочки в пространствах размерности больше двух (например, в трехмерном случае), использующие ту же идею «заворачивания».
2.3. АЛГОРИТМ ГРЭХЕМА
В 1972 году Рональд Грэхем в одной из первых работ, специально посвященных вопросу разработки эффективных геометрических алгоритмов, показал, что, выполнив предварительно сортировку точек, крайние точки можно найти за линейное время [2]. Использованный им метод стал очень мощным средством в области вычислительной геометрии.
Алгоритм Грэхема использует стек, в котором хранятся точки, являющиеся кандидатами в выпуклую оболочку. Как известно [1], стек это динамическая последовательная структура данных, в которой непосредственно доступен только тот элемент, который был добавлен в него последним. В данном алгоритме будет использоваться модификация стека, в которой доступны два последних элемента. Нам понадобятся следующие операции со стеком (стек обозначен за 8):
1) РШН(8; е) добавить элемент е в стек 8;
2) Р0Р(8) удалить верхний элемент стека 8;
3) 8ее(8) количество элементов, находящихся в стеке 8;
4) ТОР(8) верхний элемент стека 8;
5) №ХТ-Т0-Т0Р(8) элемент стека 8, который следует за верхним в стеке;
Итак, предположим, что найдена точка рШг1, заведомо являющаяся вершиной выпуклой оболочки множества точек Q (задача нахождения такой точки уже решалась в алгоритме Джарвиса). Упорядочим осталь-
ные точки в соответствии со значениями полярного угла относительно точки pstan как начала координат (осью в данной системе координат будет горизонтальный луч, исходящий из точки pstart в сторону увеличения абсцисс), а при равенстве полярных углов упорядочим точки по расстоянию от точки p . ..
* start
Другими словами введем на оставшихся точках отношение строгого порядка <. Для двух точек pa и pb будем считать pa < pb, если точка pa лежит по правую сторону от ориентированного отрезка [pstart, pb ], либо точка pa лежит на этом отрезке, и длина отрезка [pstart, pb ] меньше, чем длина отрезка [pstart, pb ]. В данном случае использование определения взаимного расположения точки и ориентированного отрезка уместно, поскольку полярные углы точек из множества Q \ {pstart} находятся в пределах [0; Р) относительно точки pstart.
Алгоритм Грэхема состоит из двух этапов: на первом этапе точки сортируются в соответствии с введенным отношением порядка <; на втором этапе в процессе обхода упорядоченной последовательности точек отбрасываются те точки, которые гарантированно не являются вершинами выпуклой оболочки. Второй этап алгоритма Грэхема также называется обходом Грэхема (рис. 2.10).
На каждом шаге обхода выпуклая оболочка уже пройденных точек хранится в стеке S, при этом верхний элемент соответ-
я6
?4 ••
Яг
Рис. 2.10. Точки множества Q \ {рлал}, упорядоченные в соответствии с введенным отношением <
(' < ] ^ Ч; < 1; ; " Л
Я
Я
Я
Я
ствует последней обработанной точке. Когда все точки будут обработаны, в стеке будут находиться все точки выпуклой оболочки (в порядке обхода против часовой стрелки).
Для обхода Грэхема ключевым понятием является понятие левого поворота.
Тройка точек (р , рь, рс) называется левым поворотом, если точка р лежит строго слева от ориентированного отрезка [р , рь ]. Если бы мы прямолинейно перемещались от точки ра до точки рь, а затем от точки рь до точки рс, то в точке рь нам бы пришлось повернуть налево, именно поэтому такой поворот называется левым.
Если обход многоугольника осуществляется против часовой стрелки, то при движении по ребру слева от ребра будет оставаться внутренность многоугольника. На рисунке 2.11 закрашенная область соответствует внутренности многоугольника при движении в направлении, указанном стрелкой. Так как выпуклая оболочка является выпуклым многоугольником с вершинами в крайних точках, то для нее допустимы только левые повороты (в случае на рисунке 2.11(6) точка рь не является крайней, а в случае на рисунке 2.11(в) многоугольник не будет выпуклым).
Для того, что поддерживать в стеке 5 выпуклую оболочку уже обработанных точек, нужно поддерживать свойство, что любые три подряд идущих элемента стека образуют левый поворот. При обработке очередной точки д1 это свойство может нарушиться только для тройки точек (№ХТ-Т0-Т0Р(£), Т0Р(5), д) Если тройка точек (№ХТ-Т0-Т0Р(Я), Т0Р(5), д^ не образует левый поворот, то необходимо уда-
лить вершину Top(S) из стека, поскольку она не может являться вершиной выпуклой оболочки (см. рисунок 2.11). Удаление необходимо выполнять до тех пор, пока в стеке не останется один элемент или пока тройка точек (Next-To-Top(S), Top(S), gt) не образует левый поворот. В конце шага в стек добавляется обрабатываемая точка gt.
Запишем алгоритм формально (листинг 5).
Во второй строке алгоритма мы находим точку pstart; все остальные точки множества Q находятся выше не (или на одном уровне, но правее), и потому она заведомо входит в CH(Q). В третьей строке оставшиеся точки множества Q упорядочиваются в соответствии с введенным отношением строгого порядка <. Затем в четвертой строке мы помещаем в стек первую точку pstart, после чего можем утверждать, что стек содержит выпуклую оболочку множества {pstart}. Дальше на каждой итерации цикла for, записанного в строках 5-10, мы добавляем по одной точке к выпуклой оболочке и поддерживаем свойство, что в конце k-ой итерации стек содержит выпуклую оболочку множества точек {pstart, g 1, q2, ... , gk} в виде последовательности вершин в порядке обхода против часовой стрелки. Заметим, что в направлении от дна стека S к его верхушке любые три подряд идущих элемента стека s , sb и sc образуют левый поворот, то есть точка sc лежит левее ориентированного отрезка [sa, sb]. После добавления очередной точки в стек это свойство может нарушиться, поэтому может потребоваться удаление нескольких верхних элементов стека (см. рисунок 2.10), что и происходит в
б)
в)
Рис. 2.11. Пример того, как тройка точек (р , рь, рс) образует (а) и не образует (б, в) левый поворот.
Листинг 5. algorithm GRAHAM-SCAN(Q) ® CH(Q)
Вход. Исходное множество точек О = (р. 1 р. = (х.. у), г е Г1..и11.
Выход. Выпуклая оболочка множества точек СЖО). заданная последовательностью
вершин (перечисленных в порядке обхода против часовой стрелки).
1 Создать пустой стек S
2 Выбрать точку с наименьшей абсциссой среди всех точек имеющих минимальную
°рдинату (Pstart)
3 Отсортировать все точки множества Q = {psfart} в порядке отношения <.
B результате получится последовательность точек {qj"-1, такая что
qi < q2 < ••• < q„-2 < q„-i
4 PUSH^ Pstart)
5 for i — 1 to n - 1 do
6 while SIZE(S) > 1 and три точки (NEXT-TO-TOP(S), TOP(S), qi) не образуют левый
поворот do
7 POP(S)
8 end-do
9 PUSH(S, qi)
10 end-do
11 Вернуть в качестве ответа последовательность точек, содержащуюся в стеке S (дно
стека будет первым элементом результирующей последовательности, а TOP(S)
последним)
цикле while в строках 6-7 алгоритма Graham-Scan.
На рисунке 2.12 показан пример итерации алгоритма GRAHAM-SCAN. На данной итерации обрабатывается точка qi. Сначала последовательность точек NEXT-TO-TOP(S) ® TOP(S) ® qt не составляет левый поворот (см. рисунок 2.12 а). Следовательно, точка Top(S) не входит в выпуклую оболочку. После двух удалений (см. рисунок 2.12 тройка точек (NEXT-TO-TOP(S), Top(S), q;) составляет левый поворот и точка qi добавляется в стек. В конце итерации
стек содержит выпуклую оболочку
{p start, qi, q2, •••, qi
Покажем теперь, что время работы алгоритма Graham-Scan есть O(n log n). Сортировка точек может быть выполнена за время O(n log n). Остальная часть алгоритма требует времени O(n). Это не сразу очевидно, поскольку очередная итерация цикла for в строках 5-10, помимо добавления одной новой точки, может потребовать удаления нескольких старых. Тем не менее, любая точка qi добавляется в стек S только один раз, а потому и удаляется не более
Рис. 2.12. Одна итерация алгоритма Graham-Scan
одного раза. Тем самым общее время и на добавление, и на удаление есть O(n). Итак, мы приходим к следующему выводу: выпуклая оболочка n точек на плоскости может быть найдена за время 0(n log n) при использовании памяти 0(n) с использованием только арифметических операций и сравнений.
2.4. ПОШАГОВЫЙ АЛГОРИТМ ПОСТРОЕНИЯ ВЫПУКЛОЙ ОБОЛОЧКИ
До сих пор были рассмотрены алгоритмы, строящие выпуклую оболочку только после того как известны все точки исходного множества (такие алгоритмы называются алгоритмами в режиме «offline»). В противоположность этому, может потребоваться алгоритм, выдающий ответ немедленно по поступлении очередной точки, не дожидаясь следующей (такие алгоритмы называются рекуррентными алгоритмами, или алгоритмами в режиме «online»). На вход такого алгоритма поступает последовательность из n точек; после поступления очередной точки (но до поступления следующей за ней) требуется указать выпуклую оболочку всех точек, полученных к данному шагу.
Можно применять обход Грэхема на каждом шаге заново, и тогда общее время работы алгоритма будет равно O(n2 log n). Можно модифицировать такой алгоритм и не сортировать точки при добавлении новой точки pt, а вставлять ее в отсортированный массив в соответствии с полярным углом за время O(i), а после выполнить просмотр методом Грэхема также за O(i). Асимптотическая сложность такого алгоритма будет O(n2). Рассмотрим пошаговый алгоритм, не использующий обход Грэхема.
Будем обрабатывать точки одну за другой по мере поступления, поддерживая на каждом шаге выпуклую оболочку. Обозначим за Qr множество {pp ..., pr}, где r > 1. Рассмотрим шаг алгоритма, на котором точка pr (r > 1) добавляется к уже построенной выпуклой оболочке для множества то-
чек @ г. Иными словами, рассмотрим переход от СЯ(2г1) к СИ^г). Возможны два случая:
1) точка рг лежит внутри СИ(2г1), либо на ее границе, тогда СИ^г) = СИ^г1);
2) точка рг лежит вне СИ(2г1). Предположим, что в точке рг находится точечный источник света. Некоторые ребра СИ^г1) будут «освещены», а остальные ребра будут находиться «в тени». Освещенные ребра выпуклой оболочки СИ^г1) образуют цепь из подряд идущих ребер. Освещенная цепь ограничена двумя опорныши точками, то есть такими точками, у которых одно из инцидентных ребер выпуклой оболочки будет освещено, а второе будет в тени. Через точку рг и каждую из опорных точек могут быть проведены две вспомогательные прямые, являющиеся опорными к СИ(2г1). Прямая является опорной к выпуклому многоугольнику Р, если она проходит через вершину Р и внутренняя область многоугольника Р полностью лежит по одну сторону от этой прямой. Будем называть опорную точку I левой опорной точкой относительно точки рг, если слева от опорной прямой, проходящей через точки рг и I, не лежит точек из СИ(2г1). Аналогично будем называть опорную точку г правой опорной точкой относительно точки рг, если справа от опорной прямой, проходящей через рг и г, не лежит точек из СИ(2г1). Опорные точки играют важную роль в преобразовании СИ(2г1) в СИ^г): они представляют собой границу между частью выпуклой оболочки, которая должна быть сохранена (неосвещенные ребра), и частью выпуклой оболочки, которая должна быть удалена (освещенные ребра). Освещенные ребра должны быть замещены ребрами, образованными точкой рг и двумя опорными точками (рис. 2.13).
Для каждой вершины р выпуклой оболочки определим №ХТ(р) как следующую за ней вершину в порядке обхода выпуклой оболочки. Аналогично определим вершину РКБУЮШ(р), предшествующую вершине р.
Ребром выпуклой оболочки с началом в точке р и концом в точке №ХТ(р) называется ориентированный отрезок [р, №ХТ(р)].
Освещенность ребра из точки необходимо определить более формально. Ребро Гр, рь 1 освещается из точки рс, если точка рс лежит по правую сторону от ориентированного отрезка [ра, рь 1, либо на прямой, содержащей этот отрезок.
Для определения опорных точек необходимо для каждой вершины текущей выпуклой оболочки узнать, является ли она опорной точкой. Вершина р является ле^ой опорном точкой относительно точки р , если ребро [РИЕ УЮШ (р), р] не освещено из точки рг, а ребро [р. №хт(р)] освещено из точки рг. Аналогично точка р является правом опорном точкой относительно р , если ребро [РКЕУЮШ (р), р ] освещено из точки рг, а ребро [р. №хт(р)] не освещено из точки рг.
После нахождения опорных точек вершины освещенной цепи, заключенные между опорными точками, должны быть замещены точкой рг.
Прежде чем углубляться дальше в детали алгоритма, необходимо описать используемые структуры данных. Вершины выпуклой оболочки будем хранить с помощью од-носвязного кольцевого списка ^ поддерживающего операцию №хт(^ р) для каждого
Рис. 2.13. Шаг последовательного алгоритма
своего элемента p. Эта операция возвращает для точки p точку NEXT(p). Чтобы работать со списком надо знать хотя бы одну вершину, принадлежащую ему, для этого вводится операция HEAD(L), возвращающая точку, являющуюся первой в списке.
В листинге 5 представлена алгоритмическая запись пошагового алгоритма.
Детализируем добавление точки p1 в пустой список L (вторая строка алгоритма Sequential) (листинг 6).
После этого шага HEAD(L) и NEXT(L, p 1) вернут p1.
Листинг 5. algorithm SEQUENTIAL(Q) ^ [CH(Qr )]1n
Вход. Последовательность точек Q = [p. I p. = (x., y), i e [1..n]}. Выход. Последовательность выпуклых оболочек {CH(Qr )}1n .
Каждая выпуклая оболочка CH(Qr ) задана последовательностью вершин (перечисленных в порядке обхода против часовой стрелки).
1 Создать пустой односвязный кольцевой список L
2 Добавить в L точку p1
3 Зафиксировать в качестве CH(Q1) последовательность точек, записанную в списке L
4 for i — 2 to n do
5 Найти вершины l и r текущей выпуклой оболочки, являющиеся левой и правой
опорными точками относительно точки pi
6 if Опорные точки l и r существуют then
7 Заменить элементы списка L в промежутке с l по r на вершину p.
8 end-if
9 Зафиксировать в качестве CH(Q. ) последовательность точек, записанную в списке L
10 end-do
Листинг 6.
2.1
2.2
Сделать p1 головой списка L Циклически замкнуть список L
Поиск опорных точек l и r относительно точки pi будем осуществлять следующим образом (пятая строка алгоритма SEQUENTIAL) (листинг 7).
Замена вершин списка L в промежутке с l по r вершиной pi можно осуществлять следующим образом (седьмая строка алгоритма Sequential) (листинг 8).
Оценим асимптотическую сложность алгоритма Sequential. Поиск опорных точек для точки pi имеет сложность O(i) (пятая строка), а замена в списке - сложность O(1) (седьмая строка). Сложность поддержания выпуклой оболочки на i-ом шаге составляет O(i). Таким образом, суммарная сложность алгоритма SEQUENTIAL составляет O(n2).
Если хранить последовательность вершин выпуклой оболочки в виде сбалансированного дерева поиска [1], то операции нахождения опорных точек, вставки и уда-
ления цепочки вершин на i-ом шаге можно осуществлять за время O(log i); таким образом, можно достичь суммарной сложности алгоритма O(n log n).
ЗАКЛЮЧЕНИЕ
Рассмотренные алгоритмы построения выпуклой оболочки на плоскости являются в некотором смысле естественными и простыми для понимания. Существуют и другие алгоритмы решения этой задачи. В продолжение данной статьи будут рассмотрены некоторые из них, а также тесная связь задачи построения выпуклой оболочки с задачей сортировки. Это позволит определить так называемую нижнюю оценку сложности задачи построения выпуклой оболочки и оптимальные по сложности алгоритмы ее решения.
Листинг 7.
5.1 p — Head(L)
5.2 do
5.3 nextp — Next(L, p)
5.4 nextnextp — Next(L, nextp)
5.5 b1 — ребро [p, nextp] освещено из точки pi
5.6 b2 — ребро [nextp, nextnextp] освещено из точки pi
5.7 if (not bj) and b2 then l — nextp
5.8 if b and (not b2) then r — nextp
5.9 p — nextp
5.10 while p Ф Head(L)
Листинг 8.
7.1 Для вершины I переставить ссылку, указывающую на следующий элемент,
на вершину
7.2 Для вершины переставить ссылку, указывающую на следующий элемент,
на вершину г
7.3 Сделать головой списка L
.Приложение.
V
ОРИЕНТИРОВАННАЯ ПЛОЩАДЬ ТРЕУГОЛЬНИКА
Обратимся к выражению для ориентированной площади треугольника :
Ра- Рь- Рс) = ха)(Ус-Уа) - (Уь - Уа)(хс-ха)]
Заметим, что если мы выполним параллельный перенос на (ох, Оу), то есть заменим точки ра, рь и рс на точки р'а = (ха + Ох, уа + Оу), р' = (хь + Ох, уь + Оу) и р'с = (хс + Ох, ус + ¿у), то для любых Ох и Оу будет верно равенство
5 (Ра , Рь , Рс ) = 5 (Р'а , Р'ь , Р'с
В этом можно легко убедиться, подставив соответствующие значения в выражение для вычисления ориентированной площади.
Вместо того, чтобы рассматривать точки Ра = (ха, уа), Рь = (хь, уь) и Рс = (хс, ус), рассмотрим три точки р0 = (0, 0), р 1 = (х1, у 1) и р2 = (х2, у2). Достаточно доказать, что ориентированная площадь вычисляется верно для всех таких троек точек, поскольку параллельным переносом мы всегда можем совместить точку ра с точкой р0, а точки рь и рс перейдут в р1 и р2 соответственно
(х1 = хь - ха , у1 = уь - уа, х2 = хс - ха, у2 = Ус - уа )'
Простой подстановкой получим:
5(Р0 -Р1 -Р2) = 1( х1 у 2 - х2 У1) .
Заметим, что площадь треугольника равна половине площади параллелограмма, построенного на двух его сторонах [р0, р1] и [р0, р2]. В дальнейшем мы рассмотрим три принципиально разных случая взаимного расположения точек р0, р1 и р2. Достаточно будет рассмотреть только те случаи, в которых знак ориентированной площади отрицательный, поскольку при рассмотрении выражения очевидно, что
5 ^ Рр Р2) = (Рo, Р2, Р1)-
Также очевидно, что 5 (р0, р 1, р 2) = 0 только в том случае, когда все три точки р0, р1 и р2 лежат на одной прямой.
1) Точки р1 и р2 лежат в одном квадранте. Не умаляя общности, будем считать, что точки р1 и р2 лежат в первом квадранте (х1 > 0, х2> 0, у1 > 0, у2 > 0). Для случая других квадрантов вывод аналогичен.
Как показано на рис. 2.14 я-в, площадь параллелограмма можно выразить как разность площадей на рис. 2.14 б и 2.14 в:
7
Рис. 2.14. Точки р1 и р2 лежат в одном квадранте.
5О = (1 Х1У1 + ^ Х2 У 2 + Х2У1)-(2 х2У2 + 2 Х1У1 + Х1 У2) = - (Х1У2 - Х2У1 )-Под подразумевается неориентированная площадь параллелограмма (то есть - неотрицательная величина).
5 (ро> рр р2) = -15 О '
так как точка р2 лежит по правую сторону от ориентированного отрезка [р0, р1 ].
2) Точки р1 и р2 лежат в соседних квадрантах. Не умаляя общности, будем считать, что точки р1 и р2 лежат во втором и первом квадрантах, соответственно (х1 < 0, х2> 0, у1 > 0, у2 > 0). Для случая других квадрантов вывод аналогичен.
Как показано на рис. 2.15 а-в, площадь параллелограмма можно выразить как разность площадей на рис. 2.15 б и 2.15 в. Заметим, что х1 - отрицательное число:
5О = Х2У2 + х2У1 + 1(-х1)У1 + Х1)У2)- (2(-хМ + 2 X2У2) = - (Х1У2 - ^2Ух).
Под 5О подразумевается неориентированная площадь параллелограмма (то есть 5О - неотрицательная величина). 1
5 (рО> р1 ' р2) = -1 5О '
так как точка р2 лежит по правую сторону от ориентированного отрезка [р0, р1].
3) Точки р1 и р2 лежат в противоположных квадрантах. Не умаляя общности, будем считать, что точки р1 и р2 лежат в третьем и первом квадрантах, соответственно (х1 < 0, х2> 0, У1 < 0, У2 > 0). Для случая других квадрантов вывод аналогичен.
Параллельным переносом сдвинем точки р0, р1 и р2 так, чтобы точка р1 совпала с началом координат.
Тогда, точки р1 и р2 будут находиться в одном квадранте. Покажем, что
5 (рО' р^ р2) = -5 (р1' рО' р2)-
По определению ориентированной площади, 15 (р0, р1, р2)1 = 15 (р1, р0, р2)1- Если 5 (р0, р1, р2) = 0, то, очевидно, 5 (р1, р0, р2) = 0. Смена знака обусловлена тем, что, если точка р2 лежит строго по левую сторону от ориентированного отрезка [р0, р1 ], то она будет лежать строго по правую сторону от ориентированного отрезка [р1, р0], и наоборот.
Выражение 5 (р1, р0, р2) вычисляется корректно, поскольку оно удовлетворяет первому рассмотренному случаю. Значит и 5 (р0, р1, р2) вычисляется корректно.
Мы получили формулу для ориентированной площади треугольника Лрарърс из сугубо геометрических соображений. Можно было бы сделать вывод короче, введя понятие векторного произве-
дения. Векторное произведение двумерных векторов a и b обозначается как [a, b]. Если известны координаты векторов а = (ax,ay) и b = (bx,by), то [a, b] можно вычислить через них:
[a, b] = axby - aybx.
В свою очередь, ориентированная площадь S (p , pb, pc) может быть выражена через векторное произведение. Здесь за papb обозначен вектор, с началом в точке pa и концом в точке pb, имеющий координаты papb = (ph - pax , pby - pay ):
S (pa , pb , pc ) = 2[papb'FaFc]-
Литература
1. Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: Построение и анализ, М.: МЦНМО, 2000. 960 с.
2. Препарата Ф., Шеймос М. Вычислительная геометрия: Введение, М.: Мир, 1989. 480 с.
3. Костовский А.П. Геометрические построения одним циркулем, М.: Наука, 1984. 80 с.
4. Киселев А.П. Элементарная геометрия. Для средних учебных заведений. 1914. 404 с.
5. Amenta N, al. Application Challenges for Computational Geometry. CG Impact Task Force Report., 1996.
Ивановский Сергей Алексеевич, кандидат технических наук, доцент кафедры Математического обеспечения и применения ЭВМ СПбГЭТУ «ЛЭТИ»,
Преображенский Алексей Семенович, студент 6-го курса СПбГЭТУ «<ЛЭТИ» (магистратура),
Симончик Сергей Константинович, магистр, студент 6-го курса СПбГЭТУ «ЛЭТИ» (магистратура).
© Наши авторы, 2007 Our authors, 2007