Перейти до основного вмісту

Сума Мінковського опуклих многокутників

Означення​

Розглянемо дві множини точок AA і BB на площині. Сума Мінковського A+BA + B означається як {a+b∣a∈A,b∈B}\{a + b| a \in A, b \in B\}. Тут ми розглядатимемо випадок, коли AA і BB складаються з опуклих многокутників PP і QQ разом з їхніми внутрішніми областями. Упродовж цієї статті ми ототожнюватимемо многокутники з упорядкованими послідовностями їхніх вершин, щоб позначення на кшталт ∣P∣|P| чи PiP_i мали сенс. Виявляється, що сума опуклих многокутників PP і QQ є опуклим многокутником, який має щонайбільше ∣P∣+∣Q∣|P| + |Q| вершин.

Коли підходить цей алгоритм?
  • Обидва доданки — це саме опуклі многокутники (а не довільні множини точок чи неопуклі фігури)?
  • Вам справді потрібна поточкова сума {a+b}\{a + b\} — наприклад, для перевірки колізій рухомих опуклих тіл чи редукції відстані між многокутниками до відстані до точки?
  • Вершини многокутників задано в порядку обходу (або їх можна впорядкувати), щоб скористатися злиттям ребер за полярним кутом за O(∣P∣+∣Q∣)O(|P| + |Q|)?

Алгоритм​

Тут ми вважаємо, що вершини многокутників пронумеровані циклічно, тобто P∣P∣=P0, Q∣Q∣=Q0P_{|P|} = P_0,\ Q_{|Q|} = Q_0 і так далі.

Оскільки розмір суми лінійно залежить від розмірів початкових многокутників, ми маємо прагнути знайти алгоритм лінійного часу. Припустимо, що обидва многокутники впорядковані проти годинникової стрілки. Розглянемо послідовності ребер {PiPi+1→}\{\overrightarrow{P_iP_{i+1}}\} і {QjQj+1→}\{\overrightarrow{Q_jQ_{j+1}}\}, упорядковані за полярним кутом. Ми стверджуємо, що послідовність ребер P+QP + Q можна отримати, злиявши ці дві послідовності зі збереженням порядку за полярним кутом і замінивши послідовні співнапрямлені вектори на їхню суму. Прямолінійне застосування цієї ідеї дає алгоритм лінійного часу, проте відновлення вершин P+QP + Q з послідовності сторін потребує багаторазового додавання векторів, що може спричинити небажані проблеми з точністю, якщо ми працюємо з координатами у форматі з рухомою комою, тому ми опишемо невелику модифікацію цієї ідеї.

Спершу ми маємо переупорядкувати вершини так, щоб перша вершина кожного многокутника мала найменшу y-координату (якщо таких вершин декілька, оберемо ту, що має найменшу x-координату). Після цього сторони обох многокутників стануть відсортованими за полярним кутом, тож сортувати їх вручну не потрібно. Тепер ми створюємо два вказівники ii (вказує на вершину PP) і jj (вказує на вершину QQ), обидва спочатку дорівнюють 0. Ми повторюємо наступні кроки, доки i<∣P∣i < |P| або j<∣Q∣j < |Q|.

  1. Додаємо Pi+QjP_i + Q_j до P+QP + Q.

  2. Порівнюємо полярні кути PiPi+1→\overrightarrow{P_iP_{i + 1}} і QjQj+1→\overrightarrow{Q_jQ_{j+1}}.

  3. Збільшуємо вказівник, який відповідає меншому куту (якщо кути рівні, збільшуємо обидва).

Візуалізація​

Ось гарна візуалізація, яка може допомогти вам зрозуміти, що відбувається.

Visual

Відстань між двома многокутниками​

Одне з найпоширеніших застосувань суми Мінковського — обчислення відстані між двома опуклими многокутниками (або просто перевірка того, чи вони перетинаються). Відстань між двома опуклими многокутниками PP і QQ означається як min⁡a∈P,b∈Q∣∣a−b∣∣\min\limits_{a \in P, b \in Q} ||a - b||. Можна зауважити, що відстань завжди досягається між двома вершинами або вершиною та ребром, тож ми можемо легко знайти відстань за O(∣P∣∣Q∣)O(|P||Q|). Проте завдяки кмітливому використанню суми Мінковського ми можемо зменшити складність до O(∣P∣+∣Q∣)O(|P| + |Q|).

Якщо ми відобразимо QQ відносно точки (0,0)(0, 0), отримавши многокутник −Q-Q, задача зводиться до знаходження найменшої відстані між точкою з P+(−Q)P + (-Q) і (0,0)(0, 0). Ми можемо знайти цю відстань за лінійний час за допомогою наступної ідеї. Якщо (0,0)(0, 0) лежить усередині многокутника або на його межі, відстань дорівнює 00, інакше відстань досягається між (0,0)(0, 0) і деякою вершиною або ребром многокутника. Оскільки суму Мінковського можна обчислити за лінійний час, ми отримуємо алгоритм лінійного часу для знаходження відстані між двома опуклими многокутниками.

Реалізація​

Нижче наведено реалізацію суми Мінковського для многокутників з цілочисловими точками. Зауважте, що в цьому випадку всі обчислення можна виконувати в цілих числах, оскільки замість обчислення полярних кутів і безпосереднього їх порівняння ми можемо дивитися на знак векторного добутку двох векторів.

struct pt{
long long x, y;
pt operator + (const pt & p) const {
return pt{x + p.x, y + p.y};
}
pt operator - (const pt & p) const {
return pt{x - p.x, y - p.y};
}
long long cross(const pt & p) const {
return x * p.y - y * p.x;
}
};

void reorder_polygon(vector<pt> & P){
size_t pos = 0;
for(size_t i = 1; i < P.size(); i++){
if(P[i].y < P[pos].y || (P[i].y == P[pos].y && P[i].x < P[pos].x))
pos = i;
}
rotate(P.begin(), P.begin() + pos, P.end());
}

vector<pt> minkowski(vector<pt> P, vector<pt> Q){
// перша вершина має бути найнижчою
reorder_polygon(P);
reorder_polygon(Q);
// ми маємо забезпечити циклічну індексацію
P.push_back(P[0]);
P.push_back(P[1]);
Q.push_back(Q[0]);
Q.push_back(Q[1]);
// основна частина
vector<pt> result;
size_t i = 0, j = 0;
while(i < P.size() - 2 || j < Q.size() - 2){
result.push_back(P[i] + Q[j]);
auto cross = (P[i + 1] - P[i]).cross(Q[j + 1] - Q[j]);
if(cross >= 0 && i < P.size() - 2)
++i;
if(cross <= 0 && j < Q.size() - 2)
++j;
}
return result;
}

Задачі​