Przeskocz do treści

Delta mi!

Sortowanie biegunowe a dualizacja

Grzegorz Jakacki

o artykule ...

  • Publikacja w Delcie: sierpień 2013
  • Publikacja elektroniczna: 31-07-2013
  • Autor: Grzegorz Jakacki
    Afiliacja: firma Codility

Jedną z najczęściej wykonywanych operacji w geometrii obliczeniowej jest sortowanie biegunowe (zwane też kątowym) zbioru math punktów na płaszczyźnie względem wybranego punktu. Innymi słowy, chcemy uporządkować punkty math według współrzędnej kątowej w układzie biegunowym zaczepionym w wybranym punkcie math Stosując jeden z efektywnych algorytmów sortowania, operację tę można zrealizować w czasie math

W wielu zastosowaniach musimy jednak pójść o krok dalej i wyznaczyć uporządkowanie biegunowe punktów math najpierw w układzie biegunowym zaczepionym w  math potem zaczepionym w  math itd. Mamy zatem do wykonania math sortowań, co łącznie możemy zrealizować w czasie math  Okazuje się jednak, że istnieje algorytm pozwalający wyznaczyć wszystkie potrzebne porządki biegunowe w czasie math  Jest on ciekawy, jednak dosyć trudny – w tym artykule przedstawimy szkic tego algorytmu.

obrazek

Rys. 1 Proste z math oraz porządek biegunowy punktów względem math

Rys. 1 Proste z math oraz porządek biegunowy punktów względem math

Punkty i proste

Pomyślmy o zbiorze punktów math na płaszczyźnie. Wybierzmy jeden z nich, na przykład math Poprowadźmy proste przez math tak, by połączyć go ze wszystkimi pozostałymi punktami math (Rys. 1). Każdemu z tych punktów odpowiada jedna prosta. Gdybyśmy umieli posortować te proste według kąta nachylenia lub, inaczej mówiąc, według współczynnika kierunkowego math w równaniu prostej math to takie uporządkowanie pozwoliłoby nam uzyskać kolejność biegunową punktów math w układzie o środku w  math

Spójrzmy teraz na wszystkie proste przechodzące przez pary punktów ze zbioru math Jest ich nie więcej niż math Jeśli uda nam się skonstruować algorytm, który dla każdego z punktów posortuje math przechodzących przez niego prostych w sumarycznym czasie lepszym niż math  to dostaniemy lepsze rozwiązanie całego zadania. Algorytm nie może jednak działać szybciej niż math  bo taka jest liczba prostych, które ma posortować. Okazuje się, że algorytm działający w czasie kwadratowym istnieje i opiera się na bardzo ciekawej konstrukcji myślowej zwanej dualizacją.

Dualizacja

Wyobraźmy sobie przekształcenie geometryczne math które punktowi math przyporządkowuje prostą math a prostej math punkt math w następujący sposób:

display-math

czyli punktowi o współrzędnych math odpowiada prosta o równaniu math a prostej o równaniu math odpowiada punkt o współrzędnych math Punkty i proste oraz ich obrazy leżą w płaszczyźnie math Płaszczyznę, z której bierzemy argumenty przekształcenia math nazywamy przestrzenią pierwotną, natomiast płaszczyznę, w której znajdują się jego wartości – przestrzenią dualną. Opisane przekształcenie ma bardzo ciekawą właściwość: punkt math leży na prostej math wtedy i tylko wtedy, gdy prosta math przechodzi przez punkt math

Zbiór punktów math oraz prostych łączących je w pary możemy odwzorować w zbiór prostych math W przestrzeni dualnej proste, które łączą np. math z pozostałymi punktami, odpowiadają punktom przecięć prostej math z prostymi math Od tej własności jest jednak mały wyjątek. Gdy pewne punkty math i  math mają tę samą pierwszą współrzędną, to w przestrzeni dualnej proste math oraz math będą równoległe. Taką sytuacją zajmiemy się jednak za chwilę.

Dotarliśmy zatem do następującego problemu. Danych jest math prostych na płaszczyźnie. Dla każdej prostej należy obliczyć, w jakiej kolejności (względem pierwszej współrzędnej) będą się z nią przecinały pozostałe proste. Z konstrukcji przestrzeni dualnej wynika, że żadna prosta nie będzie pionowa. Znając porządek przecięć dla prostej math w przestrzeni dualnej, możemy odtworzyć porządek prostych przechodzących przez punkt math w przestrzeni pierwotnej, a z niego porządek biegunowy wszystkich punktów względem math

Jak wykonać ostatni z tych kroków? Wystarczy dwa razy przejrzeć proste w niemalejącej kolejności współczynników kierunkowych i przy pierwszym przejściu wypisać tylko punkty leżące na lewo od math (tj. mające mniejszą pierwszą współrzędną), a przy drugim przejściu – wypisać pozostałe. Musimy jeszcze osobno rozważyć wszystkie punkty mające tę samą pierwszą współrzędną co math jednak bardzo łatwo możemy umieścić je w odpowiednim miejscu w porządku biegunowym wokół math Ten proces powtarzamy dla każdego punktu wśród math

