Zaawansowane algorytmy i struktury danych/Wykład 12: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Sank (dyskusja | edycje)
Nie podano opisu zmian
 
(Nie pokazano 22 wersji utworzonych przez 3 użytkowników)
Linia 1: Linia 1:
== Abstrakt ==
== Abstrakt ==


W ramach tego wykładu przedstawimy dwa algorytmu obrazujące wykorzystanie dwóch technik konstrukcji algorytmów geometrycznych. Do konstrukcji algorytmu sprawdzającego, czy w zbiorze odcinków istnieją dwa przecinające się użyjemy techniki [[../Wykład 10#zamiatanie|zamiatania]]. Natomiast do konstrukcji algorytmu wyznaczającego najmniejszą odległość w zbiorze punktów użyjemy techniki dziel i zwyciężaj.
W ramach tego wykładu przedstawimy dwa algorytmy obrazujące wykorzystanie dwóch technik konstrukcji algorytmów geometrycznych. Do konstrukcji algorytmu sprawdzającego, czy w zbiorze odcinków istnieją dwa przecinające się, użyjemy techniki [[../Wykład 11#zamiatanie|zamiatania]]. Natomiast do konstrukcji algorytmu wyznaczającego najmniejszą odległość w zbiorze punktów użyjemy techniki [[../Wykład 11#dziel_i_zwyciężaj|dziel i zwyciężaj]].


{{kotwica|przecinanie_sie_zbioru_odcinkow|}}
== Przecinanie się zbioru odcinków ==
== Przecinanie się zbioru odcinków ==
{{kotwica||przecinanie_się_zbioru_odcinków}}
Problem jaki nas będzie interesował, to '''problem przecinania się zbioru odcinków''', w którym mamy za zadanie sprawdzić, czy w danym zbiorze odcinków <math>S</math> istnieją dwa odcinki, które się przecinają. Wykorzystamy tutaj algorytm sprawdzania, czy para odcinków się przecina zaprezentowany na poprzednim wykładzie ([[../Wykład 11#przecinanie_sie_odcinkow|zobacz]]).
Problem jaki nas będzie interesował, to '''problem przecinania się zbioru odcinków''', w którym mamy za zadanie sprawdzić, czy w danym zbiorze odcinków <math>S</math> istnieją dwa które się przecinają. Wykorzystamy tutaj algorytm sprawdzania, czy para odcinków się przecina zaprezentowany na poprzednim wykładzie ([[../Wykład 11#przecinanie_się_odcinków|zobacz]]).


W celu uproszczenia prezentacji idei algorytmu poczynimy dwa upraszczające założenia. Po pierwsze załóżmy, że żaden z odcinków nie jest pionowy. Po drugie załóżmy, że żadne trzy odcinki nie przecinają się w jednym punkcie. Algorytm ten może być prosto zmieniony tak aby działał bez tych założeń.[[../Ćwiczenia 11#Zadanie 1|Ćwiczenie 1]] do tego wykładu polega na pokazaniu tej modyfikacji. Jednak nie zawsze jest tak prosto. Bardzo często największym kłopotem w dowiedzeniu poprawności działania algorytmu geometrycznego jest poradzenie sobie ze wszystkimi szczególnymi i brzegowymi przypadkami.
W celu uproszczenia prezentacji idei algorytmu poczynimy dwa upraszczające założenia. Po pierwsze załóżmy, że żaden z odcinków nie jest pionowy. Po drugie załóżmy, że żadne trzy odcinki nie przecinają się w jednym punkcie. Algorytm ten może być prosto zmieniony, tak aby działał bez tych założeń. [[../Ćwiczenia 12#Zadanie 1|Zadanie 1]] i [[../Ćwiczenia 12#Zadanie 2|Zadanie 2]] do tego wykładu polega na pokazaniu tej modyfikacji. Jednak nie zawsze jest tak prosto. Bardzo często największym kłopotem w dowiedzeniu poprawności działania algorytmu geometrycznego jest poradzenie sobie ze wszystkimi szczególnymi i brzegowymi przypadkami.


Ponieważ założyliśmy, że w zbiorze nie ma odcinków pionowych, to każdy z odcinków może przecinać [[../Wykład 10#miotła|miotłę]] w co najwyżej jednym punkcie. Dlatego dla każdego położenia miotły możemy więc uporządkować odcinki przecinające ją zgodnie z kolejnością przecięć. Niech <math>s_1</math> i <math>s_2</math> będą dwoma odcinakami. Powiemy, że są one '''porównywalne''' w <math>a</math> jeżeli miotła umieszczona w <math>x=a</math> przecina je obydwa. Mówimy, że <math>s_1</math> jest '''powyżej''' <math>s_2</math> w <math>a</math>, i oznaczamy <math>s_1 >_{a} s_2</math>, jeżeli <math>s_1</math> i <math>s_2</math> są porównywalne w <math>a</math> oraz przecięcie <math>s_1</math> z miotłą w <math>x=a</math> jest wyżej niż przecięcie z <math>s_2</math> z tą miotłą.
Ponieważ założyliśmy, że w zbiorze nie ma odcinków pionowych, to każdy z odcinków może przecinać [[../Wykład 11#miotła|miotłę]] w co najwyżej jednym punkcie. Dlatego dla każdego położenia miotły możemy uporządkować odcinki przecinające ją zgodnie z kolejnością przecięć. Niech <math>s_1</math> i <math>s_2</math> będą dwoma odcinakami. Powiemy, że są one '''porównywalne''' w <math>a</math> jeżeli miotła umieszczona w <math>x=a</math> przecina je obydwa. Mówimy, że <math>s_1</math> jest '''powyżej''' <math>s_2</math> w <math>a</math>, i oznaczamy <math>s_1 >_{a} s_2</math>, jeżeli <math>s_1</math> i <math>s_2</math> są porównywalne w <math>a</math> oraz przecięcie <math>s_1</math> z miotłą w <math>x=a</math> jest wyżej niż przecięcie z <math>s_2</math> z tą miotłą.


{{cwiczenie||ćwiczenie_porównywalne|3=
{{cwiczenie||ćwiczenie_porównywalne|3=
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Pokaż, używając technik z poprzedniego wykładu, jak sprawdzić, czy dwa odcinki <math>s_1</math> i <math>s_2</math> są porównywalne w <math>a</math>?  
Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dwa odcinki <math>s_1</math> i <math>s_2</math> są porównywalne w <math>a</math>?  
<div class="mw-collapsible-content" style="display:none">
<div class="mw-collapsible-content" style="display:none">
Wystarczy sprawdzić, czy odcinki te przekraczają dowolny odcinek współliniowy z prostą <math>x=a</math>, np. odcinek <math>(a,0)-(a,1)</math>.
Wystarczy sprawdzić, czy odcinki te przekraczają dowolny odcinek współliniowy z prostą <math>x=a</math>, np. odcinek <math>(a,0)-(a,1)</math>.
Linia 23: Linia 23:
{{cwiczenie||ćwiczenie_porządek|3=
{{cwiczenie||ćwiczenie_porządek|3=
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Pokaż używając technik z poprzedniego wykładu jak sprawdzić, czy dla dwóch porównywalnych nie przecinających się odcinków <math>s_1</math> i <math>s_2</math> zachodzi <math>s_1 <_{a} s_2</math>?  
Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dla dwóch porównywalnych nie przecinających się odcinków <math>s_1</math> i <math>s_2</math> zachodzi <math>s_1 <_{a} s_2</math>?  
<div class="mw-collapsible-content" style="display:none">
<div class="mw-collapsible-content" style="display:none">
Jeżeli odcinki się nie przecinają to jeden z nich leży cały po jednej stronie prostej współliniowej z drugim odcinkiem. Wystarczy więc sprawdzić, czy jest to po stronie prawej.
Jeżeli odcinki się nie przecinają, to jeden z nich leży cały po jednej stronie prostej współliniowej z drugim odcinkiem. Wystarczy więc sprawdzić, czy jest to po stronie prawej.
</div>
</div>
</div>
</div>
Linia 32: Linia 32:
Dla każdego <math>a</math> porządek <math><_{a}</math> jest porządkiem całkowitym na odcinkach przecinających miotłę w <math>x=a</math>. Jednak porządek ten może być różny dla różnych <math>a</math> ponieważ:
Dla każdego <math>a</math> porządek <math><_{a}</math> jest porządkiem całkowitym na odcinkach przecinających miotłę w <math>x=a</math>. Jednak porządek ten może być różny dla różnych <math>a</math> ponieważ:
* dla różnych położeń miotły różne odcinki ją przecinają,
* dla różnych położeń miotły różne odcinki ją przecinają,
* jeżeli odcinki się przecinają to ich kolejność po obydwu stronach przecięcia jest różna.
* jeżeli odcinki się przecinają, to ich kolejność po obydwu stronach przecięcia jest różna.
Co więcej ponieważ zakładamy, że żadne trzy odcinki nie przecinają się w tym samym punkcie, to dla każdej pary przecinających się odcinków <math>e</math> i <math>f</math> musi istnieć takie położenie miotły <math>a</math> dla którego <math>e</math> i <math>f</math> będą kolejne w porządku <math><_{a}</math>. własność właśnie wykorzystamy w algorytmie który za chwile skonstruujemy.
Co więcej, ponieważ zakładamy, że żadne trzy odcinki nie przecinają się w tym samym punkcie, to dla każdej pary przecinających się odcinków <math>e</math> i <math>f</math> musi istnieć takie położenie miotły <math>a</math>, dla którego <math>e</math> i <math>f</math> będą kolejne w porządku <math><_{a}</math>. własność właśnie wykorzystamy w algorytmie, który za chwilę skonstruujemy.


=== Algorytm ===
=== Algorytm ===


Będziemy przechowywać informację o porządku na odcinkach aktualnie przecinających miotłę w strukturze <math>T</math> pozwalającej na wykonanie następujących operacji:
Będziemy przechowywać informację o porządku na odcinkach aktualnie przecinających miotłę w strukturze <math>T</math>, pozwalającej na wykonanie następujących operacji:
* {{kotwica|WSTAW|WSTAW}}<math>(T,s)</math> - wstawia odcinek <math>s</math> do porządku <math>T</math>,
* {{kotwica|WSTAW|WSTAW}}<math>(T,s)</math> - wstawia odcinek <math>s</math> do porządku <math>T</math>,
* {{kotwica|USUŃ|USUŃ}}<math>(T,s)</math> - usuwa odcinek <math>s</math> z porządku <math>T</math>,
* {{kotwica|USUŃ|USUŃ}}<math>(T,s)</math> - usuwa odcinek <math>s</math> z porządku <math>T</math>,
Linia 43: Linia 43:
* {{kotwica|NASTĘPNY|NASTĘPNY}}<math>(T,s)</math> - zwraca odcinek po <math>s</math>  w porządku <math>T</math>.
* {{kotwica|NASTĘPNY|NASTĘPNY}}<math>(T,s)</math> - zwraca odcinek po <math>s</math>  w porządku <math>T</math>.


Jeżeli zaimplementujemy strukturę z użyciem zrównoważonych drzew przeszukiwania, to operacje te będziemy mogli zrealizować w czasie <math>O(\log n)</math>, gdzie <math>n</math> to całkowita liczba odcinków. Poniższy algorytm zwraca TRUE wtedy i tylko wtedy gdy zbiór <math>S</math> przekazany my jako argument zawiera parę przecinających się odcinków.
Jeżeli zaimplementujemy strukturę z użyciem zrównoważonych drzew wyszukiwania, to operacje te będziemy mogli zrealizować w czasie <math>O(\log n)</math>, gdzie <math>n</math> to całkowita liczba odcinków. Poniższy algorytm zwraca <math>TRUE</math> wtedy i tylko wtedy, gdy zbiór <math>S</math> przekazany jako argument zawiera parę przecinających się odcinków.


{{algorytm|sprawdza, w zbiorze <math>S</math> istnieją dwa przecinające się odcinki|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|3=
{{algorytm|sprawdza, w zbiorze <math>S</math> istnieją dwa przecinające się odcinki|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|3=
   ZBIÓR-ODCINKÓW-SIĘ-PRZECINA<math>(S)</math>
   ZBIÓR-ODCINKÓW-SIĘ-PRZECINA<math>(S)</math>
   1  <math>T = 0</math>
   1  <math>T = 0</math>
   2  niech <math>P</math> będzie posortowanym zbiorem końcy odcinków z <math>S</math> od lewej do prawej
   2  niech <math>P</math> będzie posortowanym zbiorem końców odcinków z <math>S</math> od lewej do prawej
   3  '''for''' każdego <math>p \in P</math> '''do'''
   3  '''foreach''' <math>p \in P</math> '''do'''
   4  '''begin'''
   4  '''begin'''
   5    '''if''' <math>p</math> to lewy koniec odcinka <math>s</math> '''then'''
   5    '''if''' <math>p</math> to lewy koniec odcinka <math>s</math> '''then'''
   6    '''begin'''
   6    '''begin'''
   7      [[#WSTAW|WSTAW]]<math>(T,s)</math>
   7      [[#WSTAW|WSTAW]]<math>(T,s)</math>
   8      <math>s_p = </math>[[#POPRZEDNI|POPRZEDNI]]<math>(T,s)</math>
   8      <math>s_p =</math>[[#POPRZEDNI|POPRZEDNI]]<math>(T,s)</math>
   9      <math>s_n = </math>[[#NASTĘPNY|NASTĘPNY]]<math>(T,s)</math>
   9      <math>s_n =</math>[[#NASTĘPNY|NASTĘPNY]]<math>(T,s)</math>
   10    '''if''' <math>s_p \neq NIL \mbox{ i } </math> [[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s,s_p)</math> '''then''' '''return''' TRUE
   10    '''if''' <math>s_p \neq NIL \mbox{ \bf i }</math>[[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s,s_p)</math> '''then''' '''return''' <math>TRUE</math>
   11    '''if''' <math>s_n \neq NIL \mbox{ i } </math> [[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s,s_n)</math> '''then''' '''return''' TRUE
   11    '''if''' <math>s_n \neq NIL \mbox{ \bf i }</math>[[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s,s_n)</math> '''then''' '''return''' <math>TRUE</math>
   12  '''end'''
   12  '''end'''
   13  '''if''' <math>p</math> to prawy koniec odcinka <math>s</math> '''then'''
   13  '''if''' <math>p</math> to prawy koniec odcinka <math>s</math> '''then'''
   14  '''begin'''
   14  '''begin'''
   15      <math>s_p = </math>[[#POPRZEDNI|POPRZEDNI]]<math>(T,s)</math>
   15      <math>s_p =</math>[[#POPRZEDNI|POPRZEDNI]]<math>(T,s)</math>
   16      <math>s_n = </math>[[#NASTĘPNY|NASTĘPNY]]<math>(T,s)</math>
   16      <math>s_n =</math>[[#NASTĘPNY|NASTĘPNY]]<math>(T,s)</math>
   17    '''if''' <math>s_p \neq NIL \s_n \neq NIL \mbox{ i } </math> [[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s_p,s_n)</math> '''then''' '''return''' TRUE
   17    '''if''' <math>s_p \neq NIL \mbox{ \bf i } s_n \neq NIL \mbox{ \bf i }</math> [[../Wykład 10#przecinanie_się_odcinków|ODCINKI-SIĘ-PRZECINAJĄ]]<math>(s_p,s_n)</math> '''then''' '''return''' <math>TRUE</math>
   18    [[#USUŃ|USUŃ]]<math>(T,s)</math>
   18    [[#USUŃ|USUŃ]]<math>(T,s)</math>
   19  '''end'''
   19  '''end'''
   20  '''end'''
   20  '''end'''
   21  '''return''' FALSE
   21  '''return''' <math>FALSE</math>
}}
}}


Następująca animacja przedstawia działania tego algorytmu.
Następująca animacja przedstawia działania tego algorytmu.


<flash>file=Zasd_ilustr_k.swf|width=600|height=300</flash>
[[File:Zasd_ilustr_k.svg|600x300px|thumb|center]]
 
{{twierdzenie|1|poprawność_odcinków|3=
{{twierdzenie|poprawność_odcinków||3=
Algorytm [[#ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA]] zwraca <math>TRUE</math> wtedy i tylko wtedy, gdy w <math>S</math> istnieje para przecinających się odcinków.
Algorytm [[#ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA]] zwraca TRUE wtedy i tylko wtedy gdy w <math>S</math> istnieje para przecinających się odcinków.
}}
}}


{{dowod|||3=
{{dowod|||3=
Algorytm [[#ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA]] zwraca TRUE w wypadku, gdy znalazł parę przecinających się odcinków. W celu pokazania jego poprawności wystarczy więc pokazać, że jeżeli w <math>S</math> istnieje para przecinających się odcinków to algorytm zwróci TRUE.
Algorytm [[#ZBIÓR-ODCINKÓW-SIĘ-PRZECINA|ZBIÓR-ODCINKÓW-SIĘ-PRZECINA]] zwraca <math>TRUE</math> w wypadku, gdy znalazł parę przecinających się odcinków. W celu pokazania jego poprawności wystarczy więc pokazać, że jeżeli w <math>S</math> istnieje para przecinających się odcinków, to algorytm zwróci <math>TRUE</math>.


Załóżmy, że w <math>S</math> jest co najmniej jedno przecięcie. Niech <math>p</math> będzie tym najbardziej na lewo spośród nich. Niech <math>a</math> i <math>b</math> będą odcinkami przecinającymi się w <math>p</math>. Ponieważ po lewej stronie <math>p</math> nie ma żadnych przecięć, to porządek przechowywany w <math>T</math> jest poprawny do momentu przekroczenia <math>p</math>. Jak zauważyliśmy poprzednio istnieje takie położenie <math>x</math> miotły, dla którego <math>a</math> i <math>b</math> są kolejne w porządku <math><_{x}</math>. Co więcej istnieje takie <math>x</math>, że miotła w <math>x</math> jest na lewo od <math>p</math>. Takie <math>x</math> istnieje ponieważ założyliśmy, że trzy odcinki nie przecinają się w jednym punkcie. W takim bądź razie widzimy, że w <math>x</math> porządek w <math>T</math> poprawnie reprezentuje porządek <math><_{x}</math>. Odcinki <math>a</math> i <math>b</math> mogły się stać kolejne w <math>T</math>:
Załóżmy, że w <math>S</math> jest co najmniej jedno przecięcie. Niech <math>p</math> będzie tym najbardziej na lewo spośród nich. Niech <math>a</math> i <math>b</math> będą odcinkami przecinającymi się w <math>p</math>. Ponieważ po lewej stronie <math>p</math> nie ma żadnych przecięć, to porządek przechowywany w <math>T</math> jest poprawny do momentu przekroczenia <math>p</math>. Jak zauważyliśmy poprzednio, istnieje takie położenie <math>x</math> miotły, dla którego <math>a</math> i <math>b</math> są kolejne w porządku <math><_{x}</math>. Co więcej, istnieje takie <math>x</math>, że miotła w <math>x</math> jest na lewo od <math>p</math>. Takie <math>x</math> istnieje, ponieważ założyliśmy, że trzy odcinki nie przecinają się w jednym punkcie. W takim razie widzimy, że w <math>x</math> porządek w <math>T</math> poprawnie reprezentuje porządek <math><_{x}</math>. Odcinki <math>a</math> i <math>b</math> mogły się stać kolejne w <math>T</math>:
* po wstawieniu jednego z nich do <math>T</math> i taka sytuacja będzie wykryta w liniach 8-11,
* po wstawieniu jednego z nich do <math>T</math>, taka sytuacja będzie wykryta w liniach 8-11,
* po usunięciu odcinka między nimi, taką sytuację wykryjemy w liniach 15-17.
* po usunięciu odcinka między nimi, taką sytuację wykryjemy w liniach 15-17.
}}
}}
Linia 88: Linia 89:
Algorytm ten działa w czasie <math>O(n \log n)</math> gdyż na jego czas działania składa się:
Algorytm ten działa w czasie <math>O(n \log n)</math> gdyż na jego czas działania składa się:
* wykonanie linii 2 w czasie <math>O(n \log n)</math>,
* wykonanie linii 2 w czasie <math>O(n \log n)</math>,
* wykonanie dla każdego końca odcinka pętli '''for''' w liniach 4-20 w całkowitym czasie <math>O(n\log n)</math> - w każdym wykonaniu pętli wywoływanych jest stała liczba operacji na strukturze <math>T</math>.
* wykonanie pętli '''foreach''' w liniach 4-20 w całkowitym czasie <math>O(n\log n)</math> - w każdym obrocie pętli wywoływana jest stała liczba operacji na strukturze <math>T</math>.
 
{{kotwica|najmniej_odlegla_para|}}


== Najmniej odległa para punktów ==
== Najmniej odległa para punktów ==
 
Będziemy się teraz zajmować problemem znalezienia najmniej odległej pary punktów w zbiorze <math>Q</math>, gdzie <math>|Q|\ge 2</math>. Interesować nas tutaj będzie odległość euklidesowa, którą dla punktów <math>p = (x_p, y_p)</math>, <math>q = (x_q, y_q)</math> oznaczamy przez <math>|p-q|</math>, oraz definiujemy jako:
Będziemy się teraz zajmować problemem znalezienia najmniej odległej pary punktów w zbiorze <math>Q</math>, gdzie <math>|Q|\ge 2</math>. Interesować nas tutaj będzie odległość Euklidesowa, która dla punktów <math>p = (x_p, y_p)</math>, <math>q = (x_q, y_q)</math> oznaczamy przez <math>|p-q|</math>, oraz definiujemy:


{{wzor2|1=
{{wzor2|1=
<math>|p-q|  = \sqrt{(x_p-x_q)^2 + (y_p-y_q)^2}.</math>
<math>|p-q|  = \sqrt{(x_p-x_q)^2 + (y_p-y_q)^2}</math>
}}
}}


Dwa punkty w zbiorze <math>Q</math> mogą mieć tą samą współrzędną, w takim przypadku odległość między nimi wynosi <math>0</math>. Informacja o najbliższej parze punktów może być przydatna w wypadku, gdy chcemy wykrywać kolizję między obiektami np. w systemach kontroli ruchu. Wyznaczając wszystkie odległości między <math>\left(\begin{matrix}|Q|\\2\end{matrix}\right)</math> parami punktów możemy ten problem rozwiązać w czasie <math>O(n^2)</math>. Pokażemy teraz jak wykorzystując technikę [[../Wykład 10#diel_i_zwyciężaj|dziel i zwyciężaj]]  problem ten można rozwiązać w czasie <math>O(n \log n)</math>.
Dwa punkty w zbiorze <math>Q</math> mogą mieć te same współrzędne. W takim przypadku odległość między nimi wynosi <math>0</math>. Informacja o najbliższej parze punktów może być przydatna w wypadku, gdy chcemy wykrywać kolizję między obiektami np. w systemach kontroli ruchu. Wyznaczając wszystkie odległości między <math>\left(\begin{matrix}|Q|\\2\end{matrix}\right)</math> parami punktów, możemy ten problem rozwiązać w czasie <math>O(n^2)</math>. Pokażemy teraz, jak wykorzystując technikę [[../Wykład 11#dziel_i_zwyciężaj|dziel i zwyciężaj]]  problem ten można rozwiązać w czasie <math>O(n \log n)</math>.


W celu uniknięcia wielokrotnego sortowania punktów w wywołaniach rekurencyjnych. Wykorzystamy dwie tablice <math>X</math> i <math>Y</math>, które zawierały będą wszystkie punkty z <math>Q</math> posortowane odpowiednio po <math>x</math>'owej współrzędnej i <math>y</math>'owej współrzędnej. Jeżeli podzielimy zbiór <math>Q</math> na dwie części <math>Q_L</math> i <math>Q_R</math>, to odpowiednie tablice <math>X_L, Y_L</math> i <math>X_R, Y_R</math> zawierające posortowane elementy z <math>Q_L</math> i <math>Q_R</math> można wyznaczyć w czasie <math>O(|Q|)</math>. Musimy po prostu przeglądając tablicę <math>X</math> oraz <math>Y</math> kopiować elementy w napotkanej kolejności do mniejszych tablic.
=== Algorytm ===
 
W celu uniknięcia wielokrotnego sortowania punktów w wywołaniach rekurencyjnych, wykorzystamy dwie tablice <math>X</math> i <math>Y</math>, które zawierały będą wszystkie punkty z <math>Q</math> posortowane odpowiednio po <math>x</math>'owej współrzędnej i <math>y</math>'owej współrzędnej. Jeżeli podzielimy zbiór <math>Q</math> na dwie części: <math>Q_L</math> i <math>Q_R</math>, to odpowiednie tablice <math>X_L, Y_L</math> i <math>X_R, Y_R</math>, zawierające posortowane elementy z <math>Q_L</math> i <math>Q_R</math> można wyznaczyć w czasie <math>O(|Q|)</math>. Musimy po prostu, przeglądając tablicę <math>X</math> oraz <math>Y</math>, kopiować elementy w napotkanej kolejności do mniejszych tablic.


{{algorytm|znajduje najmniejszą odległość między parą punktów w <math>Q</math>|NAJMNIEJ-ODLEGŁA-PARA|3=
{{algorytm|znajduje najmniejszą odległość między parą punktów w <math>Q</math>|NAJMNIEJ-ODLEGŁA-PARA|3=
Linia 110: Linia 114:
   4  niech <math>Q_L</math>  i <math>Q_P</math> to będą punkty odpowiednio po lewej i prawej stronie <math>l</math>
   4  niech <math>Q_L</math>  i <math>Q_P</math> to będą punkty odpowiednio po lewej i prawej stronie <math>l</math>
   5  wyznacz tablice <math>X_L,X_P</math> oraz <math>Y_L,Y_P</math>
   5  wyznacz tablice <math>X_L,X_P</math> oraz <math>Y_L,Y_P</math>
   6  <math>d_L = NAJMNIEJ-ODLEGŁA-PARA(Q_L, X_L, Y_L)</math>
   6  <math>d_L =</math>NAJMNIEJ-ODLEGŁA-PARA<math>(Q_L, X_L, Y_L)</math>
   7  <math>d_R = NAJMNIEJ-ODLEGŁA-PARA(Q_R, X_R, Y_R)</math>
   7  <math>d_R =</math>NAJMNIEJ-ODLEGŁA-PARA<math>(Q_R, X_R, Y_R)</math>
   8  <math>d = \min(d_L, d_R)</math>
   8  <math>d = \min(d_L, d_R)</math>
   9  niech <math>Y'</math> będzie tablicą <math>Y</math> po usunięciu z niej punktów odległych od <math>l</math> o więcej niż <math>d</math>
   9  niech <math>Y'</math> będzie tablicą <math>Y</math> po usunięciu z niej punktów odległych od <math>l</math> o więcej niż <math>d</math>
Linia 121: Linia 125:


Działanie tej procedury przedstawione jest na poniższej animacji.
Działanie tej procedury przedstawione jest na poniższej animacji.
<flash>file=Zasd_Ilustr_g.swf|width=600|height=300</flash>
[[File:Zasd_ilustr_g.svg|600x300px|thumb|center]]
 
Nie wliczając wywołań rekurencyjnych, procedura ta działa w czasie liniowym. Zakładając, że <math>|Q|=n</math>, otrzymujemy równanie rekurencyjne na czas działania w postaci
Nie wliczając wywołań rekurencyjnych procedura ta działa w czasie liniowym. Zakładając, że <math>|Q|=n</math>, otrzymujemy równanie rekurencyjne na czas działania w postaci


{{wzor2|1=
{{wzor2|1=
<math>
<math>
T(n)=\begin{cases}
T(n)=\begin{cases}
2 T(\farc{n}{2}) + O(n) & \mbox{jeżeli } n>2,\\
2 T(\frac{n}{2}) + O(n) & \mbox{jeżeli } n>2,\\
O(1) & \mbox{jeżeli } n \le 2.
O(1) & \mbox{jeżeli } n \le 2.
\end{cases}
\end{cases}
Linia 134: Linia 137:
}}
}}


Którego rozwiązaniem jest <math>T(n) = O(n\log n)</math>. Zauważmy, że czas potrzebny na posortowanie po raz pierwszy tablic <math>X</math> i <math>Y</math> także wynosi <math>O(n\log n)</math>. Całkowity czas potrzebny na znalezienie najmniejszej odległości między parą punktów w <math>n</math>-elementowym zbiorze wynosi więc <math>O(n \log n)</math>.
którego rozwiązaniem jest <math>T(n) = O(n\log n)</math>. Zauważmy, że czas potrzebny na posortowanie po raz pierwszy tablic <math>X</math> i <math>Y</math> także wynosi <math>O(n\log n)</math>. Całkowity czas potrzebny na znalezienie najmniejszej odległości między parą punktów w <math>n</math>-elementowym zbiorze wynosi więc <math>O(n \log n)</math>.


{{twierdzenie|poprawność_odległości||3=
{{twierdzenie|2|poprawność_odległości|3=
Algorytm [[#NAJMNIEJ-ODLEGŁA-PARA|NAJMNIEJ-ODLEGŁA-PARA]] wyznacza najmniejszą odległość między parą punktów w <math>Q</math>.
Algorytm [[#NAJMNIEJ-ODLEGŁA-PARA|NAJMNIEJ-ODLEGŁA-PARA]] wyznacza najmniejszą odległość między parą punktów w <math>Q</math>.
}}
}}


{{dowod|||3=
{{dowod|||3=
Jeżeli najmniej odległa para punktów znajduje się w zbiorze <math>Q_L</math> bądź <math>Q_P</math> to zostanie ona znaleziona w wywołaniu rekurencyjnym. W takim razie musimy rozważyć tylko przypadek, w którym najmniej odległa para punktów to <math>p_L\in Q_L</math> i <math>p_R \in Q_R</math>. Oznaczmy, tak jak w algorytmie, przez <math>d</math> najmniejszą z odległości <math>d_L</math> i <math>d_R</math>. Widzimy teraz, że aby odległość między <math>p_L</math> i <math>p_R</math> mogła być mniejsza niż <math>d</math>, to punkty te nie mogą być bardziej odległe od <math>l</math> niż o <math>d</math>. W związku z tym usunięcie bardziej odległych punktów z <math>Y</math> w linii 9 algorytmu nie zmieni wyniku.
Jeżeli najmniej odległa para punktów znajduje się w zbiorze <math>Q_L</math> bądź <math>Q_P</math>, to zostanie ona znaleziona w wywołaniu rekurencyjnym. W takim razie musimy rozważyć tylko przypadek, w którym najmniej odległa para punktów to <math>p_L\in Q_L</math> i <math>p_R \in Q_R</math>. Oznaczmy, tak jak w algorytmie, przez <math>d</math> najmniejszą z odległości <math>d_L</math> i <math>d_R</math>. Widzimy teraz, że aby odległość między <math>p_L</math> i <math>p_R</math> mogła być mniejsza niż <math>d</math>, punkty te nie mogą być bardziej odległe od <math>l</math> niż o <math>d</math>. W związku z tym usunięcie bardziej odległych punktów z <math>Y</math> w linii 9 algorytmu nie zmieni wyniku.
 
Rozważmy teraz prostokąt rozmiaru <math>2d \times d</math> ułożony centralnie na linii <math>l</math>, w kwadracie tym może znajdować się co najwyżej <math>8</math> punktów z <math>Q_L</math> i <math>Q_R</math> gdyż odległość między nimi jest co najmniej <math>d</math>. Zauważmy, że punktów mieszczących się w tym prostokącie znajdujących się poniżej danego punktu jest co najwyżej 7 (zobacz rysunek poniżej).


<flash>file=Zasd_Ilustr_g_stat.swf|width=400|height=200</flash>
Rozważmy teraz prostokąt rozmiaru <math>2d \times d</math> ułożony centralnie na linii <math>l</math>. W prostokącie tym może znajdować się co najwyżej <math>8</math> punktów z <math>Q_L</math> i <math>Q_R</math>, gdyż odległość między nimi jest co najmniej <math>d</math>. Zauważmy, że punktów mieszczących się w tym prostokącie znajdujących się poniżej danego punktu jest co najwyżej 7 (zobacz rysunek poniżej).


Gdyby było ich więcej to odległość między nimi musiałaby być mniejsza niż <math>d</math>. Dlatego też w linii 11 algorytmu możemy ograniczyć się do przejrzenia tylko 7 kolejnych punktów.
[[File:Zasd_ilustr_g_stat.svg|400x200px|thumb|center]]
Gdyby było ich więcej, odległość między nimi musiałaby być mniejsza niż <math>d</math>. Dlatego też w linii 11 algorytmu możemy ograniczyć się do przejrzenia tylko 7 kolejnych punktów.
}}
}}

Aktualna wersja na dzień 21:01, 3 sie 2024

Abstrakt

W ramach tego wykładu przedstawimy dwa algorytmy obrazujące wykorzystanie dwóch technik konstrukcji algorytmów geometrycznych. Do konstrukcji algorytmu sprawdzającego, czy w zbiorze odcinków istnieją dwa przecinające się, użyjemy techniki zamiatania. Natomiast do konstrukcji algorytmu wyznaczającego najmniejszą odległość w zbiorze punktów użyjemy techniki dziel i zwyciężaj.

Przecinanie się zbioru odcinków

Problem jaki nas będzie interesował, to problem przecinania się zbioru odcinków, w którym mamy za zadanie sprawdzić, czy w danym zbiorze odcinków S istnieją dwa odcinki, które się przecinają. Wykorzystamy tutaj algorytm sprawdzania, czy para odcinków się przecina zaprezentowany na poprzednim wykładzie (zobacz).

W celu uproszczenia prezentacji idei algorytmu poczynimy dwa upraszczające założenia. Po pierwsze załóżmy, że żaden z odcinków nie jest pionowy. Po drugie załóżmy, że żadne trzy odcinki nie przecinają się w jednym punkcie. Algorytm ten może być prosto zmieniony, tak aby działał bez tych założeń. Zadanie 1 i Zadanie 2 do tego wykładu polega na pokazaniu tej modyfikacji. Jednak nie zawsze jest tak prosto. Bardzo często największym kłopotem w dowiedzeniu poprawności działania algorytmu geometrycznego jest poradzenie sobie ze wszystkimi szczególnymi i brzegowymi przypadkami.

Ponieważ założyliśmy, że w zbiorze nie ma odcinków pionowych, to każdy z odcinków może przecinać miotłę w co najwyżej jednym punkcie. Dlatego dla każdego położenia miotły możemy uporządkować odcinki przecinające ją zgodnie z kolejnością przecięć. Niech s1 i s2 będą dwoma odcinakami. Powiemy, że są one porównywalne w a jeżeli miotła umieszczona w x=a przecina je obydwa. Mówimy, że s1 jest powyżej s2 w a, i oznaczamy s1>as2, jeżeli s1 i s2 są porównywalne w a oraz przecięcie s1 z miotłą w x=a jest wyżej niż przecięcie z s2 z tą miotłą.

Ćwiczenie

Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dwa odcinki s1 i s2 są porównywalne w a?


Ćwiczenie

Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dla dwóch porównywalnych nie przecinających się odcinków s1 i s2 zachodzi s1<as2?

Dla każdego a porządek <a jest porządkiem całkowitym na odcinkach przecinających miotłę w x=a. Jednak porządek ten może być różny dla różnych a ponieważ:

  • dla różnych położeń miotły różne odcinki ją przecinają,
  • jeżeli odcinki się przecinają, to ich kolejność po obydwu stronach przecięcia jest różna.

Co więcej, ponieważ zakładamy, że żadne trzy odcinki nie przecinają się w tym samym punkcie, to dla każdej pary przecinających się odcinków e i f musi istnieć takie położenie miotły a, dla którego e i f będą kolejne w porządku <a. Tę własność właśnie wykorzystamy w algorytmie, który za chwilę skonstruujemy.

Algorytm

Będziemy przechowywać informację o porządku na odcinkach aktualnie przecinających miotłę w strukturze T, pozwalającej na wykonanie następujących operacji:

  • WSTAW(T,s) - wstawia odcinek s do porządku T,
  • USUŃ(T,s) - usuwa odcinek s z porządku T,
  • POPRZEDNI(T,s) - zwraca odcinek przed s w porządku T,
  • NASTĘPNY(T,s) - zwraca odcinek po s w porządku T.

Jeżeli zaimplementujemy tę strukturę z użyciem zrównoważonych drzew wyszukiwania, to operacje te będziemy mogli zrealizować w czasie O(logn), gdzie n to całkowita liczba odcinków. Poniższy algorytm zwraca TRUE wtedy i tylko wtedy, gdy zbiór S przekazany jako argument zawiera parę przecinających się odcinków.

Algorytm sprawdza, w zbiorze S istnieją dwa przecinające się odcinki


 ZBIÓR-ODCINKÓW-SIĘ-PRZECINA(S)
 1  T=0
 2  niech P będzie posortowanym zbiorem końców odcinków z S od lewej do prawej
 3  foreach pP do
 4  begin
 5    if p to lewy koniec odcinka s then
 6    begin
 7      WSTAW(T,s)
 8      sp=POPRZEDNI(T,s)
 9      sn=NASTĘPNY(T,s)
 10     if Parser nie mógł rozpoznać (błąd składni): {\displaystyle s_p \neq NIL \mbox{ \bf i }}
ODCINKI-SIĘ-PRZECINAJĄ(s,sp) then return TRUE
 11     if Parser nie mógł rozpoznać (błąd składni): {\displaystyle s_n \neq NIL \mbox{ \bf i }}
ODCINKI-SIĘ-PRZECINAJĄ(s,sn) then return TRUE
 12   end
 13   if p to prawy koniec odcinka s then
 14   begin
 15      sp=POPRZEDNI(T,s)
 16      sn=NASTĘPNY(T,s)
 17     if Parser nie mógł rozpoznać (błąd składni): {\displaystyle s_p \neq NIL \mbox{ \bf i } s_n \neq NIL \mbox{ \bf i }}
 ODCINKI-SIĘ-PRZECINAJĄ(sp,sn) then return TRUE
 18     USUŃ(T,s)
 19   end
 20  end
 21  return FALSE


Następująca animacja przedstawia działania tego algorytmu.

Plik:Zasd ilustr k.svg

Twierdzenie 1

Algorytm ZBIÓR-ODCINKÓW-SIĘ-PRZECINA zwraca TRUE wtedy i tylko wtedy, gdy w S istnieje para przecinających się odcinków.

Dowód

Algorytm ZBIÓR-ODCINKÓW-SIĘ-PRZECINA zwraca TRUE w wypadku, gdy znalazł parę przecinających się odcinków. W celu pokazania jego poprawności wystarczy więc pokazać, że jeżeli w S istnieje para przecinających się odcinków, to algorytm zwróci TRUE.

Załóżmy, że w S jest co najmniej jedno przecięcie. Niech p będzie tym najbardziej na lewo spośród nich. Niech a i b będą odcinkami przecinającymi się w p. Ponieważ po lewej stronie p nie ma żadnych przecięć, to porządek przechowywany w T jest poprawny do momentu przekroczenia p. Jak zauważyliśmy poprzednio, istnieje takie położenie x miotły, dla którego a i b są kolejne w porządku <x. Co więcej, istnieje takie x, że miotła w x jest na lewo od p. Takie x istnieje, ponieważ założyliśmy, że trzy odcinki nie przecinają się w jednym punkcie. W takim razie widzimy, że w x porządek w T poprawnie reprezentuje porządek <x. Odcinki a i b mogły się stać kolejne w T:

  • po wstawieniu jednego z nich do T, taka sytuacja będzie wykryta w liniach 8-11,
  • po usunięciu odcinka między nimi, taką sytuację wykryjemy w liniach 15-17.

Algorytm ten działa w czasie O(nlogn) gdyż na jego czas działania składa się:

  • wykonanie linii 2 w czasie O(nlogn),
  • wykonanie pętli foreach w liniach 4-20 w całkowitym czasie O(nlogn) - w każdym obrocie pętli wywoływana jest stała liczba operacji na strukturze T.

Najmniej odległa para punktów

Będziemy się teraz zajmować problemem znalezienia najmniej odległej pary punktów w zbiorze Q, gdzie |Q|2. Interesować nas tutaj będzie odległość euklidesowa, którą dla punktów p=(xp,yp), q=(xq,yq) oznaczamy przez |pq|, oraz definiujemy jako:

|pq|=(xpxq)2+(ypyq)2

Dwa punkty w zbiorze Q mogą mieć te same współrzędne. W takim przypadku odległość między nimi wynosi 0. Informacja o najbliższej parze punktów może być przydatna w wypadku, gdy chcemy wykrywać kolizję między obiektami np. w systemach kontroli ruchu. Wyznaczając wszystkie odległości między (|Q|2) parami punktów, możemy ten problem rozwiązać w czasie O(n2). Pokażemy teraz, jak wykorzystując technikę dziel i zwyciężaj problem ten można rozwiązać w czasie O(nlogn).

Algorytm

W celu uniknięcia wielokrotnego sortowania punktów w wywołaniach rekurencyjnych, wykorzystamy dwie tablice X i Y, które zawierały będą wszystkie punkty z Q posortowane odpowiednio po x'owej współrzędnej i y'owej współrzędnej. Jeżeli podzielimy zbiór Q na dwie części: QL i QR, to odpowiednie tablice XL,YL i XR,YR, zawierające posortowane elementy z QL i QR można wyznaczyć w czasie O(|Q|). Musimy po prostu, przeglądając tablicę X oraz Y, kopiować elementy w napotkanej kolejności do mniejszych tablic.

Algorytm znajduje najmniejszą odległość między parą punktów w Q


 NAJMNIEJ-ODLEGŁA-PARA(Q,X,Y)
 1  if |Q|=2 then return 
 2  if |Q|=2 then return |Q[1]Q[2]|
 3  korzystając z tablicy X znajdź pionową prostą l taką, że po jej lewej i
    prawej stronie jest |Q|2 punktów
 4  niech QL  i QP to będą punkty odpowiednio po lewej i prawej stronie l
 5  wyznacz tablice XL,XP oraz YL,YP
 6  dL=NAJMNIEJ-ODLEGŁA-PARA(QL,XL,YL)
 7  dR=NAJMNIEJ-ODLEGŁA-PARA(QR,XR,YR)
 8  d=min(dL,dR)
 9  niech Y będzie tablicą Y po usunięciu z niej punktów odległych od l o więcej niż d
 10 for i=1 to |Y| do
 11   for j=1 to min(7,|Y|i) do
 12     if |P[i]P[i+j]|<d then d=|P[i]P[i+j]|
 13 return d

Działanie tej procedury przedstawione jest na poniższej animacji.

Plik:Zasd ilustr g.svg

Nie wliczając wywołań rekurencyjnych, procedura ta działa w czasie liniowym. Zakładając, że |Q|=n, otrzymujemy równanie rekurencyjne na czas działania w postaci

T(n)={2T(n2)+O(n)jeżeli n>2,O(1)jeżeli n2.

którego rozwiązaniem jest T(n)=O(nlogn). Zauważmy, że czas potrzebny na posortowanie po raz pierwszy tablic X i Y także wynosi O(nlogn). Całkowity czas potrzebny na znalezienie najmniejszej odległości między parą punktów w n-elementowym zbiorze wynosi więc O(nlogn).

Twierdzenie 2

Algorytm NAJMNIEJ-ODLEGŁA-PARA wyznacza najmniejszą odległość między parą punktów w Q.

Dowód

Jeżeli najmniej odległa para punktów znajduje się w zbiorze QL bądź QP, to zostanie ona znaleziona w wywołaniu rekurencyjnym. W takim razie musimy rozważyć tylko przypadek, w którym najmniej odległa para punktów to pLQL i pRQR. Oznaczmy, tak jak w algorytmie, przez d najmniejszą z odległości dL i dR. Widzimy teraz, że aby odległość między pL i pR mogła być mniejsza niż d, punkty te nie mogą być bardziej odległe od l niż o d. W związku z tym usunięcie bardziej odległych punktów z Y w linii 9 algorytmu nie zmieni wyniku.

Rozważmy teraz prostokąt rozmiaru 2d×d ułożony centralnie na linii l. W prostokącie tym może znajdować się co najwyżej 8 punktów z QL i QR, gdyż odległość między nimi jest co najmniej d. Zauważmy, że punktów mieszczących się w tym prostokącie znajdujących się poniżej danego punktu jest co najwyżej 7 (zobacz rysunek poniżej).

Plik:Zasd ilustr g stat.svg
Gdyby było ich więcej, odległość między nimi musiałaby być mniejsza niż d. Dlatego też w linii 11 algorytmu możemy ograniczyć się do przejrzenia tylko 7 kolejnych punktów.