Flip-алгоритм для нахождения триангуляции Делоне/диаграммы Вороного

Revision ru14, by VitaliiV, 2025-12-02 19:27:11

Всем привет! Недавно я захотел сдать задачу 102482G - Panda Preserve, и я не смог найти нигде простую реализацию построения триангуляции Делоне.

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

Начнём с нескольких определений и фактов:

Триангуляция — это разбиение некоторого множества точек S на треугольники, при этом внутренние области треугольников не пересекаются.

Триангуляция Делоне — это такое разбиение некоторого множества точек S на треугольники, что внутри окружности каждого треугольника не находится никаких точек из множества.

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

Диаграмма Вороного и триангуляция Делоне являются двойственными графами

Алгоритм построения триангуляции Делоне за $$$O(n^2)$$$

Прежде всего, стоит сказать, что триангуляция Делоне может быть построена значительно быстрее чем за $$$O(n^2)$$$. Подробнее об этом можно прочитать тут. Однако алгоритм фортуна, приведённый тут, слишком сложный для понимания, и написать его за время контеста крайне тяжело.

Реализация за $$$O(n^2)$$$ довольно простая в плане идеи и состоит из 2 шагов.

Шаг 1

Построим произвольную триангуляцию заданного множества точек. Это построение похоже на построение выпуклой оболочки. Среди всех точек множества найдём точку с наименьшей x-координатой, а если таких несколько, то с наименьшей y-координатой. Пусть это будет какая-то точка P. Сдвинем все координаты так, чтобы точка p оказалась в начале координат. Вычисляем полярные углы всех точек относительно точки p для сортировки вокруг нее. Далее будем строить триангуляцию в 2 этапа:

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

На втором этапе будем действовать аналогично алгоритму Грэхема для построения выпуклой оболочки. Каждый раз при добавлении новой точки, необходимо проверить не нарушается ли условие выпуклости. Если нарушается, то мы нашли "впадину", которую нужно заполнить.

Шаг 2

У нас уже есть какая-то триангуляция набора точек, однако она пока что не удовлетворяет критериям триангуляции Делоне. Будем итеративно улучшать нашу триангуляцию. На каждом шаге будем искать четырехугольник ABCD такой, что в нем есть ребро AC и треугольники ABC и ACD не удовлетворяют критерию Делоне. Операцией flip будем называть удаления ребра AC и добавления ребра BD. Например, если изначально у нас были треугольники ABC и ACD, то после применения операции будут треугольники ABD и BCD. Можно доказать, что если исходное разбиение не удовлетворяет критерию Делоне, то flip решает эту проблему. Однако для краткости я не буду приводить это доказательство здесь. Этот процесс повторяется до тех пор, пока все ребра не будут удовлетворять критерию Делоне

Связь с диаграммой Вороного

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

Обобщение Итеративный flip-алгоритм работает за $$$O(n^2)$$$ в худшем случае, так как в худшем случае требуется провести до (O(n)) операций флипа на каждый из $$$O(n)$$$ ребер, которые могут быть подвержены операции flip. Как уже говорилось ранее, существуют значительно более быстрые и точные способы найти триангуляцию Делоне, однако этот алгоритм более просто для понимания и его вполне можно написать за 30-40 минут.

Здесь я привожу реализацию алгоритма, однако хотя этот код и прошёл все тесты задачи 102482G - Panda Preserve, я всё же не стал бы ему доверять и на месте читателя написал бы его сам.

