Алгоритм Грэхема: строим выпуклую оболочку точек

Алгоритм Грэхема (Graham scan, опубликован Рональдом Грэхемом в 1972 году) - классический способ построить выпуклую оболочку конечного набора точек на плоскости за время . Идея простая и красивая: выбрать заведомо граничную опорную точку, отсортировать остальные по полярному углу относительно неё и обойти их одним проходом, поддерживая стек кандидатов в вершины оболочки и каждый раз проверяя направление поворота. Это первая историческая реализация выпуклой оболочки с оптимальной по нижней оценке асимптотикой и до сих пор - учебный эталон при разборе вычислительной геометрии.
Постановка задачи
Дано множество из точек на плоскости. Выпуклая оболочка - наименьшее выпуклое множество, содержащее все точки . Геометрически это многоугольник, чьи вершины - подмножество , такой что все остальные точки лежат внутри или на границе этого многоугольника. Задача - выдать вершины оболочки в порядке обхода (обычно против часовой стрелки), не пропустив ни одной граничной точки и не включив ни одной внутренней.
Нижняя оценка задачи - : построение выпуклой оболочки сводится к сортировке (если расположить чисел как точки на параболе, их оболочка возвращает в отсортированном порядке). Значит, алгоритмы быстрее в общем случае невозможны без дополнительных предположений на вход.
Идея алгоритма Грэхема
Алгоритм Грэхема выбирает опорную точку - ту, у которой минимальная координата (а при равных - минимальная ). Такая точка заведомо принадлежит выпуклой оболочке: ниже неё точек нет, значит, она лежит на нижней границе. Затем все остальные точки сортируются по полярному углу относительно - то есть по углу луча с положительным направлением оси . После сортировки получается список - обход начинается из и идёт против часовой стрелки.
Дальше включается стек кандидатов. В стек кладутся . Для каждой следующей точки проверяется ориентация тройки (предпоследняя вершина стека, верхушка стека, ). Если поворот левый (против часовой) - точка добавляется в стек. Если поворот правый или нулевой (по часовой или коллинеарный) - верхушка стека выкидывается, и проверка повторяется заново с новой верхушкой. После прохода всех точек стек содержит вершины выпуклой оболочки в порядке обхода.
Проверка поворота через cross product
Ключевая операция алгоритма - определение, куда повернула ломаная в очередной точке. Для трёх точек , , вычисляется псевдоскалярное произведение векторов и :
Знак определяет ориентацию:
- - поворот против часовой (левый), точка лежит слева от прямой .
- - поворот по часовой (правый), точка справа.
- - три точки коллинеарны.
Операция целочисленная, если все входные координаты целые, - никаких тригонометрий, никаких накапливающихся ошибок плавающей точки. Это одна из причин, почему алгоритм Грэхема устойчив численно и удобен для соревновательного программирования.
Корректность
Утверждение: после прохода стек содержит ровно вершины выпуклой оболочки в порядке против часовой стрелки. Доказательство идёт индукцией по числу обработанных точек.
База: точки - два соседа по углу, - следующая граничная точка против часовой. Шаг: пусть после шагов на стеке - корректная выпуклая оболочка точек в порядке обхода. При добавлении алгоритм откатывает с вершины все точки, образующие правый или нулевой поворот с - то есть все точки, оказавшиеся внутри ломаной . Любая такая точка не может быть вершиной финальной оболочки. После откатов сверху лежит точка, от которой переход к - левый поворот; новый кусок ломаной остаётся выпуклым.
Сортировка по полярному углу гарантирует, что точки добавляются в обход именно против часовой стрелки; опорная точка с минимальным гарантирует, что все полярные углы лежат в диапазоне и сортировка корректно их упорядочивает.
Сложность
Затраты по шагам:
- Поиск опорной точки - одним проходом.
- Сортировка точек по полярному углу - через любой стандартный -сорт (сравнения через cross product, не через
atan2). - Обход и работа со стеком - амортизированно : каждая точка попадает в стек ровно один раз и выкидывается из него не более одного раза. Каждая итерация внутреннего
whileуменьшает размер стека, общее число выкидываний не превышает .
Итого , причём константа доминирует на сортировке. Это оптимально по нижней оценке.
Сравнение с другими алгоритмами
Jarvis march (gift wrapping, 1973) - , где - число вершин оболочки. Идея: каждый раз искать самую «правую» точку среди оставшихся, пока не вернёмся в начало. При быстрее Грэхема; при (точки на окружности) деградирует до . Берут, когда заведомо мало или нужен потоковый вывод вершин по одной.
Andrew's monotone chain (1979) - модификация Грэхема: сортирует по (а не по углу), строит верхнюю и нижнюю полуоболочки и склеивает. Тоже , но численно стабильнее и кодируется короче - в продакшене на C++ и Python чаще используют именно его.
QuickHull (1977) - рекурсивный аналог быстрой сортировки: находит крайние точки по , делит остальные относительно хорды и обрабатывает рекурсивно. Среднее , худший случай . Эффективен на разбросанных данных, плох на точках, плотно лежащих на окружности.
Chan's algorithm (1996) - output-sensitive . Объединяет Грэхема на подмножествах с шагом Jarvis по ним. Лучше всех при заранее неизвестном маленьком , но сложнее в реализации.
Подводные камни
Коллинеарные точки. Когда несколько точек лежат на одном луче из , их полярный угол одинаков и сортировка даёт неоднозначный порядок. Починка - при равных углах сортировать по расстоянию от . Если нужна строгая оболочка (только настоящие вершины), при откатывают предыдущую точку.
Совпадающие точки. Дубликаты убирают заранее, иначе сортировка выдаёт ноль для пары и обход уходит в цикл выкидываний.
Менее трёх точек. Алгоритм требует ; при оболочка - сама точка, при - отрезок. Эти случаи обрабатывают отдельной веткой.
Все точки коллинеарны. Оболочка вырождается в отрезок между двумя крайними; нужна явная проверка, иначе результат зависит от порядка обработки.
Где применяется
- Компьютерная графика. Bounding shape для коллизий, упрощённая граница спрайтов и моделей - компактное «грубое» приближение формы, по которому быстро проверяются пересечения.
- GIS и картография. Минимальная выпуклая область наблюдений: ареал вида, зона покрытия датчиков, граница облака GPS-отметок.
- Кластеризация в ML. Граница кластера через convex hull точек кластера - для визуализации и для проверки «попадает ли новая точка внутрь».
- SVM и оптимизация. Выпуклые оболочки классов - стандартный способ анализа линейной разделимости: если оболочки не пересекаются, классы разделимы.
- Физические движки. Convex hull triangle mesh как broad-phase bounding до точного теста коллизий.
- Олимпиадные задачи. Диаметр оболочки через rotating calipers, минимальный охватывающий прямоугольник, ближайшая пара точек.
Частые ошибки
- Считают полярный угол через
atan2и сравниваютfloat. Числовая ошибка превращает соседние углы в произвольный порядок. Правильно - сравнивать через cross product двух векторов: означает « левее, угол меньше». - Не обрабатывают коллинеарные точки. Алгоритм без явной ветки на может оставить точку в оболочке или выкинуть, как повезёт. Решите заранее: нужна строгая оболочка или допускается коллинеарность на стороне.
- Берут случайную опорную точку. Опорной должна быть гарантированно граничная точка (самая нижняя/левая), иначе сортировка по полярному углу не покрывает всю окружность .
- Сравнивают
crossс нулём какfloat. Если координаты целые - считайте вint64, иначе при больших значениях получите переполнение и неправильный знак. - Забывают сжать дубликаты. Несколько одинаковых точек ломают сортировку и порождают зацикливание на выкидываниях.
FAQ
Чем алгоритм Грэхема отличается от Andrew's monotone chain? Идеи одинаковые - отсортировать точки и собрать оболочку линейным проходом со стеком. Различие в порядке сортировки. Грэхем - по полярному углу относительно опорной точки. Andrew - по координате , строит верхнюю и нижнюю полуоболочки и склеивает. У Andrew проще обработка коллинеарных случаев, у Грэхема приятнее интуиция.
Когда выгоднее использовать Jarvis march вместо Грэхема? Когда заранее известно, что число вершин оболочки мало, скажем . Jarvis работает за , и при таком это - как Грэхем, но с меньшей константой и без сортировки (что удобно при потоковых данных). Если же оболочка может содержать почти все точки - Jarvis деградирует до , и Грэхем выигрывает.
Можно ли построить трёхмерную выпуклую оболочку алгоритмом Грэхема? Прямо - нет. Полярный угол на сфере и стек кандидатов уже не задают однозначного обхода. Для 3D используют QuickHull, incremental hull или divide and conquer - асимптотика лучших алгоритмов для случайных распределений и в худшем случае на специально подобранных входах.
Коротко
Алгоритм Грэхема (1972) строит выпуклую оболочку точек на плоскости за . Шаги: выбор опорной точки с минимальным , сортировка остальных по полярному углу, проход стеком с проверкой поворота через cross product. При левом повороте точка добавляется, при правом - верхушка стека выкидывается. Сложность доминирует сортировкой; обход со стеком - амортизированно . Сравнимые альтернативы - Jarvis march (), Andrew's monotone chain (, проще), QuickHull (среднее ). Применяется в компьютерной графике, GIS, кластеризации, физических движках и олимпиадных задачах.
Читайте также

Алгоритм Чана: как строить выпуклую оболочку за O(n log h)
Алгоритм Чана строит выпуклую оболочку n точек за O(n log h): разбор идеи с угадыванием числа вершин h, комбинацией Graham scan и Jarvis march по шагам.

Алгоритм Рабина-Карпа: поиск подстроки за O(n+m)
Разбираем алгоритм Рабина-Карпа: как полиномиальный хеш и скользящее окно ускоряют поиск подстроки до O(n+m) в среднем, почему бывают ложные совпадения и при чём тут плагиат.

Распределение Фишера критические значения: как искать F-квантили
Распределение Фишера и его критические значения: что такое F-распределение, как читать таблицу критических значений по двум степеням свободы, как применять F-квантили в F-тесте на равенство дисперсий и в дисперсионном анализе.