Дано множество точек в координатной плоскости; предложите критерии и алгоритмы построения выпуклой оболочки и обсудите, как выбор алгоритма меняется с ростом числа точек и с требованиями по времени и памяти
Критерии (определения и проверки) - Выпуклая оболочка множества точек SSS — наименьшее выпуклое множество, содержащее SSS. Эквивалентно: пересечение всех выпуклых множеств, содержащих SSS, или множество всех выпуклых комбинаций точек из SSS. - Точка ppp лежит в выпуклой оболочке многогранника (многоугольника) тогда и только тогда, когда ее можно представить как невязко-отрицательную комбинацию вершин с суммой коэффициентов 111. - Для проверки ориентации (левый/правый поворот) трех точек A(ax,ay),B(bx,by),C(cx,cy)A(a_x,a_y),B(b_x,b_y),C(c_x,c_y)A(ax,ay),B(bx,by),C(cx,cy) используется векторное (кросс) произведение: cross(A,B,C)=(bx−ax)(cy−ay)−(by−ay)(cx−ax).
\operatorname{cross}(A,B,C)=(b_x-a_x)(c_y-a_y)-(b_y-a_y)(c_x-a_x). cross(A,B,C)=(bx−ax)(cy−ay)−(by−ay)(cx−ax).
Знак этого выражения даёт направление поворота. Для выпуклого (ориентированного против часовой) многоугольника все последовательные тройки должны иметь cross≥0\operatorname{cross}\ge 0cross≥0 (или ≤0\le 0≤0 в зависимости от ориентации), с учётом коллинеарности по требованию включения/исключения границ. Основные алгоритмы (коротко, с идеей и сложностью) - Джарвис (Gift wrapping) - Идея: стартовая экстремальная точка, итеративно выбира самая «левую» относительно последней добавленной -> обводит оболочку. - Сложность времени: O(nh)\displaystyle O(nh)O(nh), где hhh — число вершин оболочки. - Память: O(1)\displaystyle O(1)O(1) дополнительной (помимо хранения точек), результат в O(h)O(h)O(h). - Применимость: хорош для малого hhh (тонкая оболочка), плох при большом hhh. - Грэм (Graham scan) - Идея: выбрать опорную точку (низшая по yyy), отсортировать остальные по полярному углу, проход стеком исключая правые повороты. - Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn) из-за сортировки; проход — O(n)\displaystyle O(n)O(n). - Память: O(n)\displaystyle O(n)O(n) для сортировки, стек O(h)\displaystyle O(h)O(h). - Применимость: простой, надёжен при общем случае. - Andrew / Monotone chain - Идея: сортировать точки по xxx (и по yyy для одинаковых xxx), строить нижнюю и верхнюю цепи монотонным методом (аналог стековой проверки поворотов). - Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn); память: O(n)\displaystyle O(n)O(n) (можно делать in-place). - Практически предпочтителен за счёт простоты и стабильности. - Quickhull - Идея: рекурсивно делит множества по опорной хорде, отбирает внешние точки (аналог quicksort). - Средняя сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn), худшая: O(n2)\displaystyle O(n^2)O(n2). - Память: рекурсивная, обычно O(n)\displaystyle O(n)O(n). - Практически быстрый на случайных данных; осторожно для специально выстроенных входов. - Divide and Conquer (в т.ч. Kirkpatrick–Seidel) - Идея: разделить по координате, рекурсивно найти оболочки и слить двумя выпуклыми цепями. - Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn); есть варианты с гарантией лучшего по hhh. - Используется для параллелизации и теоретических границ. - Chan’s algorithm - Идея: комбинирует грубые группы и Jarvis, даёт оптимальную зависимость от hhh. - Сложность: O(nlogh)\displaystyle O(n\log h)O(nlogh) (лучше, когда h≪nh\ll nh≪n). - Память: O(n)\displaystyle O(n)O(n). - Применимость: когда ожидается маленькое число вершин оболочки. - Инкрементальные и случайные алгоритмы - Инкрементально вставляют точки, поддерживают текущую оболочку; простой вариант O(n2)\displaystyle O(n^2)O(n2), рандомизированные варианты дают O(nlogn)\displaystyle O(n\log n)O(nlogn) в среднем. - Полезны для динамических/онлайн задач; для полноценной динамики существуют сложные структуры с логарифмическими апдейтами. Выбор алгоритма в зависимости от nnn, hhh, требований по времени и памяти - Малые nnn (nnn до десятков тысяч): алгоритмы O(nlogn)\displaystyle O(n\log n)O(nlogn) (Monotone chain, Graham) — просты и быстры на практике. - Когда hhh существенно меньше nnn: используйте алгоритмы с зависимостью от hhh — Jarvis если hhh очень мал, Chan’s для гарантированно O(nlogh)\displaystyle O(n\log h)O(nlogh). - Для очень больших nnn и ограниченной памяти: Monotone chain in-place или внешняя сортировка + объединение; для потоковой обработки — онлайн/инкрементальные структуры. - Для практической скорости на «случайных» данных: Quickhull часто очень быстрый; для худших случаев (вырожденные распределения, кольца) предпочтительны Graham/Andrew/Chan с гарантией. - Для параллельных или распределённых задач: divide-and-conquer легко распараллеливается (параллельно сортировка и слияние оболочек). - Для численной устойчивости: важен надёжный предикат ориентации; при целых координатах используйте целочисленные 64-битные вычисления или exact arithmetic; при вещественных — использовать расширенную точность/кораровку. Практические замечания - Обработка вырожденных случаев: коллинеарные точки — решать, включать ли внутренние точки границы (обычно исключают). - Предварительная фильтрация: удаление внутренних точек (например, минимальный/максимальный по x/y) может ускорить на больших наборах. - Реализация: Monotone chain — компактна и легко отлаживается; Quickhull — требует аккуратной реализации для устойчивости. Краткая сводка сложностей (время / память) - Jarvis: O(nh)\displaystyle O(nh)O(nh) / O(1)\displaystyle O(1)O(1) доп. - Graham: O(nlogn)\displaystyle O(n\log n)O(nlogn) / O(n)\displaystyle O(n)O(n)
- Monotone chain: O(nlogn)\displaystyle O(n\log n)O(nlogn) / O(n)\displaystyle O(n)O(n) (можно in-place) - Quickhull: среднее O(nlogn)\displaystyle O(n\log n)O(nlogn), худшее O(n2)\displaystyle O(n^2)O(n2) / O(n)\displaystyle O(n)O(n)
- Chan: O(nlogh)\displaystyle O(n\log h)O(nlogh) / O(n)\displaystyle O(n)O(n) Если нужно — могу привести компактный псевдокод для выбранного алгоритма или советы по реализации и устойчивым предикатам.
- Выпуклая оболочка множества точек SSS — наименьшее выпуклое множество, содержащее SSS. Эквивалентно: пересечение всех выпуклых множеств, содержащих SSS, или множество всех выпуклых комбинаций точек из SSS.
- Точка ppp лежит в выпуклой оболочке многогранника (многоугольника) тогда и только тогда, когда ее можно представить как невязко-отрицательную комбинацию вершин с суммой коэффициентов 111.
- Для проверки ориентации (левый/правый поворот) трех точек A(ax,ay),B(bx,by),C(cx,cy)A(a_x,a_y),B(b_x,b_y),C(c_x,c_y)A(ax ,ay ),B(bx ,by ),C(cx ,cy ) используется векторное (кросс) произведение:
cross(A,B,C)=(bx−ax)(cy−ay)−(by−ay)(cx−ax). \operatorname{cross}(A,B,C)=(b_x-a_x)(c_y-a_y)-(b_y-a_y)(c_x-a_x).
cross(A,B,C)=(bx −ax )(cy −ay )−(by −ay )(cx −ax ). Знак этого выражения даёт направление поворота. Для выпуклого (ориентированного против часовой) многоугольника все последовательные тройки должны иметь cross≥0\operatorname{cross}\ge 0cross≥0 (или ≤0\le 0≤0 в зависимости от ориентации), с учётом коллинеарности по требованию включения/исключения границ.
Основные алгоритмы (коротко, с идеей и сложностью)
- Джарвис (Gift wrapping)
- Идея: стартовая экстремальная точка, итеративно выбира самая «левую» относительно последней добавленной -> обводит оболочку.
- Сложность времени: O(nh)\displaystyle O(nh)O(nh), где hhh — число вершин оболочки.
- Память: O(1)\displaystyle O(1)O(1) дополнительной (помимо хранения точек), результат в O(h)O(h)O(h).
- Применимость: хорош для малого hhh (тонкая оболочка), плох при большом hhh.
- Грэм (Graham scan)
- Идея: выбрать опорную точку (низшая по yyy), отсортировать остальные по полярному углу, проход стеком исключая правые повороты.
- Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn) из-за сортировки; проход — O(n)\displaystyle O(n)O(n).
- Память: O(n)\displaystyle O(n)O(n) для сортировки, стек O(h)\displaystyle O(h)O(h).
- Применимость: простой, надёжен при общем случае.
- Andrew / Monotone chain
- Идея: сортировать точки по xxx (и по yyy для одинаковых xxx), строить нижнюю и верхнюю цепи монотонным методом (аналог стековой проверки поворотов).
- Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn); память: O(n)\displaystyle O(n)O(n) (можно делать in-place).
- Практически предпочтителен за счёт простоты и стабильности.
- Quickhull
- Идея: рекурсивно делит множества по опорной хорде, отбирает внешние точки (аналог quicksort).
- Средняя сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn), худшая: O(n2)\displaystyle O(n^2)O(n2).
- Память: рекурсивная, обычно O(n)\displaystyle O(n)O(n).
- Практически быстрый на случайных данных; осторожно для специально выстроенных входов.
- Divide and Conquer (в т.ч. Kirkpatrick–Seidel)
- Идея: разделить по координате, рекурсивно найти оболочки и слить двумя выпуклыми цепями.
- Сложность: O(nlogn)\displaystyle O(n\log n)O(nlogn); есть варианты с гарантией лучшего по hhh.
- Используется для параллелизации и теоретических границ.
- Chan’s algorithm
- Идея: комбинирует грубые группы и Jarvis, даёт оптимальную зависимость от hhh.
- Сложность: O(nlogh)\displaystyle O(n\log h)O(nlogh) (лучше, когда h≪nh\ll nh≪n).
- Память: O(n)\displaystyle O(n)O(n).
- Применимость: когда ожидается маленькое число вершин оболочки.
- Инкрементальные и случайные алгоритмы
- Инкрементально вставляют точки, поддерживают текущую оболочку; простой вариант O(n2)\displaystyle O(n^2)O(n2), рандомизированные варианты дают O(nlogn)\displaystyle O(n\log n)O(nlogn) в среднем.
- Полезны для динамических/онлайн задач; для полноценной динамики существуют сложные структуры с логарифмическими апдейтами.
Выбор алгоритма в зависимости от nnn, hhh, требований по времени и памяти
- Малые nnn (nnn до десятков тысяч): алгоритмы O(nlogn)\displaystyle O(n\log n)O(nlogn) (Monotone chain, Graham) — просты и быстры на практике.
- Когда hhh существенно меньше nnn: используйте алгоритмы с зависимостью от hhh — Jarvis если hhh очень мал, Chan’s для гарантированно O(nlogh)\displaystyle O(n\log h)O(nlogh).
- Для очень больших nnn и ограниченной памяти: Monotone chain in-place или внешняя сортировка + объединение; для потоковой обработки — онлайн/инкрементальные структуры.
- Для практической скорости на «случайных» данных: Quickhull часто очень быстрый; для худших случаев (вырожденные распределения, кольца) предпочтительны Graham/Andrew/Chan с гарантией.
- Для параллельных или распределённых задач: divide-and-conquer легко распараллеливается (параллельно сортировка и слияние оболочек).
- Для численной устойчивости: важен надёжный предикат ориентации; при целых координатах используйте целочисленные 64-битные вычисления или exact arithmetic; при вещественных — использовать расширенную точность/кораровку.
Практические замечания
- Обработка вырожденных случаев: коллинеарные точки — решать, включать ли внутренние точки границы (обычно исключают).
- Предварительная фильтрация: удаление внутренних точек (например, минимальный/максимальный по x/y) может ускорить на больших наборах.
- Реализация: Monotone chain — компактна и легко отлаживается; Quickhull — требует аккуратной реализации для устойчивости.
Краткая сводка сложностей (время / память)
- Jarvis: O(nh)\displaystyle O(nh)O(nh) / O(1)\displaystyle O(1)O(1) доп.
- Graham: O(nlogn)\displaystyle O(n\log n)O(nlogn) / O(n)\displaystyle O(n)O(n) - Monotone chain: O(nlogn)\displaystyle O(n\log n)O(nlogn) / O(n)\displaystyle O(n)O(n) (можно in-place)
- Quickhull: среднее O(nlogn)\displaystyle O(n\log n)O(nlogn), худшее O(n2)\displaystyle O(n^2)O(n2) / O(n)\displaystyle O(n)O(n) - Chan: O(nlogh)\displaystyle O(n\log h)O(nlogh) / O(n)\displaystyle O(n)O(n)
Если нужно — могу привести компактный псевдокод для выбранного алгоритма или советы по реализации и устойчивым предикатам.