obrazek

Rys. 4 Struktura danych u dołu reprezentuje układ odcinków przedstawiony u góry. Został on zbudowany przez otoczenie prostokątną ramką prostych z rysunku 3

Rys. 4 Struktura danych u dołu reprezentuje układ odcinków przedstawiony u góry. Został on zbudowany przez otoczenie prostokątną ramką prostych z rysunku 3

Wyznaczanie przecięć prostych

Spójrzmy na rysunek 4 Przedstawia on strukturę danych, która reprezentuje układ odcinków stykających się jedynie końcami. Co więcej, każdy koniec odcinka jest jednocześnie końcem jakiegoś innego odcinka. Każdy punkt styku reprezentujemy za pomocą listy cyklicznej. Element listy odpowiada końcowi odcinka i oprócz wskaźników do następnika i poprzednika zawiera także wskaźnik do elementu innej listy, który reprezentuje drugi koniec tego samego odcinka. Ponadto elementy listy reprezentującej punkt styku zawierają jego współrzędne. Kolejność elementów na liście jest istotna i musi odpowiadać kolejności występowania odcinków na płaszczyźnie, na przykład być zgodna z kierunkiem ruchu wskazówek zegara.

obrazek

Rys. 5 Fazy dodawania prostej do struktury danych.

Rys. 5 Fazy dodawania prostej do struktury danych.

Opisana struktura jest wariantem tzw. doubly-connected edge list. Pozwala ona szybko dodawać nowe odcinki do istniejącego układu. Zauważmy też, że dzięki wskaźnikom do sąsiedniego odcinka oraz dzięki odpowiedniemu uporządkowaniu elementów na listach cyklicznych, odpowiadających punktom styku, możemy w prosty sposób obchodzić dookoła obszary (ściany) wyznaczone przez układ odcinków. Rysunek 5 przedstawia kolejne fazy dodawania odcinków prostej do układu.

Operacje na strukturze są dosyć intuicyjne. Nas interesuje odpowiedź na pytanie, jaki jest ich czas działania względem math bo od tego zależy złożoność algorytmu. Widać, że nowo powstająca ściana może mieć bardzo wiele krawędzi, nawet liniowo wiele względem math Okazuje się jednak, że sumaryczna liczba krawędzi, które trzeba odwiedzić przy dodawaniu odcinków jednej prostej, jest również rzędu math  Dowód tego faktu pominiemy, choć nie jest bardzo trudny. Opiera się on na obserwacji, że odwiedzane krawędzie można przyporządkować do punktów przecięć przetwarzanej prostej w ten sposób, by na jeden punkt przypadało co najwyżej 10 krawędzi. Zainteresowani dowodem powinni szukać hasła twierdzenie strefowe lub zone theorem.

Powyższy fakt jest kluczowy dla analizy złożoności czasowej algorytmu, bo wynika z niego, że strukturę daje się zbudować w czasie math  Ponadto przejście po kolejnych odcinkach jednej prostej można zrealizować w czasie math Zauważmy, że jeżeli zbiór punktów math  na płaszczyźnie przekształcimy za pomocą dualizacji na zbiór prostych math a te proste otoczymy ramką, to otrzymamy układ odcinków podobny do tego z rysunku 4

Prostokątną ramkę, obejmującą wszystkie punkty przecięcia układu math prostych, możemy obliczyć następująco. Sortujemy (w czasie math) wszystkie proste względem ich współczynników kierunkowych i bierzemy pod uwagę tylko punkty przecięć prostych sąsiadujących w tym porządku oraz punkt przecięcia pierwszej i ostatniej prostej. Wszystkie pozostałe punkty przecięć znajdują się wewnątrz otoczki wypukłej tych punktów.

Dalej, za pomocą opisanego powyżej algorytmu konstrukcji struktury doubly-connected edge list jesteśmy w stanie w czasie math  wyznaczyć kolejność punktów przecięć dla wszystkich prostych. Jeśli teraz dla dowolnego punktu z  math (np. math) zapragniemy wyznaczyć porządek biegunowy pozostałych punktów math to wystarczy odczytać porządek przecięć prostej math z pozostałymi prostymi. Numery prostych w tym porządku wyznaczą kolejność punktów math w porządku biegunowym dookoła math o czym pisaliśmy powyżej.

Za pomocą dualizacji udało nam się przyspieszyć algorytm sortowania biegunowego zbioru math punktów z czasu math  do czasu math Usprawnienie to nie ma dużego znaczenia w przypadku np. zadań opisywanych w Informatycznym Kąciku Olimpijskim, ponieważ jego implementacja jest bardzo skomplikowana, a przyspieszenia raczej nie dałoby się zauważyć na zbiorach danych o stosunkowo niewielkim rozmiarze. Przyspieszanie takich algorytmów ma jednak rację bytu w poważniejszych zastosowaniach geometrii obliczeniowej, takich jak generowanie grafiki lub projektowanie układów scalonych.