Zaawansowane algorytmy i struktury danych/Wykład 12: Różnice pomiędzy wersjami
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 | 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 == | ||
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# | |||
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 | 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 | 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 | 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 | 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>. | 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>. Tę 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 | Jeżeli zaimplementujemy tę 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 | 2 niech <math>P</math> będzie posortowanym zbiorem końców odcinków z <math>S</math> od lewej do prawej | ||
3 ''' | 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. | ||
[[File:Zasd_ilustr_k.svg|600x300px|thumb|center]] | |||
{{twierdzenie|1|poprawność_odcinków|3= | |||
{{twierdzenie|poprawność_odcinków | 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 | 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> | * 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 | * 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ść | |||
{{wzor2|1= | {{wzor2|1= | ||
<math>|p-q| = \sqrt{(x_p-x_q)^2 + (y_p-y_q)^2} | <math>|p-q| = \sqrt{(x_p-x_q)^2 + (y_p-y_q)^2}</math> | ||
}} | }} | ||
Dwa punkty w zbiorze <math>Q</math> mogą mieć | 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 | === 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. | ||
[[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(\ | 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>. | |||
{{twierdzenie|poprawność_odległości | {{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>, | 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 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 | [[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 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 i będą dwoma odcinakami. Powiemy, że są one porównywalne w jeżeli miotła umieszczona w przecina je obydwa. Mówimy, że jest powyżej w , i oznaczamy , jeżeli i są porównywalne w oraz przecięcie z miotłą w jest wyżej niż przecięcie z z tą miotłą.
Ćwiczenie
Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dwa odcinki i są porównywalne w ?
Ćwiczenie
Pokaż jak sprawdzić, używając technik z poprzedniego wykładu, czy dla dwóch porównywalnych nie przecinających się odcinków i zachodzi ?
Dla każdego porządek jest porządkiem całkowitym na odcinkach przecinających miotłę w . Jednak porządek ten może być różny dla różnych 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 i musi istnieć takie położenie miotły , dla którego i będą kolejne w porządku . 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 , pozwalającej na wykonanie następujących operacji:
- WSTAW - wstawia odcinek do porządku ,
- USUŃ - usuwa odcinek z porządku ,
- POPRZEDNI - zwraca odcinek przed w porządku ,
- NASTĘPNY - zwraca odcinek po w porządku .
Jeżeli zaimplementujemy tę strukturę z użyciem zrównoważonych drzew wyszukiwania, to operacje te będziemy mogli zrealizować w czasie , gdzie to całkowita liczba odcinków. Poniższy algorytm zwraca wtedy i tylko wtedy, gdy zbiór przekazany jako argument zawiera parę przecinających się odcinków.
Algorytm sprawdza, w zbiorze istnieją dwa przecinające się odcinki
ZBIÓR-ODCINKÓW-SIĘ-PRZECINA 1 2 niech będzie posortowanym zbiorem końców odcinków z od lewej do prawej 3 foreach do 4 begin 5 if to lewy koniec odcinka then 6 begin 7 WSTAW 8 POPRZEDNI 9 NASTĘPNY 10 if Parser nie mógł rozpoznać (błąd składni): {\displaystyle s_p \neq NIL \mbox{ \bf i }} ODCINKI-SIĘ-PRZECINAJĄ then return 11 if Parser nie mógł rozpoznać (błąd składni): {\displaystyle s_n \neq NIL \mbox{ \bf i }} ODCINKI-SIĘ-PRZECINAJĄ then return 12 end 13 if to prawy koniec odcinka then 14 begin 15 POPRZEDNI 16 NASTĘPNY 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Ą then return 18 USUŃ 19 end 20 end 21 return
Następująca animacja przedstawia działania tego algorytmu.
Twierdzenie 1
Dowód
Załóżmy, że w jest co najmniej jedno przecięcie. Niech będzie tym najbardziej na lewo spośród nich. Niech i będą odcinkami przecinającymi się w . Ponieważ po lewej stronie nie ma żadnych przecięć, to porządek przechowywany w jest poprawny do momentu przekroczenia . Jak zauważyliśmy poprzednio, istnieje takie położenie miotły, dla którego i są kolejne w porządku . Co więcej, istnieje takie , że miotła w jest na lewo od . Takie istnieje, ponieważ założyliśmy, że trzy odcinki nie przecinają się w jednym punkcie. W takim razie widzimy, że w porządek w poprawnie reprezentuje porządek . Odcinki i mogły się stać kolejne w :
Algorytm ten działa w czasie gdyż na jego czas działania składa się:
- wykonanie linii 2 w czasie ,
- wykonanie pętli foreach w liniach 4-20 w całkowitym czasie - w każdym obrocie pętli wywoływana jest stała liczba operacji na strukturze .
Najmniej odległa para punktów
Będziemy się teraz zajmować problemem znalezienia najmniej odległej pary punktów w zbiorze , gdzie . Interesować nas tutaj będzie odległość euklidesowa, którą dla punktów , oznaczamy przez , oraz definiujemy jako:
Dwa punkty w zbiorze mogą mieć te same współrzędne. W takim przypadku odległość między nimi wynosi . 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 parami punktów, możemy ten problem rozwiązać w czasie . Pokażemy teraz, jak wykorzystując technikę dziel i zwyciężaj problem ten można rozwiązać w czasie .
Algorytm
W celu uniknięcia wielokrotnego sortowania punktów w wywołaniach rekurencyjnych, wykorzystamy dwie tablice i , które zawierały będą wszystkie punkty z posortowane odpowiednio po 'owej współrzędnej i 'owej współrzędnej. Jeżeli podzielimy zbiór na dwie części: i , to odpowiednie tablice i , zawierające posortowane elementy z i można wyznaczyć w czasie . Musimy po prostu, przeglądając tablicę oraz , kopiować elementy w napotkanej kolejności do mniejszych tablic.
Algorytm znajduje najmniejszą odległość między parą punktów w
NAJMNIEJ-ODLEGŁA-PARA 1 if then return 2 if then return 3 korzystając z tablicy znajdź pionową prostą taką, że po jej lewej i prawej stronie jest punktów 4 niech i to będą punkty odpowiednio po lewej i prawej stronie 5 wyznacz tablice oraz 6 NAJMNIEJ-ODLEGŁA-PARA 7 NAJMNIEJ-ODLEGŁA-PARA 8 9 niech będzie tablicą po usunięciu z niej punktów odległych od o więcej niż 10 for to do 11 for to do 12 if then 13 return
Działanie tej procedury przedstawione jest na poniższej animacji.
Nie wliczając wywołań rekurencyjnych, procedura ta działa w czasie liniowym. Zakładając, że , otrzymujemy równanie rekurencyjne na czas działania w postaci
którego rozwiązaniem jest . Zauważmy, że czas potrzebny na posortowanie po raz pierwszy tablic i także wynosi . Całkowity czas potrzebny na znalezienie najmniejszej odległości między parą punktów w -elementowym zbiorze wynosi więc .
Twierdzenie 2
Dowód
Rozważmy teraz prostokąt rozmiaru ułożony centralnie na linii . W prostokącie tym może znajdować się co najwyżej punktów z i , gdyż odległość między nimi jest co najmniej . 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).