Code
Tags geometry, tutorial

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en10 English VitaliiV 2025-12-03 08:52:29 19 Tiny change: 'am).\n\n**Generalization**\n\nThe ' -> 'am).\n\n**Summary**\n\nThe '
en9 English VitaliiV 2025-12-03 08:06:32 4
en8 English VitaliiV 2025-12-03 07:45:08 12
en7 English VitaliiV 2025-12-03 07:41:32 4
en6 English VitaliiV 2025-12-03 07:41:22 152
en5 English VitaliiV 2025-12-03 07:37:16 46 Tiny change: 'bout this х[here](htt' -> 'bout this [here](htt' (published)
en4 English VitaliiV 2025-12-03 07:32:36 57
en3 English VitaliiV 2025-12-03 07:31:40 7477 Tiny change: 's ABD and BCD. It can b' -> 's ABD and **BCD**. It can b'
en2 English VitaliiV 2025-12-03 07:24:10 102
en1 English VitaliiV 2025-12-03 07:21:34 4608 Initial revision for English translation (saved to drafts)
ru42 Russian VitaliiV 2025-12-02 20:28:55 2 Мелкая правка: ' алгоритм фортуна, пр' -> ' алгоритм Фортуна, пр'
ru41 Russian VitaliiV 2025-12-02 20:26:28 119
ru40 Russian VitaliiV 2025-12-02 20:19:02 4 Мелкая правка: 'о — такое раз' -> 'о — это такое раз'
ru39 Russian VitaliiV 2025-12-02 20:18:25 4
ru38 Russian VitaliiV 2025-12-02 20:18:12 13 Мелкая правка: ' операций флипа на каждый' -> ' операций flip на каждый'
ru37 Russian VitaliiV 2025-12-02 20:16:37 4
ru36 Russian VitaliiV 2025-12-02 20:16:25 44
ru35 Russian VitaliiV 2025-12-02 20:14:46 22
ru34 Russian VitaliiV 2025-12-02 20:13:45 80
ru33 Russian VitaliiV 2025-12-02 20:11:38 6 Мелкая правка: 'овести до \(O(n)\) операций ' -> 'овести до $O(n)$ операций '
ru32 Russian VitaliiV 2025-12-02 20:04:15 2 Мелкая правка: 'бщение**\nИтератив' -> 'бщение**\n\nИтератив'
ru31 Russian VitaliiV 2025-12-02 20:01:31 0 (опубликовано)
ru30 Russian VitaliiV 2025-12-02 20:01:03 1 Мелкая правка: ' проверить не наруша' -> ' проверить, не наруша'
ru29 Russian VitaliiV 2025-12-02 20:00:29 45
ru28 Russian VitaliiV 2025-12-02 19:59:10 255
ru27 Russian VitaliiV 2025-12-02 19:45:44 1 Мелкая правка: ' построить не исполь' -> ' построить, не исполь'
ru26 Russian VitaliiV 2025-12-02 19:44:58 1 Мелкая правка: 'зать о том что такое' -> 'зать о том, что такое'
ru25 Russian VitaliiV 2025-12-02 19:43:20 26
ru24 Russian VitaliiV 2025-12-02 19:42:43 125
ru23 Russian VitaliiV 2025-12-02 19:38:27 79
ru22 Russian VitaliiV 2025-12-02 19:37:44 116
ru21 Russian VitaliiV 2025-12-02 19:35:44 74
ru20 Russian VitaliiV 2025-12-02 19:32:56 2 Мелкая правка: 'полнить.\n![ ](htt' -> 'полнить.\n\n![ ](htt'
ru19 Russian VitaliiV 2025-12-02 19:32:40 173
ru18 Russian VitaliiV 2025-12-02 19:32:14 160
ru17 Russian VitaliiV 2025-12-02 19:31:58 2 Мелкая правка: '/ris1.png)На рисунке' -> '/ris1.png)\nНа рисунке'
ru16 Russian VitaliiV 2025-12-02 19:31:39 82
ru15 Russian VitaliiV 2025-12-02 19:31:23 238 Мелкая правка: 'полнить.\n\n![](https://' -> 'полнить.\n![ ](https://'
ru14 Russian VitaliiV 2025-12-02 19:27:11 382
ru13 Russian VitaliiV 2025-12-02 19:04:05 25
ru12 Russian VitaliiV 2025-12-02 19:03:40 8
ru11 Russian VitaliiV 2025-12-02 18:55:34 602
ru10 Russian VitaliiV 2025-12-02 18:50:10 7 Мелкая правка: '0%D1%84)\n[cut]\n\n\n**Ал' -> '0%D1%84)\n\n\n**Ал'
ru9 Russian VitaliiV 2025-12-02 18:49:59 4 Мелкая правка: 'n[cut]\n\n**Алго' -> 'n[cut]\n\n\n**Алго'
ru8 Russian VitaliiV 2025-12-02 18:49:42 8407 Мелкая правка: 'а $O(n^2)$в худшем с' -> 'а $O(n^2)$ в худшем с'
ru7 Russian VitaliiV 2025-12-02 18:38:24 784
ru6 Russian VitaliiV 2025-12-02 18:26:53 1490 Мелкая правка: '[0, 2 $\pi \)' -> '[0, 2 $\pi\)'
ru5 Russian VitaliiV 2025-12-02 18:07:01 5 Мелкая правка: '0%D1%84)\n\n**Свойст' -> '0%D1%84)\n[cut]\n**Свойст'
ru4 Russian VitaliiV 2025-12-02 18:06:43 376
ru3 Russian VitaliiV 2025-12-02 18:02:15 811 Мелкая правка: 'роного за O(n^2)\nТриангул' -> 'роного за $O(n^2)$\nТриангул'
ru2 Russian VitaliiV 2025-12-02 17:48:11 448
ru1 Russian VitaliiV 2025-12-02 17:38:34 43 Первая редакция (сохранено в черновиках)