Wstęp do Grafiki Komputerowej i Geometrii Obliczeniowej zima 16/17 Jakub Maksymiuk
Plan 1 Line segment intersection Problem 1 - analiza Algorytm naiwny Algorytm lepszy - Bentley Ottmann - przegląd Algorytm lepszy - Bentley Ottmann - struktury danych
Line segment intersection Motywacja - GIS (Geographic Information System): Mamy dwie mapy: dróg i rzek. Szukamy: mostów. Mamy dwie mapy: granice miast i lasów. Szukamy: szukamy lasów w miastach.
Line segment intersection Uproszczenie 1: Mapy składają się z odcinków.
Line segment intersection Uproszczenie 1: Mapy składają się z odcinków. Problem 1: Dla danego zbioru odcinków S podać punkty przecięcia oraz pary odcinków przecinających się.
Line segment intersection Uproszczenie 1: Mapy składają się z odcinków. Problem 1: Dla danego zbioru odcinków S podać punkty przecięcia oraz pary odcinków przecinających się. Problem2 : Dla danego zbioru wielokątów S podać punkty przecięcia oraz pary wielokątów (i odcinków) przecinających się.
Line segment intersection Uproszczenie 1: Mapy składają się z odcinków. Problem 1: Dla danego zbioru odcinków S podać punkty przecięcia oraz pary odcinków przecinających się. Problem2 : Dla danego zbioru wielokątów S podać punkty przecięcia oraz pary wielokątów (i odcinków) przecinających się. Uproszczenie 2: Zamiast dwóch zbiorów będziemy rozpatrywać jeden, po rozwiazaniu problemu trzeba odfiltrować szukane odpowiedzi.
Rysunek: Prosty problem
Rysunek: Też łatwy? Odcinki: 100, Przecięć: 1077, Max: 4950
Line segment intersection Jak reprezentować odcinki? Czy odcinki są domknięte? Jak sprawdzić czy dwa odcinki się przecinają? Jeżeli wiemy, że dwa odcinki się przecinają, jak wyliczyć punkt przecięcia? Co zrobić z odcinkami równoległymi?
Line segment intersection Odcinki reprezentujemy jako ich końce. Odcinki są domkniete. Problem testowania i znajdowania punktów przecięcia ćwiczenia, teraz przyjmujemy, że umiemy. Odcinki równoległe później.
Line segment intersection Algorithm 1 NaiveSegInt(S) Input: Zbiór odcinków S Output: Lista puntów przecięcia wraz o odcinkami. 1: for każda para odcinków do 2: if Przecinają się then 3: Znajdź punkt przecięcia. Dodaj punkt i odcinki do listy. 4: end if 5: end for
Line segment intersection Trzeba wykonać (n 2 n)/2 testów, złożoność O(n 2 ) Nawet jeżeli żadne dwa odcinki się nie przecinają. Zauważmy, że algorytm nie wymaga dodatkowej pamięci do przechowywania danych.
Line segment intersection Trzeba wykonać (n 2 n)/2 testów, złożoność O(n 2 ) Nawet jeżeli żadne dwa odcinki się nie przecinają. Zauważmy, że algorytm nie wymaga dodatkowej pamięci do przechowywania danych. zauważmy, że w pesymistycznym przypadku (wszystkie odcinki się przecinają) (n 2 n)/2 testów jest konieczne. W praktyce: dużo odcinków się nie przecina. Chcielibyśmy algorytm, działający szybciej, gdy jest mało przecięć. (czyli output-sensitive)
Line segment intersection Obserwacja 1 Jeżeli odcinki są daleko, to się nie przecinają. Jeżeli rzuty odcinków na oś Y nie zachodzą na siebie, to odcinki nie przecinają się. Wystarczy zatem testować pary odcinków, których rzuty na siebie zachodzą.
Line segment intersection Obserwacja 1 Jeżeli odcinki są daleko, to się nie przecinają. Jeżeli rzuty odcinków na oś Y nie zachodzą na siebie, to odcinki nie przecinają się. Wystarczy zatem testować pary odcinków, których rzuty na siebie zachodzą. Jak znaleźć takie pary?
Line segment intersection
Line segment intersection Sweeping line, plane sweeping algorithm Wyobraźmy sobie prostą l : y = a przesuwającą się z góry na dół. status lista odcinków przecinających l
Line segment intersection Sweeping line, plane sweeping algorithm Wyobraźmy sobie prostą l : y = a przesuwającą się z góry na dół. status lista odcinków przecinających l status zmienia się podczas przesuwania event point punkt w którym zmienia się status
Line segment intersection 1: przetważaj event point od góry 2: if event point = górny koniec odcinka then 3: dodaj odcinek do status 4: testuj przecięcie z odcinkami w status 5: else // event point = dolny koniec odcinka 6: usuń odcinek z status 7: end if
Line segment intersection 1: przetważaj event point od góry 2: if event point = górny koniec odcinka then 3: dodaj odcinek do status 4: testuj przecięcie z odcinkami w status 5: else // event point = dolny koniec odcinka 6: usuń odcinek z status 7: end if W tym wypadku nadal możemy być zmuszeni wykonać na darmo wszystkie testy.
Line segment intersection ograniczymy się do testowania tylko odcinków sąsiadujących w poziomie zmiana statusu oznacza zmianę sąsiadów status uporządkowana od lewej lista odcinków przecinających l nowy typ event point: punkt przecięcia
Line segment intersection 1: przetważaj event point w porządku od góry, od lewej 2: if event point = górny koniec odcinka then 3: dodaj odcinek do status 4: testuj przecięcie z sąsiadami 5: else if event point = dolny koniec odcinka then 6: usuń odcinek z status 7: przetestuj nowych sąsiadów 8: else // event point = punkt przecięcia 9: zamień sąsiadów i przetestuj 10: end if 11: if jest przecięcie poniżej bierzacego punktu then 12: wstaw nowy event point, jeżeli jeszcze go nie ma, tak aby zachować porządek od góry, od lewej 13: end if
Line segment intersection event point = górny koniec odcinka
Line segment intersection event point = punkt przecięcia
Line segment intersection event point = dolny koniec odcinka
Line segment intersection Algorytm jest prawidłowy przy założeniu, że nie ma odcinków poziomych dwa odcinki przecinają się w conajwyżej jednym punkcie przecinają się tylko dwa odcinki na raz
Line segment intersection Trick na odcinki poziome Przyjmujemy, że lewy koniec jest końcem górnym. W ten sposób zachowujemy porządek: od góry, od lewej.
Line segment intersection Podczas wykonywania tego algorytmu musimy przechowywać dane: event points i status Szybkość działania algorytmu zależy od szybkości operacji na strukturach danych, np.: wstawiania, wyszukiwania, usuwania. Czas wykonania operacji na strukturach danych jest kluczowy.
Line segment intersection Event queue Q Ta struktura przechowuje event points, wraz z odcinkami które ich dotyczą. Potrzebujemy: relacji porządkującej: od góry, od lewej operacji wstawiania, usuwania (dokładniej - fetch), wyszukiwania Taką strukturę (kolejkę) można zaimplementowac korzystając z balanced Binary Search Tree, z czasem wstawiania i usuwania O(log m), m - liczba elementów kolejki
Line segment intersection Status T T struktura przechowuje odcinki przecinające l w kolejności od lewej w taki sposób aby można było łatwo(szybko) podać sąsiadów. Potrzebujemy: realcji porządkujacej: od lewej Szybkiego wstawiania, usuwania, wyszukiwania Ponownie, możemy zaimplementować T za pomocą BST.
Line segment intersection OBRAZEK dla Q oraz T
Line segment intersection Korzystanie z T Liście przechowują odcinki Węzły przechowją najbardziej prawy liść z lewego poddrzewa wyszukiwanie sąsiadów: OBRAZEK
Line segment intersection Algorithm 2 SegInt(S) Input: Zbiór odcinków S Output: Lista puntów przecięcia wraz o odcinkami. 1: Utwórz Q i dodaj końce odcinków, 2: Utwórz T 3: while Q jest niepuste do 4: Pobierz (fetch) następny event point p z Q 5: HandleEventPoint(p) 6: end while Algorytm HandleEventPoint został naszkicowany wcześniej (bez uwzględnienia przyadków zdegenerowanych)
Line segment intersection Podany algorytm (nawet po uwzględnieniu przypadków zdegenerowanych) działa w czasie O((n + I ) log n), używa O(n) pamięci, gdzie I jest liczbą przecięć.
Plan 2 Line segment intersection 2 Analiza Doubly-connected edge list Problem jeszcze raz Algorytm Algorytm - nowa lista wierzchołków i pół-krawędzi Algorytm - nowa lista ścian Algorytm - podsumowanie Zastosowania
Analiza Problem2 (przypomnienie): Dla danego zbioru wielokątów S podać punkty przecięcia oraz pary wielokątów (i odcinków) przecinających się.
Analiza Problem2 (przypomnienie): Dla danego zbioru wielokątów S podać punkty przecięcia oraz pary wielokątów (i odcinków) przecinających się. Uproszczenie Zamiast zbioru wielokątów rozważamy graf planarny, a dokładnie jego zanurzenie w płaszczyźnę. Krawędzie zanurzamy jako linie proste. Takie zanurzenie indukuje podział płaszczyzny na wielokąty.
Analiza Jak reprezentować taki podział płaszczyzny 1 Możliwość pierwsza, przechowywać jadną z trzech poniższych lista wierzchołków lista krawędzi lista ścian
Analiza Jak reprezentować taki podział płaszczyzny 1 Możliwość pierwsza, przechowywać jadną z trzech poniższych lista wierzchołków lista krawędzi lista ścian Cieżko otrzymać informacje topologiczne, np.: co z czym graniczy, brzeg danego obszaru itd.
Analiza Jak reprezentować taki podział płaszczyzny 2 Możliwość druga listy: wierzchołków, krawędzi, ścian lista informacji topologicznych, tzn. co z czym się łączy i graniczy
Analiza Jak reprezentować taki podział płaszczyzny 2 Możliwość druga listy: wierzchołków, krawędzi, ścian lista informacji topologicznych, tzn. co z czym się łączy i graniczy Trzeba wybrać strukturę danych, która przechowuje powyższe informacje zajmuje jak najmniejszą ilość pamięci jest wydajna
Doubly-connected edge list Będziemy przechowywać trzy listy: lista wierzchołków listę krawędzi, a dokładniej pół-krawędzi listę ścian każdy element tych list będzie zawierać informacje topologiczne
Doubly-connected edge list Reprezentacja krawędzi Każda krawedź jest brzegiem dwóch ścian. Każdą krawędź traktujemy jako dwie pół-krawędzie. Każda z nich ogranicza jedną ścianę.
Doubly-connected edge list Reprezentacja krawędzi Każda krawedź jest brzegiem dwóch ścian. Każdą krawędź traktujemy jako dwie pół-krawędzie. Każda z nich ogranicza jedną ścianę. orientację pół-krawędzi - ściana którą ogranicza pół-krawędź leży po lewej stronie. mamy w ten sposób zdefiniowany początkowy i końcowy wierzchołek pół-krawędzi
Doubly-connected edge list Reprezentacja krawędzi Każda krawedź jest brzegiem dwóch ścian. Każdą krawędź traktujemy jako dwie pół-krawędzie. Każda z nich ogranicza jedną ścianę. orientację pół-krawędzi - ściana którą ogranicza pół-krawędź leży po lewej stronie. mamy w ten sposób zdefiniowany początkowy i końcowy wierzchołek pół-krawędzi następnik e jest to pół-krawędź zaczynajaca się w końcowym wierzchołku e, ograniczajaca tą samą ścianę.
Doubly-connected edge list Dla pół-krawędzi (half-edge) e = v w definiujemy Origin( e ) = v Twin( e ) = w v Destination( e ) = Origin(Twin( e ) Next( e ) Prev( e ) IncidentFace(e)
Doubly-connected edge list Reprezentacja ściany Aby powiedzieć jaki jest zewnętrzny brzeg danej ściany, wystarczy podać jedną pół-krawędź z jej brzegu, a następnie przegladać Next().
Doubly-connected edge list Reprezentacja ściany Aby powiedzieć jaki jest zewnętrzny brzeg danej ściany, wystarczy podać jedną pół-krawędź z jej brzegu, a następnie przegladać Next(). Sciany mogą mieć dziury. Aby powiedzieć jaki jest brzeg dziury należy podać jedna pół-krawedź z jej brzegu i przegladać Prev(). Dla kazdej dziury potrzebujemy jedną pół-krawedź.
Doubly-connected edge list Dla ściany f definiujemy OuterComponent(f ) = dowolna pół-krawędź zewnętrzna InnerComponents(f ) = lista pół-krawedzi dziur, jedna pół-krawedź dla każdej dziury
Doubly-connected edge list Reprezentacja wierzchołka Każdy wierzchołek jest opisany przez swoje współrzędne Każdy wierzchołek posiada pół-krawędzie, dla który jest on poczatkiem Ponieważ korzystamy z pól-krawędzi, aby podać wszystkie pół-krawedzie zaczynajace sie w danym wierzchołku wszystkie przylegajace ściany wystarczy podać jedna pół-krawedź
Doubly-connected edge list Dla wierzchołka v definiujemy Coordinates(v) IncidentEdge(v) - dovolna pół-krawędź taka, że Origin = v
Doubly-connected edge list Struktura danych lista wierzchołków, każdy rekord zawiera: Coordinates, InicdentEdge lista pół-krawedzi, każdy rekord zawiera: Origin, Twin, IncidentFace, Next, Prev lista ścian, każdy rekord zawiera: OuterComponent, InnerComponents
Doubly-connected edge list Uwagi Jakie dane przechowujemy: Coordinates(v) przechowuje dwie liczby InnerComponents przechowuje listę wszystkie pozostałe pola są wskaźnikami do rekordów Ilośc przechowywanych danych dla kazdego wierzchołka i krawedzi jest taka sama. Dla ściany zależy od topologii - liczba dziur - liniowo.
Doubly-connected edge list Uwagi Tak skonstruowana struktura danych pozwala przechowywać dodatkowe informacje, np.: wierzchołki: nazwy miast, rond, węzłów na autostradzie lub typ obiektu pół-krawędzie: nazwy ulic, informacja czy jest to droga jednokierunkowa, zamknięta, autostrada, etc., ściany: typ obszaru np.: miasto, las, pszenica, smoki, area51,
Doubly-connected edge list Uwagi Jeżeli nie chcemy przechowywać żadnych informacji, to możemy zrezygnować z listy wierzchołków. Wtedy Origin(e) = Coordinates(Origin(e)) Możemy także zrezygnować z listy ścian (np.: w przypadku sieci dróg).
Problem jeszcze raz Mamy dane dwa podziały płaszczyzny (grafy planarne) S 1, S 2. Znaleźć nowy podział płaszczyzny (overlay) O(S 1, S 2 ) taki, że f O(S 1, S 2 ) jest ścianą istnieją ściany f 1 S 1, f 2 S 2, takie, że f jest maksymalnym spójnym podzbiorem f 1 f 2
Problem jeszcze raz Majac dane struktury związane z S 1 i S 2 pdoać strukturę w O(S 1, S 2 ) w której z każdę krawędzią zwiazana jest informacja o ścianach z S 1 i S 2, które zawierają tą krawędź. W ten sposób możemy określić jakie informacje są zwiazane z częścią wspólną tych ścian.
Algorytm Połączmy listy S 1 i S 2. Zauważmy, że obrazek O zawiera w zasadzie takie same pół-krawedzie jak S 1 i S 2. Brakuje wierzchołków odpowiadających punktom przecięcia.
Algorytm Połączmy listy S 1 i S 2. Zauważmy, że obrazek O zawiera w zasadzie takie same pół-krawedzie jak S 1 i S 2. Brakuje wierzchołków odpowiadających punktom przecięcia. Niektóre z pół-krawędzi są podzielone na części w punktach przecięcia krawędzi z tych dwóch list. każdy punkt przecięcia jest prawidłowym wierzchołkiem Każda taka część jest prawidłową pół krawędzią (orientacja, następnik itd.).
Algorytm Musimy zatem naprawić strukturę O(S 1, S 2 ), tzn.: utworzyć nowe lub zaktualizować rekordy dla wierzchołków - znaleźć punkty przecięcia pół-krawędzi (i usunać stare rekordy). ścian
Algorytm - nowa lista wierzchołków i pół-krawędzi Będziemy korzystać z algorytmu SegInt dla odcinków tworzących krawędzie S 1 i S 2 Q kolejka eventów i T drzewo status - takie jak w SegInt każde znalezione przecięcie jest wierzchołkiem w O(S 1, S 2 ), Ponadto mamy D - struktura opisana wyżej, zawiera informacje skopiowane z S 1 i S 2
Algorytm - nowa lista wierzchołków i pół-krawędzi Będziemy korzystać z algorytmu SegInt dla odcinków tworzących krawędzie S 1 i S 2 Q kolejka eventów i T drzewo status - takie jak w SegInt każde znalezione przecięcie jest wierzchołkiem w O(S 1, S 2 ), Ponadto mamy D - struktura opisana wyżej, zawiera informacje skopiowane z S 1 i S 2 dodatkowo w T wraz z każdą krawędzią przechowujemy informację o odpowiadających jej pół-krawędziom umożliwi to zaktualizowanie D gdy SegInt napotka punkt przecięcia
Algorytm - nowa lista wierzchołków i pół-krawędzi Co zrobić, gdy SegInt napotka event point? jeżeli event dotyczy tylko odcinków z S 1 albo S 2 nie trzeba nic robić, jest to wierzhołek w tych siatkach jeżeli event dotyczy odcinków z obu podziałów trzeba poprawić D
Algorytm - nowa lista wierzchołków i pół-krawędzi Co się może zdażyć krawędź S 1 przechodzi przez wierzchołek S 2, lub na odwrót przecięcie krawędzi z dwóch podziałów wierzchołki z dwóch podziałów pokrywają się
Algorytm - nowa lista wierzchołków i pół-krawędzi e S 1 przechodzi przez v S 2 e musi zostać podzielona na dwie częsci (4 pół-krawędzie) należy utworzyć 2 nowe pół-krawędzie trzeba poprawić wpisy w dwóch istniejących pół-krawędziach trzeba poprawić wpisy w 4 pół-krawędziach z S 2, sąsiadach krawędzi e
Algorytm - nowa lista wierzchołków i pół-krawędzi e S 1 przechodzi przez v S 2 Input: e S 1, v S 2 Output: poprawione D (lokalnie) 1: e i e - półkrawędzie odpowiadające e // patrz obrazek 2: utwórz dwie nowe pół-krawędzie e 1, e 2, 3: Origin(e 1 ) = v, Origin(e 2 ) = v, 4: Twin(e ) = e 1, Twin(e ) = e 2, Twin(e 1 ) = e, Twin(e 2 ) = e 5: Next(e 1 ) = Next(e ), Next(e 2 ) = Next(e ) + uaktualnij Prev w Next(e i ) 6: Znajdź sąsiadów e, zaktualizuj Next i Prev // patrz obrazek 7: Znajdź sąsiadów e, zaktualizuj Next i Prev // patrz obrazek
Algorytm - nowa lista wierzchołków i pół-krawędzi e S 1 przecina f S 2 trzeba utworzyć nowy wierzchołek trzeba utworzyć 2 nowe pół-krawędzie dla e trzeba utworzyć 2 nowe pół-krawędzie dla f trzeba poprawić wpisy w 4 istniejących pół-krawędziach
Algorytm - nowa lista wierzchołków i pół-krawędzi e S 1 przecina f S 2 Input: e S 1, f S 2 Output: poprawione D (lokalnie) 1: e i e - półkrawędzie odpowiadające e // patrz obrazek 2: f i f - półkrawędzie odpowiadające e // patrz obrazek 3: utwórz nowy wierzchołek v 4: utwórz dwie nowe pół-krawędzie e 1, e 2 5: utwórz dwie nowe pół-krawędzie f 1, f 2 6: ustaw Origin = v dla e 1, e 2, f 1, f 2 7: ustaw Twin dla e 1, e 2, e, e i f 8: Dla każdej pół-krawedzi znajdz sąsiadów i ustaw Next i Prev
Algorytm - nowa lista wierzchołków i pół-krawędzi v S 1 pokrywa się z w S 2 trzeba usunąć w trzeba poprawić Origin dla pół-krawędzi z S 2 trzeba poprawić Next i Prev dla istniejacych krawędzi ALGORYTM Ćwiczenie
Algorytm - nowa lista wierzchołków i pół-krawędzi Po wykonaniu SegInt w powyzszy sposób D zawiera poprawne informacje o wierzchołkach i krawędziach. Można to wykonać w czasie O((n + k) log n), gdzie n - ilość krawędzi+wierzchołków+ścian S 1 i S 2, k - tak samo dla O(S 1, S 2 )
Algorytm - nowa lista ścian Dla każdej ściany w O(S 1, S 2 ) musimy utworzyć rekord, musimy to zrobić bazując na D dla każdej półkrawędzi musimy zaktualizować IncidentFace każda ściana w nowy podziale musi zawierać informacje o ścianach S 1, S 2, które ja zawierają to pozwoli wykorzystać wynik do znajdowania częsci wspólnych itd...
Algorytm - nowa lista ścian każda ściana ma swój zewnętrzny brzeg (cykl), złożony z pół-krawędzi przeglądając listę krawędzi w D, łatwo jest znaleźć wszystkie takie cykle trzeba zdecydować, czy taki cykl jest brzegiem ściany, czy raczej brzegiem dziury trzeba zdecydować które cykle (zewnętrzny i dziury) ograniczają tą samą ścianę
Algorytm - nowa lista ścian Czy cykl jest brzegiem ściany czy dziury? 1: znajdź najbardziej lewy wierzchołek cyklu i przylegajace pół-krawędzie e 1 i e 2 2: if (e 1, e 2 ) < π then 3: cykl jest brzegiem zewnętrznym 4: else 5: cykl jest brzegiem dziury 6: end if Zauważmy, że jeżeli cykl jest brzegiem dziury to Twin jest brzegiem zewnętrznym i na odwrót.
Algorytm - nowa lista ścian Które cykle ograniczają tą samą ścianę? Aby to stwierdzić budujemy graf G kazdy cykl jest reprezentowany przez wierzchołek mamy też wyimaginowany wierzchołek reprezentujacy ograniczenie całej mapy dwa wierzchołki są połączone krawędzią jeden z nich jest brzegiem dziury, a drugi posiada półkrawędzi bezpośrednio na lewo od najbardziej lewego wierzchołka pierwszego cyklu jeżeli takiej krawędzi nie ma, łączymy cykl z cyklem wyimaginowanym
Algorytm - nowa lista ścian Można wykazać, że każdy spójny podgraf G wyznacza cykle ograniczajace jedną ścianę. W łatwy sposób można teraz Uzupełnić InnerComponents oraz OuterComponent.
Algorytm - nowa lista ścian Jak skonstruować G? Podczas SegInt sprawdzamy jaka krąwędź leżeli bezpośrednio na lewo od wierzchołka należy zapamiętać tą informację po utworzeniu listy krawędzi i wierzchołków utworzyć węzły G przeglądając cykle pół-krawędzi dla każdego cyklu znaleźć najbardizej lewy wierzchołek i krawędź bezpośrednio na lewo połączyć odpowiadające węzły w G krawędzią można to zrobic wydajnie, jezeli dla kazdej krawędzi jej rekord będzie przechowywał informację o cyklu (węźle G) który ją zawiera
Algorytm - nowa lista ścian Jak podać ściany w S 1 i S 2 zawierajace ścianę w O(S 1, S 2 )? Niech v należy do ściany f O(S 1, S 2 ). Jeżeli v jest punktem przecięcia krawędzi z S 1 i S 2 wtedy możemy wykorzystać IncidentFace tych krawędzi Jeżeli v jest wierzchołkiem z (przykładowo) S 1 wtedy znamy ściane z S 1, trzeba wyznaczyć ścianę z S 2 która zawiera v Możemy to zrobić podczas wykonywania SegInt, wystarczy skorzystać z IncidentFace krawędzi należącej do S 2 bezpośrednio na lewo i zapamietac tą informację zatem w SegInt musimy wokonac dodatkową czynność - zapamietać ściany do których należy wierzchołek.
Algorytm - podsumowanie Input: podziały płaszczyzny S 1 i S 2 Output: nowy podział płaszczyzny O(S 1, S 2 ), zawierajacy informacje o pokrywaniu 1: Skopiuj listy wierzchołków i krawędzi z S 1 i S 2 do D 2: WYkonaj SegInt dla krawędzi z D, aktualizuj D jeżeli potrzeba, zapamiętaj informacje o najbardziej na lewo krawędzi i ścianie zawierającej kazdy wierzchołek 3: Wyznacz cykle, przeglądając D 4: Utwórz G 5: for każdy spójny podgraf G do 6: Utwórz nową ścianę f, przypisz OuterComponent, InnerComponents 7: Dla kazdej pół-krawędzi z OuterComponent ustaw IncidentFace 8: end for 9: zaktualizuj informację o ścianach zawierajacych ściany z O(S 1, S 2 )
Algorytm - podsumowanie Ten algorytm można zreaizować w czasie O((n + k) log n) został podany przez Bentleya i Ottmana w 1979 Istnieje algorytm O(n log n + k) potrzebujacy O(n) pamięci
Zastosowania wykrywanie przecięć dwóch siatek pokrywanie się (overlay) dwóch mapy operacje logiczne na wielokątach (CAD)
Plan 3 Polygon triangulation Problem i motywacja Traingulacja - analiza Monotone pieces Algorytm MakeMonotone Algorytm TriangulateMonotonePolygon
Problem i motywacja Dla dowolnego wielokąta podać jego podział na trójkąty. j.w. ale oszczędnie, tzn jak najmnije trójkatów. Dla do płaszczyzny na wielokaty podać odpowiadajacy mu oszczędny podział na trójkaty.
Problem i motywacja Art gallery problem Ilu strażników potrzeba aby pilnować galerii sztuki? Mając plan danego piętra w galerii sztuki, określić ilu nieruchomych strażników (kamer) potrzeba, aby każde miejsce było podobserwacją.
Problem i motywacja plan piętra - skomplikowany wielokąt zakładamy, że jest on jednospójny (brzeg jest spójny) kamera widzi każdy punkt który można połączyć odcinkiem leżacym wewnątrz wielokąta Ile potrzeba kamer? Odpowiedź zależy od ilości wierzchołków i samego wielokata. Dla dowolnego wypukłego wystarczy jedna kamera. Znalezienie minimalnej liczby kamer jest problemem NP-trudnym Zajmiemmy się przypadkiem pesymistycznym - ile najmniej kamer potrzeba aby chronić n-kąt.
Problem i motywacja Najłatwiejszym do upilnowania wielokatem jest trójkąt. Podzielimy wiec nasz wielokąt na trójkąty, czyli wykonamy triangulację: łączymy wierzchołki za pomocą przekątnych. każdy wierzchołek ma być użyty przekątne nie mogą sie przecinać Mając daną triangulację, w każdym trójkącie mozemy ustawić kamerę.
Problem i motywacja Twierdzenie Każdy jednospójny wielokąt posiada triangulację. Każda triangulacja n-kąta składa się z dokładnie n 2 trójkątów.
Problem i motywacja Zatem n 2 kamery wystarczą zawsze. Zauważmy, że jest to rozwiazanie mocno nie optymalne.
Problem i motywacja Zatem n 2 kamery wystarczą zawsze. Zauważmy, że jest to rozwiazanie mocno nie optymalne. Umieszczając kamerę na przekątnej możemy pilnować dwóch trójkatów. Dobrze wybierając przekatne możemy zredukować liczbę potrzebnych kamer do n/2
Problem i motywacja Zatem n 2 kamery wystarczą zawsze. Zauważmy, że jest to rozwiazanie mocno nie optymalne. Umieszczając kamerę na przekątnej możemy pilnować dwóch trójkatów. Dobrze wybierając przekatne możemy zredukować liczbę potrzebnych kamer do n/2 Umieszczając kamerę w wierzchołku możemy pilnować wszystkie trójkąty o tym wierzchołku.
Problem i motywacja Twierdzenie Każdy jednospójny n-kąt może być pilnowany przez n/3 kamer. Podane oszacowanie jest optymalnym oszacowaniem z góry. Rysunek: Tu potrzeba n/3 kamer
Problem i motywacja Korzystając z 3-kolorowania grafu dualnego do triangulacji można podać algorytm, który dla danej triangluacji podaje pozycje kamer w czasie liniowym.
Traingulacja - analiza Algorytm naiwny?
Traingulacja - analiza Algorytm naiwny? Algorytm rekurencyjny: trójkąt jest jest już striangulowany załóżmy, że umiemy podać triagulację m-kąta, m < n w n kącie znajdź przekątną, wyznacza ona podział wielokąta na dwa wielokaty o mniejszej liczbie wierzchołków. zastosuj algorytm do każdego z tych wielokątów
Traingulacja - analiza Jak znaleźc przekatną? Dla najbardziej na lewo położonego wierzchołka v spróbuj połączyć jego sąsiadów u, w przekątną. Jeżeli się nei uda (odcinek wychodzi z wielokata) połącz najbardziej lewy z wierzchołkiem najdalszym od wierzchołkiem leżacym wewnatrz trójkata vuw. Opisany algorytm działa w czasie n 2.
Traingulacja - analiza Czy można to zrobić szybciej? Triangulacja wielokąta wypukłego może być wykonana w czasie liniowym Niestety, podział wielokąta na wielokąty wypukłe jest tak samo trudny jak jego triagulacja. Potrzebujemy czegoś innego...
Monotone pieces Mówimy, że jednospójny wielokat jest y-monotoniczny, jeżeli część wspólna tego wielokąta i dowolnej prostej poziomej jest spójna: czyli zbiór pusty, punkt lub odcinek. Jeżeli wielokat jest y-monotoniczny, to poruszając się wzdłuż krawędzi od najwyższego wierzchołka w kierunku najniższego, zawsze poruszamy się w dół lub poziomo, a nigdy w górę.
Monotone pieces Poruszając się wzdłuż krawędzi welokąta, możemy napotkać wierzchołek w którym kierunek poruszania zmienia się z w dół na w górę (lub odwrotnie). taki wierzchołek nie może wystąpić w wielokacie monotonicznym w takich wierzchołkach będziemy dodawac przekątne, dzieląc w ten sposób wielokat na dwa nowe wierzchołek należy do obu nowych, jednak teraz kierunek ruchu się nie zmienia i monotniczność nie jest zepsuta
Monotone pieces Jekie typy wierzchołków możemy napotkać? Zależy to od położenia sąsiadów i kąta wewnętrznego. start vertex: sąsiedzi poniżej, kąt wewnętrzny < π split vertex: sąsiedzi poniżej, kąt wewnętrzny > π merge vertexe: sąsiedzi powyżej, kat wewnętrzny > π end vertex: sąsiedzi powyżej, kąt wewnętrzny < π regular: jeden sąsiad powyzej, drugi ponizej
Monotone pieces Mamy dany wielokąt P. uporządkujmy jego wierzchołki w kolejnosci przeciwnej do ruchu wskazówek zegara: v 1,..., v n oznaczmy krawędzie e i = v i v i+1, e n = v n v 1 Będziemy robić sweeping od góry do dołu. event points: wierzchołki, Q - kolejka piorytetowa żadne wierzchołki nie bedą dodawane, zatem możemy je posortować (w czasie logarytmicznym) od góry i od lewej. Wtedy dostep do kolejnego elementu jest w czasie stałym.
Monotone pieces Naszymm celem jest: dla kazdego split vertex dodać przekatną do wierzchołka leżacego powyżej dla każdego merge vertex dodać przekatną do wierzchołka leżacego poniżej
Monotone pieces Niech v i - split vertex. e j krawędź najbliżej na lewo e k krawędź najbliżej na prawo Możemy połączyć v i z wierzchołkiem leżacym najniżej, pomiędzy krawędziami e j i e k. W ostateczności taki wierzcołkiem jest górny koniec e j lub e k. Nazwijmy go helper(e j ). helper(e j ) wierzchołek leżący najniżej nad sweeping line taki, że poziomy odcinke łączący helper(e j ) i e j leży wewnątrz P.
Monotone pieces Podczas przesuwania sweeping line w dół, możemy zapamiętywać wierzchołki helper(e j ). Trudniej sytuacja wyglada, gdy napotykamy merge vertex, ponieważ wtedy potrzebujemy wierzchołka poniżej sweeping line. Czyli takiego, którego jescze nie napotkaliśmy.
Monotone pieces Zauważmy, że jeżeli v i jest merge vertex, a e j jest krawędzią najbliżej na lewo, to helper(e j ) = v i.
Monotone pieces Zauważmy, że jeżeli v i jest merge vertex, a e j jest krawędzią najbliżej na lewo, to helper(e j ) = v i. v i możemy połączyć z najwyżej leżacym wierzchołkiem poniżej sweeping line tego wierzchołka nie znamy w tym momencie, ale poznamy go wykonujac algorytm
Monotone pieces Zauważmy, że jeżeli v i jest merge vertex, a e j jest krawędzią najbliżej na lewo, to helper(e j ) = v i. v i możemy połączyć z najwyżej leżacym wierzchołkiem poniżej sweeping line tego wierzchołka nie znamy w tym momencie, ale poznamy go wykonujac algorytm gdy zdarzy się sytuacja, w której bedziemy zmieniać helper(e j ) wystarczy sprawdzić czy poprzedni heleper był merge vertex. jeżeli tak, to należy połączyć przekątną stary i nowy zauważmy, że tak dzieje sie gdy nowy helepr jest split vertex, jeżeli helper(e j ) się nie zmieni wtedy trzeba go połączyć z dolnym wierzchołkiem e j
Monotone pieces Kolejny element do sweeping: status: drzewo binarne T przechowujemy krawędzie od góry i od lewej razem z każdą krawędzią przechowujemy jej helper wystarczy przechowywac te krawędzie, dla których P leży po prawej stronie
Monotone pieces Kolejny element do sweeping: status: drzewo binarne T przechowujemy krawędzie od góry i od lewej razem z każdą krawędzią przechowujemy jej helper wystarczy przechowywac te krawędzie, dla których P leży po prawej stronie status zmienia się, gdy nowa krawędź zaczyna przecinać sie ze sweeping line zmienia się helper krawedzi, która jest w T
Algorytm MakeMonotone D - lista wierzchołków i krawędzi (jak na poprzednim wykładzie) Q - event queue T - status Jeżeli do P zostanie dodana przekatna, to dodamy ją do D. Po zakończeniu algorytmu D bedzie przechowywać podział P an wielokąty monotoniczne. Każdy z tych wielokatów może być łatwo wyciągniety z D (Czyli jak?)
Algorytm MakeMonotone Algorithm 3 MakeMonotone(P) Input: jednospójny wielokąt P, przechowywany w D Output: podział P na wielokąty monotoniczne, przechowywany w D 1: Skonstruuj Q, umieszczająć wierzchołki od góry od lewej 2: Utwórz puste drzewo T 3: while Q nie jest pusta do 4: Usuń wierzchołek o najwyzszym priorytcie z Q 5: Wykonaj odpowiednią procedurę Handle{type}Vertex w zależności od typu wierzchołka 6: end while
Algorytm MakeMonotone Algorithm 4 HandleStartVertex(v i ) 1: Dodaj e i do T 2: helper(e i ) = v i Algorithm 5 HandleEndVertex(v i ) 1: if helper(e i 1 ) == merge vertex then 2: Dodaj przekątną łączącą v i z helper(e i 1 ) 3: end if
Algorytm MakeMonotone Algorithm 6 HandleSplitVertex(v i ) 1: wyszukaj w T krawędź e j najbliżej na lewo od v i 2: wstaw przekatną łączącą v i oraz helper(e j ) 3: helper(ej) = v i 4: Dodaj e i do T 5: helper(e i ) = v i
Algorytm MakeMonotone Algorithm 7 HandleMergeVertex(v i ) 1: if helper(e i 1 )==merge vertex then 2: wstaw przekątną łączącą v i oraz helper(e i 1 ) 3: end if 4: usuń e i 1 z T 5: Znajdż w T krawędź e j najbliżej na lewo od v i 6: if helper(e j )==merge vertex then 7: wstaw przekątną łączącą v i oraz helper(e j ) 8: end if 9: helper(e j ) = v i
Algorytm MakeMonotone Algorithm 8 HandleRegularVertex(v i ) 1: if Wnętrze P leży na prawo od v i then 2: if helper(e i 1 ) == merge vertex then 3: dodaj przekątną łączącą v i oraz helper(e i 1 ) 4: end if 5: usuń e i 1 z T 6: dodaj e i do T 7: helper(e i ) = v i 8: else 9: znajdź w T krawędź e j leżącą bezpośrednio na lewo od v i 10: if helper(e j )==merge vertex then 11: dodaj przekątną łączącą v i oraz helper(e j ) 12: end if 13: helper(e j ) = v i 14: end if
Algorytm MakeMonotone Twierdzenie Algorytm MakeMonotone(P) dzieli jednospójny wielokąt na monotoniczne częsci w czasie O(n log n) używając O(n) dodatkowej pamięci.
Algorytm TriangulateMonotonePolygon Bedziemy wykorzystywać pomocniczą strukturę danych - stos: dostęp tylko do ostatniego dodanego elementu, LIFO pop() - pobiera element ze szczytu stosu push(v) - odkłada element v na szczyt stosu Na stosie bedziemy przechowywać odwiedzone wierzchołki, które potrzebują jeszcze przekątnych.
Algorytm TriangulateMonotonePolygon Robimy sweeping od góry do dołu Jeżeli napotkamy wierzchołek, dodajemy tyle przekatnych ile jesteśmy w stanie. Część z dodanych przekątnych oddziela trójkaty od P Napotkane wierzchołki, które nie zostały odzielone odkładamy na stos. potrzebują przekątnych, bo znajdują się w tej cześci która musi być podzielona.
Algorytm TriangulateMonotonePolygon OBRAZKI
Algorytm TriangulateMonotonePolygon Algorithm 9 TriangulateMonotonePolygon(P) Input: wielokąt monotoniczny P, przechwywany w D Output: triangulacja P, przechowywana w D 1: uporządkuj wierzchołki P od góey i od lewej, u 1,..., u n 2: odłóż na stos u 1 i u 2 3: for j=3 to n-1 do 4: if u j i wierzchołek stosu leżą po różnych stronach then 5: pobierz wszystkie wierzchołki ze stosu 6: dla każdego pobranego wierzchołka, połącz go przekątną z u j, z wyjątkiem ostatniego 7: odłóż na stos u j i oraz u j 8: else 9: Pobierz wierzchołek ze stosu 10: Pobieraj wierzchołki ze stosu dopóki przekątne od u j do pobranego wierzchołka leżą w P 11: odłóż ostatni zabrany ze stosu wierzchołek spowrotem na stos 12: odłóż u j na stos 13: end if 14: end for 15: Dodaj przekątne z u n do wszystkich wierzchołków na stosie, pomijajac ostatni.
Algorytm TriangulateMonotonePolygon Powyższy algorytm działa w czasie liniowym. Aby dokonać triagulacji dowolnego wielokąta trzeba najpierw podzielić go na monotoniczne części, a potem dokonać triagulacji każdej z nich. Dowolny jednospójny wielokąt może być striangulowany w czasie O(n log n) używając dodatkowo O(n) pamięci.
Algorytm TriangulateMonotonePolygon Miła informacja Algorytm MakeMonotone działa dla dowolnych wielokątów, nie tlko dla jednospójnych.
Algorytm TriangulateMonotonePolygon Miła informacja Algorytm MakeMonotone działa dla dowolnych wielokątów, nie tlko dla jednospójnych. Kolejna miła informacja Korzystając z powzszego algorytmu można dokonać triangulacji dowolngo podziału płaszczyzny.
Plan 4 Ortogonal Range Searching Motywacja
Motywacja przechowujemy duży zbiór rekordów: klienci, pracownicy, towary każdy rekord zawiera pewną liczbę pól, niektóre z nich mają specjalną własność: można je uporządkować liniowo ilość zakupionego towaru, wartość zakupionego towaru wiek, pensja, staż pracy cena, ilość RAM w komputerze, przekątna ekranu,
Motywacja Chcemy wyszukiwać rekordy które spełniają kryteria typu od... do, np.: pracownicy w wieku od 18 do 26 lat pracownicy w wieku od 50 do 67 lat, zatrudnieni ponad 10 lat, zarabiający mniej niż 2000PLN monitory w cenie od 500 do 1000 PLN laptopy, od 8GB do 16GB RAM i dyskiem od 240GB do 512 GB
Motywacja Każda z interesujacych nas cech jest opisana przez wartości liczbowe z pewnego przedziału. znalezienie rekordów spełniajacego wszystkie kryteria jest równoważne znalezieniu rekordów, dla których wartości interesujacych nas pól leżą w tych przedziałach czyli w pewnej d-wymiarowej kostce. Musimy wykonać: orthogonal range query
Plan 5 Przypadek 1D Problem 2D Algorytm BuildKdTree Algorytm SearchKdTree trick warty zapamiętania Drzewa przedziałowe Algorytm Build2DRangeTree Algorytm 2DRangeQuery
Motywacja Problem Mamy dany zbiór P punktów na prostej Znaleźć wszystkie punkty P należące do przedziału [a, b] proste rozwiazanie, algorytm naiwny: sprawdź każdy punkt, czas działanie O(n). chcemy rowiązanie szybsze najlepiej gdyby czas działania zależał od ilości pasujacych punktów (output-sensitive)
Motywacja Będziemy używać drzewa BST operacje w czasie log n W liściach przechowujemy punkty zbioru P W wężle v przechowujemy spliting value x v lewe poddrzewo v zawiera punkty mniejsze lub równe x v prawe poddrzewo zawiera punkty większe
Motywacja znalezienie punktów z przedziału [a, b] oznacza teraz podanie lisci z pewnego poddrzewa z uwagi na naturę BST łatwo jest znaleźć poddrzewo w którym są one zwarte zaczna się ono w wierzchołku v split, w którym drogi do a i b się rozchodzą
Motywacja znalezienie punktów z przedziału [a, b] oznacza teraz podanie lisci z pewnego poddrzewa z uwagi na naturę BST łatwo jest znaleźć poddrzewo w którym są one zwarte zaczna się ono w wierzchołku v split, w którym drogi do a i b się rozchodzą idąc od v split do a poruszamy się w lewo lub w prawo jeżeli skręcamy w lewo w v, to wszystkei liście w prawym poddrzewie są większe od a jeżeli skręcamy w prawo w v, to jego lewe poddrzewo jest nieistotne podobnie dla b
Motywacja Rozwiązanie problemu polega zatem na wykonaniu trzech kroków: 1 znleźć poddrzewo zawierajace punkty z szukanego przedziału 2 przeszukać jego lewe (prawe) poddrzewo, wyszukujac odpowiednie prawe (lewe) poddrzewa 3 wypisać wszystkie liscie ze znalezionych poddrzew
Motywacja Algorithm 10 FindSplitNode(a,b) Input: drzewo T j.w. i dwie wartości a b Output: wierzchołek v (najmniejszego) poddrzewa zawierajacego punkty z przedziału [a, b] 1: v = root(t ) 2: while v nie jest liściem and (b x v or a > x v ) do 3: if b x v then 4: v = leftchild(v) 5: else 6: v = rightchild(v) 7: end if 8: end while
Motywacja Algorithm 11 1DRangeQuery(a,b) Input: drzewo T j.w. i dwie wartości a b Output: punkty z przedziału [a, b] 1: v split = FindSplitNode(a, b) 2: if v split jest liściem then 3: jeżeli v split [a, b], to zwróć ten liść 4: else 5: v = leftchild(v split 6: while v nie jest liściem do 7: if a < x v then 8: wypisz liście z prawego poddrzewa v, v = leftchild(v) 9: else 10: v = rightchild(v) 11: end if 12: end while 13: sprawdź czy v należy do [a, b] 14: wykonaj analogiczne kroki dla prawego poddrzewa v split 15: end if
Motywacja wypisanie wszystkich liści z poddrzewa wykonuje sie w czasie liniowym O(k), k-liczba znalezionych punktów w pesymistycznym przypadku (wszystkei punkty należą) czas jest liniowy Twierdzenie Podany algorytm działa w czasie O(logn + k), potrzebuje O(n log n) czasu na utworzenie drzewa i O(n) pamięci na przechowywanie.
Problem 2D Mamy dany zbiór P punktów na płaszczyźnie, zakładamy (na chwilę), że żadne dwa punkty nei maja takiej samej pierwszej ani drugiej współrzędnej. szukamy punktów należących do prostokąta [a, b] [c, d] Zasadniczo, wykonamy ten sam algorytm co w 1D. Wyszukiwanie 2D jest dwukrotnym wyszukiwaniem 1D. Cchemy utrzymać lepszy niż liniowy czas wyszukiwania.
Problem 2D Wykorzystamy zadsadę dziel na pół dzielimy zbiór punktów na przemian linią pionową i poziomą podziału dokonujemy tak aby po każdej stronie linii znalazło się tyle samo punktów (czyli szukamy mediany) punkt na linii traktujemy jako punkt leżący po lewej (dolnej) stronie linii
Problem 2D Będziemy korzystać z dwuwymiarowego kd-drzewa, które jest drzewem binarnym, tylko skonstruowanym sprytnie: wierzchołki przechowują proste, które dzielą na połowy liście przechowują punkty płaszczyzny wierzchołki o nieparzystej głębokości przechowują pionowe linie podziału wierzchołki o parzystej głębokości przechowują poziome linie podziału
Problem 2D OBRAZEK
Problem 2D
Algorytm BuildKdTree Algorithm 12 BuildKdTree(P,depth) Input: Zbiór punktów P i głębokość depth Output: korzeń kd-drzewa 1: if P zawiera jeden punkt then 2: dodaj ten punkt i zwróć jego wierzchołek 3: else 4: if depth jest parzysta then 5: podziel P linią pionowa równe zbiory P 1 (lewy) i P 2 (prawy) 6: else 7: podziel P linią poziomą równe zbiory P 1 (dolny) i P 2 (górny) 8: end if 9: v left = BuildKdTree(P 1, depth + 1) 10: v right = BuildKdTree(P 2, depth + 1) 11: dodaj węzeł v dla bierzącej linii, 12: leftchild(v) = v left, rightchild(v) = v right 13: end if
Algorytm BuildKdTree twierdzenie Kd-drzewo potrzebuje O(n log n) czasu na utworzenie O(n) pamieci na przechowywanie.
Algorytm SearchKdTree poddrzewo o wierzchołku v zawiera punkty leżace w pewnym prostokącie (być może nieogranczonym) oznaczmy go przez region(v) punkty są przechowywane w lisciach poddrzewa wtedyi tylko wtedy, gdy należą do region(v)
Algorytm SearchKdTree poddrzewo o wierzchołku v zawiera punkty leżace w pewnym prostokącie (być może nieogranczonym) oznaczmy go przez region(v) punkty są przechowywane w lisciach poddrzewa wtedyi tylko wtedy, gdy należą do region(v) algorytm wyszukiwania polega na przegladaniu drzewa, szukamy tylko wierzchołków których region przecina się z prostokątem [a, b] [c, d] jeżeli region(v) [a, b] [c, d], wtedy zwracamy wszystkie liście jeżeli dotarliśmy do liścia, to sprawdzamy i ewentualnie zwracamy ten liść
Algorytm SearchKdTree Algorithm 13 SearchKdTree(v) Input: korzeń v drzewa skonstruowanego j.w. i prostokąt R Output: liście (punkty) należące do R 1: if v jest liściem then 2: zwróć v jeżeli należy do R 3: else 4: if region(leftchild(v)) R then 5: zwróć liście drzewa leftchild(v) 6: else if region(leftchild(v)) przecina R then 7: earchkdtree(leftchild(v), R) 8: end if 9: if region(rightchild(v)) R then 10: zwróć liście drzewa rightchild(v) 11: else if region(rightchild(v)) przecina R then 12: SearchKdTree(rightchild(v), R) 13: end if 14: end if
Algorytm SearchKdTree zadania na ćwiczenia jak sprawdzić, czy punkt jest w prostokacie (łatwe) jak wyznaczyc region(v) w trakcie wykonywania algorytmu jak wyznaczyć region(leftchild(v) i region(rightchild(v)) w trakcie trwania algorytmu
Algorytm SearchKdTree Twierdzenie Wyszukiwanie może być wykonane w czasie O( n + k).
Algorytm SearchKdTree Twierdzenie Wyszukiwanie może być wykonane w czasie O( n + k). Powyższy algorytm można uogólnić na d wymiarów uzywając do podziału hiperpłaszczyzn zamiast prostych. Wtedy czas wykonania tego algorytmu wynosi O(n 1 1/d + k).
trick warty zapamiętania zalożenie, że żadne dwa punkty nie mają takiej samej pierwszej ani takiej samej drugie wspólrzędnej jest bzdurą, w realnym świecie wiele cech jest takich samych. Zamiast punktu p = (x, y) rozpatrujemy punkt ˆp = ((x, y), (y, x)) relacja porządkujaca współrzędne to porzadek leksykograficzny musimy na nowo zdefiniować R = [a, b] [c, d] ˆR = [(a, ), (b, )] [(c, ), (d, )] ćw. - zrozumieć powyższy zapis... dla takich danych możemy skostruowac 2-wymiarowe kd-drzewo i wykonać algorytm wyszukiwania w dokładnie ten sam sposób
Drzewa przedziałowe Rozwiązanie problemu wyszukiwania na płaszczyznie w inny sposób: najpierw znajdziemy punkty których pierwsza wspólrzędna jest w przedziale [a, b] a potem bedziemy się martwić o druga współrzędna. Budujemy drzewo T tak jak w przypadku 1-wymiarowym, dla pierwszej współrzędnej z każdym węzłem v drzewa T stowarzyszamy drzewo T y (a). Jest to drzewo wszystkich liści w poddrzewie v uporządkowane względem drugiej współrzędnej. W nowym drzewie węzły przechowują nie tylko swoje wartości, ale takze wskaźniki do innej struktury danych.
Algorytm Build2DRangeTree Algorithm 14 Build2DRangeTree(P) Input: zbiór punktów na płaszczyźnie P Output: korzeń 2-wymiarowego drzewa przedziałowego 1: zbuduj drzewo T y dla zbioru P, w liściach przechowuj unkty zbioru P 2: if P zawiera jeden punkt then 3: utwórz liść zawierajacy v oraz T y (v) = T y 4: else 5: podziel P na dwa równe zbiory P left i P right względem pierwszej współrzędnej 6: v left = Build2DRangeTree(P left ) 7: v right = Build2DRangeTree(P right ) 8: utwórz węzeł dla v, leftchild(v) = v left, rightchild(v) = v right, T y (v) = T y 9: end if
Algorytm Build2DRangeTree Twierdzenie Drzewo przedziałowe może być utworzone w czasie O(n log n) i wymaga O(n log n) pamięci.
Algorytm 2DRangeQuery Algorithm 15 2DRangeQuery(P, [a, b]x[c, d]) Input: drzewo przedziałowe i prostokąt [a, b] [c, d] Output: punkty w przedziale 1: v split = FindSplitNode(T, a, b) 2: if v split jest liściem then 3: zwróć v split jeżeli należy do R 4: else 5: v = leftchild(v split ) 6: while v nie jest liściem do 7: if a x v then 8: 1DRangeQuery(T y (rightchild(v), c, d)) 9: v = leftchild(v) 10: else 11: v = rightchild(v) 12: end if 13: end while 14: zwróć v jeżeli należy do R 15: wykonaj analogicznie dla v = rightchild(v) 16: end if
Algorytm 2DRangeQuery twierdzenie Powyższy algorytm działa w czasie (log 2 n + k)
Algorytm 2DRangeQuery twierdzenie Powyższy algorytm działa w czasie (log 2 n + k) Konstrukcję drzewa przedziałowego można uogółnić na większa liczę przedziałów. Z kazdym wierzchołkiem m-tej współrzędnej trzeba stworyć drzewo przedziałowe dla m 1-współrzędnej. Można wykazać, że d wymiarowe drzewo przedziałowe potrzebuje O(n log d 1 n) pamięci, może być utworzone w czasie O(n log d 1 n), a wyszukiwanie może być wykonane w czasie O(log d n + k).
Plan 6 Point location Problem Pierwsza próba Drugie podejście Algorytm TrapezoidalMap(S) Przypadek zdegenerowany
Problem Problem Dany jest podział płaszczyzny na wielokąty oraz punkt. Stwierdzić do którego wielokąta należy dany punkt. Jeżeli leży na krawędzi/wierzchołku podać tą krawędź/ ten wierzchołek.
Problem Zastosowanie 1 statyczne: w jakim powiecie/działce znajduje się wskazany punkt 2 dynamiczne: w jakim obszarze (np.: zamknięty akwen, zakaz lotów) znajduje się statek/samolot Ostatnie zadanie... lepiej, żeby robić to często i szybko... Potrzebujemy zatem nie tyle algorytmu, co struktury danych pozwalającej na szybkie wyszukiwanie.
Pierwsza próba porządkujemy wierzchołki wg pierwszej współrzędnej, od lewej przez każdy wierzchołek prowadzimy pionową prostą, dzieląc płaszczyznę na pasy
Pierwsza próba porządkujemy wierzchołki wg pierwszej współrzędnej, od lewej przez każdy wierzchołek prowadzimy pionową prostą, dzieląc płaszczyznę na pasy W czasie O(log(n) można określić w którym pasie znajduje się szukany punkt (jak to zrobić?).
Pierwsza próba żaden wierzchołek nie leży wewnątrz pasa, czyli żadna krawędź nie zaczyna ani nie kończy się wewnątrz pasa ponadto, żadne dwie krawędzie nie przecinają się wewnątrz pasa w każdym pasie można uporządkować, przecinające go krawędzie od góry do dołu każdy obszar pomiędzy dwoma krawędziami należy do dokładnie jednej ściany albo należy do zewnętrza
Pierwsza próba 1: jeżeli nie zostało wykonane wcześniej: 2: podziel S na pasy 3: dla każdego pasa utwórz uporządkowaną (od góry) listę przecinających krawędzi 4: oznacz każdą krawędź przez ścianę leżąco powyżej (w tym zewnętrze) 5: WŁAŚCIWY ALGORYTM Input: podział płaszczyzny S na pasy wrz z krawędziami w tych pasach oraz punkt q Output: ściana, krawędź lub wierzchołek w którym leży punkt 6: znajdź pasek w którym leży punkt q 7: znajdź krawędź leżącą bezpośrednio pod q 8: jeżeli takiej krawędzi nie ma, to punkt leży na zewnątrz
Pierwsza próba podany algorytm działa w czasie logarytmicznym (dlaczego?) niestety, w pesymistycznym przypadku wymaga n 2 pamięci (n pasów n-krawędzi przecinających pas) łatwo podać taki przypadek (OBRAZEK) Pomimo, ze algorytm działa szybko, ilość dodatkowej pamięci jest zbyt duża (np.: systemy wbudowane, małe urządzenia)
Pierwsza próba jeżeli dokładnie przyjrzymy się obrazkowi, to zobaczymy, że utworzyliśmy drobniejszy podział płaszczyzny każda ściana nowego podziału zawiera się jednej ścianie starego okazuje się, że rozwiązaliśmy problem dla drobniejszego podziału płaszczyzny (stąd zapotrzebowanie na pamięć) Nowy pomysł: stworzyć taki podział płaszczyzny, który nie wymaga tyle pamięci, ale zachować, w jak największym stopniu, szybkość poprzedniego
Drugie podejście Uproszczenia: do istniejącego podziału płaszczyzny dodajemy prostokąt R zawierający ten podział żadne dwa punkty nie mają takiej samej pierwszej współrzędnej (to założenie jest, nie realistyczne i później trzeba je usunąć)
Drugie podejście Będziemy tworzyć trapezoidal map T (S), czyli podział płaszczyzny, który składa się z trapezów. podstawy trapezów są równoległe do osi y dopuszczamy sytuację, że jedna z podstaw jest zdegenerowana do punktu, czyli uważamy trójkąt za trapez
Drugie podejście Będziemy tworzyć trapezoidal map T (S), czyli podział płaszczyzny, który składa się z trapezów. podstawy trapezów są równoległe do osi y dopuszczamy sytuację, że jedna z podstaw jest zdegenerowana do punktu, czyli uważamy trójkąt za trapez Z każdego wierzchołka podziału S wyprowadzamy pionowe odcinki w górę i w dół, łączące wierzchołek z krawędzią pod i nad tym wierzchołkiem ( zawsze istnieje bo S jest zawarty w R) tutaj jest założenie o różnych pierwszych współrzędnych
Drugie podejście Będziemy tworzyć trapezoidal map T (S), czyli podział płaszczyzny, który składa się z trapezów. podstawy trapezów są równoległe do osi y dopuszczamy sytuację, że jedna z podstaw jest zdegenerowana do punktu, czyli uważamy trójkąt za trapez Z każdego wierzchołka podziału S wyprowadzamy pionowe odcinki w górę i w dół, łączące wierzchołek z krawędzią pod i nad tym wierzchołkiem ( zawsze istnieje bo S jest zawarty w R) tutaj jest założenie o różnych pierwszych współrzędnych do podziału S dodajemy te krawędzie dodajemy także nowe wierzchołki oraz tworzymy na nowo listę ścian OBRAZEK
Drugie podejście każda nowa ściana podziału T (S) jest ograniczona przez pewną liczbę krawędzi z T (S) sąsiadujące i współliniowe krawędzie ograniczające ścianę nazywamy jej bokiem Twierdzenie Każda ściana w T (S) posiada dokładnie jeden lub dwa pionowe boki oraz dokładnie dwa boki, które nie są pionowe. OBRAZEK
Drugie podejście każdy nie pionowy bok ściany jest zawarty w pewnej krawędzi S lub poziomym boku R bok ograniczający z góry oznaczamy top( ) bok ograniczający z dołu oznaczamy bottom( )
Drugie podejście Lewa krawędź ściany może być: 1 punktem wspólnym top( ) i bottom( ) 2 odcinkiem zaczynającym się w lewym wierzchołku top( ) kończącym się w bottom( ) 3 odcinkiem zaczynającym się w lewym wierzchołku bottom( ) kończącym się w top( ) 4 sumą dwóch odcinków zaczynających się w wierzchołku leżącym pomiędzy top( ) i botton( ) i kończącym się na tych bokach 5 jest lewą krawędzią R (dla dokładnie jednej ściany) OBRAZKI Dla prawego boku mamy analogiczne pięć sytuacji.
Drugie podejście W każdym z wymienionych przypadków, lewy bok jest definiowany przez pewien wierzchołek, będziemy go oznaczać leftp( ). Odpowiedni wierzchołek dla prawego boku będziemy oznaczać rightp( ).
Drugie podejście W każdym z wymienionych przypadków, lewy bok jest definiowany przez pewien wierzchołek, będziemy go oznaczać leftp( ). Odpowiedni wierzchołek dla prawego boku będziemy oznaczać rightp( ). Zauważmy, że każda ściana T (S) jest jednoznacznie wyznaczona przez top, bottom, leftp oraz rightp.
Drugie podejście Twierdznie Trapezoidal map T (S) dla podziału S złożonego z n krawędzi zawiera co najwyżej 6n + 4 wierzchołków oraz co najwyzej 3n + 1 ścian.
Drugie podejście Dwie ściany T (S) nazywamy sąsiadującymi, jeżeli mają wspólną pionową krawędź (nie bok!) ponieważ żadne dwa wierzchołki S nie miały takiej samej pierwszej współrzędnej, każda ściana posiada co najwyżej cztery sąsiadujące ściany. bez tego założenia mogłaby posiadać ich dowolnie dużo (OBRAZEK)
Drugie podejście Dwie ściany T (S) nazywamy sąsiadującymi, jeżeli mają wspólną pionową krawędź (nie bok!) ponieważ żadne dwa wierzchołki S nie miały takiej samej pierwszej współrzędnej, każda ściana posiada co najwyżej cztery sąsiadujące ściany. bez tego założenia mogłaby posiadać ich dowolnie dużo (OBRAZEK) Jeżeli jest ścianą sąsiadującą z lewej strony ze ścianą to mają one wspólny top lub bottom. w pierwszym przypadku nazywamy lewym górnym sąsiadem w drugim przypadku nazywamy lewym dolnym sąsiadem, może posiadać jednego lewego sąsiada, dwóch albo żadnego