Professional Documents
Culture Documents
Wojciech Chrobak
Nr albumu: 1137045
Kraków 2019
1:7805098381
Oświadczenie autora pracy
Świadom odpowiedzialności prawnej oświadczam, że niniejsza praca dy-
plomowa została napisana przeze mnie samodzielnie i nie zawiera treści uzy-
skanych w sposób niezgodny z obowiązującymi przepisami.
Oświadczam również, że przedstawiona praca nie była wcześniej przed-
miotem procedur związanych z uzyskaniem tytułu zawodowego w wyższej
uczelni.
2:1016072021
Chcę serdecznie podziękować Panu doktorowi ha-
bilitowanemu Andrzejowi Kapanowskiemu za każ-
dą cenną poradę, liczne wskazówki i poświęcony
czas, które pomogły mi dokończyć tę pracę.
3:7808896910
Streszczenie
W pracy przedstawiono implementację w języku Python wybranych algo-
rytmów z geometrii obliczeniowej, które wykorzystują technikę zamiatania
płaszczyzny. Wykorzystano reprezentacje podstawowych obiektów geome-
trycznych (punkt, odcinek, prostokąt), a także zaimplementowano struktury
danych niezbędne do optymalnego działania algorytmów.
Przygotowano trzy algorytmy do szukania przecięć w zbiorze odcinków:
algorytm Bentleya-Ottmanna (korzysta ze zmodyfikowanego drzewa AVL)
i algorytm Shamosa-Hoeya do odcinków w pozycji ogólnej oraz dodatkowy
algorytm do odcinków pionowych i poziomych.
Dodatkowo zaimplementowano trzy algorytmy do szukania pary najbliż-
szych punktów na płaszczyźnie. Pierwszy algorytm wykorzystuje technikę
zamiatania, dwa następne technikę dziel i zwyciężaj.
Przedstawiono również implementację struktury danych drzewa czwórko-
wego, które może przechowywać punkty lub prostokątne obszary płaszczyzny.
Drzewo czwórkowe pozwala na wykonanie szybkich operacji wyszukiwania
danych na płaszczyźnie albo zwartego przechowywania informacji o prosto-
kątnych fragmentach pewnego obszaru płaszczyzny.
4:7249736091
English title: Sweep line technique
Abstract
Python implementation of selected computational geometry algorithms using
a sweep line technique is presented. Standard geometrical objects are used
(point, segment, rectangle) together with special data structures needed in
optimal algorithms.
Three algorithms for finding line segment intersections are prepared: the
Bentley-Ottmann algorithm (using a modified AVL tree) and the Shamos-Hoey
algorithm (general position is assumed), the special algorithm for horizontal
and vertical line segments.
Additionally, three algorithms for finding the closest pair of points in
the plane are implemented. The first algorithm is using plane sweep, next
algorithms are using the divide and conquer technique.
A tree data structure called a quadtree is also presented. It is used to
partition a rectangular region into four subregions (with additional data) or
to maintain point sets with efficient lookup.
5:8132802133
Spis treści
Spis rysunków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Listings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1. Wstęp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2. Geometria obliczeniowa . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1. Przecinanie się odcinków na płaszczyźnie . . . . . . . . . . . . . . 6
2.2. Bliskość dla punktów na płaszczyźnie . . . . . . . . . . . . . . . . 6
3. Implementacja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1. Założenia implementacyjne . . . . . . . . . . . . . . . . . . . . . . 8
3.2. Podstawowe obiekty geometryczne . . . . . . . . . . . . . . . . . . 8
3.2.1. Punkt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2.2. Odcinek . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.3. Prostokąt . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.3. Struktury danych . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.3.1. Zdarzenie . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.3.2. Kolejka priorytetowa . . . . . . . . . . . . . . . . . . . . . 11
3.3.3. Drzewo AVL - klucze statyczne . . . . . . . . . . . . . . . . 12
3.3.4. Drzewo AVL - klucze dynamiczne . . . . . . . . . . . . . . 17
3.4. Przykładowe użycie algorytmów . . . . . . . . . . . . . . . . . . . 19
4. Algorytmy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1. Algorytm Bentleya-Ottmanna . . . . . . . . . . . . . . . . . . . . . 22
4.2. Algorytm Shamosa-Hoeya . . . . . . . . . . . . . . . . . . . . . . . 29
4.3. Przecinanie się odcinków pionowych i poziomych . . . . . . . . . . 31
4.4. Para najbliższych punktów - algorytm siłowy . . . . . . . . . . . . 34
4.5. Para najbliższych punktów - technika zamiatania . . . . . . . . . . 35
4.6. Para najbliższych punktów - technika dziel i zwyciężaj . . . . . . . 37
4.7. Drzewo czwórkowe . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5. Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
A. Testy algorytmów . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
A.1. Testy przecinania się odcinków . . . . . . . . . . . . . . . . . . . . 48
A.2. Testy wyszukiwania najbliższej pary punktów . . . . . . . . . . . . 55
A.3. Testy drzewa czwórkowego . . . . . . . . . . . . . . . . . . . . . . 57
Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6:8305708099
Spis rysunków
4.1. Przykład działania algorytmu Bentleya-Ottmanna. . . . . . . . . . . . 24
4.2. Struktura drzewa czwórkowego . . . . . . . . . . . . . . . . . . . . . . 43
7:2068168832
A.20. Wynik działania algorytmu wyszukiwania najbliższego sąsiada
pewnego punktu wykorzystując drzewo czwórkowe. . . . . . . . . . . . 59
A.21. Wykres porównujący wydajność algorytmu wyszukiwania najbliższego
sąsiada pewnego punktu wykorzystując drzewo czwórkowe
z algorytmem naiwnym. . . . . . . . . . . . . . . . . . . . . . . . . . . 59
8:8943706716
Listings
3.1 Użycie klasy Point z modułu points. . . . . . . . . . . . . . . . 8
3.2 Użycie klasy Segment z modułu segments. . . . . . . . . . . . . . 9
3.3 Użycie klasy Rectangle z modułu rectangles . . . . . . . . . . . . 9
3.4 Klasa Event z modułu events. . . . . . . . . . . . . . . . . . . . 10
3.5 Klasa PriorityQueue z modułu priority_queue. . . . . . . . . . . . 11
3.6 Moduł avltree - klucze statyczne. . . . . . . . . . . . . . . . . . 12
3.7 Moduł avltree2 - klucze dynamiczne. . . . . . . . . . . . . . . . 17
4.1 Moduł bentleyottmann. . . . . . . . . . . . . . . . . . . . . . . . 27
4.2 Moduł shamoshoey. . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.3 Moduł horizontalvertical . . . . . . . . . . . . . . . . . . . . . . 33
4.4 Funkcja find_two_closest_points(). . . . . . . . . . . . . . . . . . 35
4.5 Klasa ClosestPair z modułu closestpair2 . . . . . . . . . . . . . . . 36
4.6 Klasa ClosestPair z modułu closestpair3 . . . . . . . . . . . . . . . 39
4.7 Klasa ClosestPair z modułu closestpair4 . . . . . . . . . . . . . . . 41
4.8 Klasa QuadTree z modułu quadtree. . . . . . . . . . . . . . . . . 44
9:8179171755
1. Wstęp
Tematem niniejszej pracy jest technika zamiatania płaszczyzny (ang. swe-
ep line technique) [1], która jest jedną z kluczowych technik w geometrii
obliczeniowej (ang. computational geometry) [2]. Inne nazwy spotykane w li-
teraturze polskiej to wymiatanie, omiatanie, miotełkowanie.
Działanie algorytmów korzystających z techniki zamiatania płaszczyzny
można wyobrazić sobie jako przesuwanie prostej pionowej z lewa na prawo
lub prostej poziomej z dołu do góry. Prosta (miotła) zatrzymuje się w pew-
nych punktach, aby przetworzyć zdarzenie powiązane z danym punktem. Jest
istotne, że operacje geometryczne są ograniczone do obiektów przecinających
miotłę lub leżących w bezpośredniej bliskości miotły. Pełne rozwiązanie da-
nego problemu otrzymuje się, gdy miotła minie wszystkie rozważane obiekty.
Typowe obiekty omiatane przez miotłę to punkty lub odcinki. Wybrane al-
gorytmy wykorzystujące technikę zamiatania płaszczyzny:
— Algorytm Bentleya-Ottmanna (przecinanie się odcinków) [3].
— Algorytm Shamosa-Hoeya (przecinanie się odcinków) [4].
— Algorytm Fortune’a (konstrukcja diagramu Voronoi) [5].
Wynalezienie techniki zamiatania płaszczyzny przyniosło przełom w geo-
metrii obliczeniowej. Algorytm Shamosa-Hoeya wykrywania przecięć odcin-
ków obniżył granicę złożoności z O(n2 ) do O(n log n). Pewne algorytmy uży-
wające techniki zamiatania są czułe na wyjście (ang. output-sensitive algo-
rithms), czyli ich złożoność obliczeniowa zależy od wielkości rozwiązania, a
nie tylko od wielkości danych wejściowych. Przykładem jest liczba k punk-
tów przecięcia odcinków w algorytmie Bentleya-Ottmanna. Algorytm będzie
wydajny dla k mniejszego lub porównywalnego z liczbą odcinków.
Algorytmy z geometrii obliczeniowej pojawiają się w ogólnych podręczni-
kach do algorytmów i struktur danych [6], [7], są też książki poświęcone tylko
algorytmom geometrycznym [8], [9]. W opisach na ogół pomija się szczegóły
użytych struktur danych, bez których trudno osiągnąć teoretyczną złożoność
obliczeniową. Naszym celem jest podanie pełnego i przetestowanego kodu,
który w pełni wyjaśni nieraz subtelne szczegóły. Do implementacji algoryt-
mów użyto języka Python [10], ponieważ łączy on czytelność kodu, bogatą
bibliotekę standardową i przyjemność programowania. Implementację pod-
stawowych obiektów geometrycznych zaczerpnięto z pracy Permusa [11].
Organizacja niniejszej pracy jest następująca. Rozdział 1 podaje cele pra-
cy. Rozdział 2 wprowadza w geometrię obliczeniową. Rozdział 3 prezentuje in-
terfejs obiektów geometrycznych i przykładowe obliczenia. Rozdział 4 opisuje
implementacje algorytmów geometrycznych. Rozdział 5 jest podsumowaniem
pracy. Dodatek A przedstawia wyniki testów wydajnościowych.
10:5108920456
2. Geometria obliczeniowa
Rozdział ten poświęcony jest wyjaśnieniu podstawowych zagadnień z za-
kresu algorytmów geometrii obliczeniowej na płaszczyźnie. Omówimy proble-
my związane z przecinaniem się odcinków i bliskością punktów.
11:6917944172
— Dla n danych punktów na płaszczyźnie, stworzyć drzewo o najmniejszej
całkowitej długości, którego wierzchołki są danymi punktami. Jest to mi-
nimalne drzewo euklidesowe EMST (ang. Euclidean minimum spanning
tree).
— Dla n danych punktów na płaszczyźnie, połączyć je nieprzecinającymi się
odcinkami tak, aby każdy obszar wewnątrz otoczki wypukłej był trójką-
tem. Jest to problem triangulacji zbioru punktów.
— Dla n danych punktów na płaszczyźnie, znaleźć najbliższego sąsiada no-
wego punktu q.
12:2963941613
3. Implementacja
W tym rozdziale zostaną przedstawione podstawowe obiekty wykorzysta-
ne w pracy, a także założenia naszej implementacji.
3.2.1. Punkt
Obiekt punktu wewnętrznie reprezentowany jest przez parę współrzęd-
nych (x, y) w układzie kartezjańskim.
13:1070037041
# Kopiowanie punktu .
>>> q = p . copy ( )
# Dlugosc wektora .
>>> p . l e n g t h ( )
# Hashowanie punktu .
>>> hash ( p )
3.2.2. Odcinek
Obiekt odcinka wewnętrznie reprezentowany jest przez parę punktów
(pt1, pt2)oznaczających odpowiednio jego początek i koniec. Jest to więc
odcinek skierowany.
3.2.3. Prostokąt
Obiekt prostokąta wewnętrznie reprezentowany jest przez parę punktów
(pt1, pt2) oznaczających jego lewy dolny i prawy górny róg. Boki prostokąta
są ułożone równolegle do osi układu współrzędnych.
10
14:1477869061
# Porownywanie p r o s t o k a t o w .
>>> s == t , s != t
# Kopiowanie p r o s t o k a t a .
>>> t = s . copy ( )
# Wyznaczanie s r o d k a p r o s t o k a t a .
>>> p = s . c e n t e r ( )
# Obliczanie pola powierzchni prostokata .
>>> s . a r e a ( )
# P o d z i a l p r o s t o k a t a na c z t e r y m n i e j s z e jednakowe .
>>> t l , t r , bl , br = s . make4 ( )
# Sprawdzanie c z y pu n kt n a l e z y do p r o s t o k a t a .
>>> p in s , q not in s
# P r o s t o k a t p o k r y w a j a c y dwa p r o s t o k a t y .
>>> r = s . c o v e r ( t )
# Wyznaczanie c z e s c i w s p o l n e j dwoch p r o s t o k a t o w .
>>> r = s . i n t e r s e c t i o n ( t )
# Test c z y p r o s t o k a t j e s t kwadratem ( b o o l ) .
>>> s . i s _ s q u a r e ( )
# Przesuniecie prostokata o wektor .
>>> t = s . move ( x , y )
>>> t = s . move ( p )
# Hashowanie p r o s t o k a t a .
>>> hash ( s )
3.3.1. Zdarzenie
Struktura wykorzystana jest do przechowywania informacji o zdarzeniu
(ang. event), w którym prosta zamiatająca się zatrzymuje. Będzie to klasa
Event, która definiuje atrybuty klasy opisujące typy zdarzeń. Jest reprezento-
wana za pomocą punktu, typu zdarzenia i odcinka (w pewnych przypadkach
dwóch odcinków). Jedyną operacją wykonywaną na tym obiekcie jest opera-
tor porównywania, w naszej implementacji wyższy priorytet ma współrzędna
x. Na podstawie typu zdarzenia w rozważanych algorytmach wybierana jest
obsługa tego zdarzenia. Obiekt ten jest hashowalny.
c l a s s Event :
"""The c l a s s d e f i n i n g an e v e n t . """
LEFT = 0
CROSSING = 1
11
15:1104424163
RIGHT = 2
HORIZONTAL = 3
VERTICAL = 4
def __str__( s e l f ) :
return " Event ( { } , { } ) " . format ( s e l f . pt , s e l f . type )
def __cmp__( s e l f , o t h e r ) :
return cmp( s e l f . pt , o t h e r . pt )
def __hash__( s e l f ) :
""" H a s h a b l e e v e n t s . """
return hash ( ( s e l f . pt , s e l f . type ) ) # hash b a s e d on t u p l e
import heapq
class PriorityQueue :
"""A p r i o r i t y queue w i t h o u t r e p e t i t i o n s , O( l o g n ) time o p e r a t i o n s . """
def __init__ ( s e l f ) :
s e l f . heap = [ ]
s e l f . item_set = set ( ) # quick search
def __str__( s e l f ) :
return s t r ( s e l f . heap )
12
16:8802975520
heapq . heappush ( s e l f . heap , item )
s e l f . item_set . add ( item )
def pop ( s e l f ) :
i f not s e l f . is_empty ( ) :
item = heapq . heappop ( s e l f . heap )
s e l f . item_set . remove ( item )
return item
else :
r a i s e V a l u e E r r o r ( "Queue i s empty" )
def is_empty ( s e l f ) :
return len ( s e l f . heap ) == 0
def __len__( s e l f ) :
return len ( s e l f . heap )
# h t t p s : / / r o s e t t a c o d e . o r g / w i k i /AVL_tree#Python
# h t t p s : / / g i t h u b . com/ TylerSandman /py−b s t
c l a s s Node :
"""The c l a s s d e f i n i n g a node . """
def __init__ ( s e l f , v a l u e ) :
s el f . value = value
s e l f . p a r e n t = None
s e l f . l e f t = None
s e l f . r i g h t = None
def __repr__( s e l f ) :
return s t r ( s e l f . v a l u e )
def i n s e r t ( s e l f , node ) :
13
17:8932153279
i f node i s None :
return
i f node . v a l u e < s e l f . v a l u e :
i f s e l f . l e f t i s None :
node . p a r e n t = s e l f
s e l f . l e f t = node
else :
s e l f . l e f t . i n s e r t ( node )
else :
i f s e l f . r i g h t i s None :
node . p a r e n t = s e l f
s e l f . r i g h t = node
else :
s e l f . r i g h t . i n s e r t ( node )
def f i n d ( s e l f , v a l u e ) :
i f v a l u e == s e l f . v a l u e :
return s e l f
e l i f value < s el f . value :
i f s e l f . l e f t i s None :
return None
else :
return s e l f . l e f t . f i n d ( v a l u e )
else :
i f s e l f . r i g h t i s None :
return None
else :
return s e l f . r i g h t . f i n d ( v a l u e )
def find_min ( s e l f ) :
current = self
while c u r r e n t . l e f t i s not None :
current = current . l e f t
return c u r r e n t
def find_max ( s e l f ) :
current = self
while c u r r e n t . r i g h t i s not None :
current = current . right
return c u r r e n t
def s u c c e s s o r ( s e l f ) :
i f s e l f . r i g h t i s not None :
return s e l f . r i g h t . find_min ( )
current = self
while c u r r e n t . p a r e n t i s not None and c u r r e n t i s c u r r e n t . p a r e n t . r i g h t :
current = current . parent
return c u r r e n t . p a r e n t
def p r e d e c e s s o r ( s e l f ) :
i f s e l f . l e f t i s not None :
return s e l f . l e f t . find_max ( )
current = self
while c u r r e n t . p a r e n t i s not None and c u r r e n t i s c u r r e n t . p a r e n t . l e f t :
current = current . parent
return c u r r e n t . p a r e n t
14
18:5455142588
def h e i g h t ( node ) :
i f node i s None :
return −1
else :
return node . h e i g h t
c l a s s AVLTree :
"""The c l a s s d e f i n i n g an AVL t r e e . """
def __init__ ( s e l f ) :
s e l f . r o o t = None
def i n s e r t ( s e l f , v a l u e ) :
node = Node ( v a l u e )
i f s e l f . r o o t i s None :
s e l f . r o o t = node
else :
s e l f . r o o t . i n s e r t ( node )
s e l f . r e b a l a n c e ( node )
def f i n d ( s e l f , k ) :
return s e l f . r o o t and s e l f . r o o t . f i n d ( k )
def d e l e t e ( s e l f , v a l u e ) :
node = s e l f . f i n d ( v a l u e )
i f node :
i f not ( node . l e f t or node . r i g h t ) :
s e l f . _ d e l e t e _ l e a f ( node )
e l i f not ( node . l e f t and node . r i g h t ) :
s e l f . _ d e l e t e _ l e a f _ p a r e n t ( node )
else :
s e l f . _delete_node ( node )
def _ d e l e t e _ l e a f ( s e l f , node ) :
parent_node = node . p a r e n t
i f parent_node :
i f parent_node . l e f t == node :
parent_node . l e f t = None
else :
parent_node . r i g h t = None
del node
s e l f . r e b a l a n c e ( parent_node )
else :
s e l f . r o o t = None
def _ d e l e t e _ l e a f _ p a r e n t ( s e l f , node ) :
parent_node = node . p a r e n t
i f node . v a l u e == s e l f . r o o t . v a l u e :
i f node . r i g h t :
15
19:4537362069
s e l f . r o o t = node . r i g h t
node . r i g h t = None
s e l f . r o o t . p a r e n t = None
else :
s e l f . r o o t = node . l e f t
node . l e f t = None
s e l f . r o o t . p a r e n t = None
else :
i f parent_node . r i g h t == node :
i f node . r i g h t :
parent_node . r i g h t = node . r i g h t
parent_node . r i g h t . p a r e n t = parent_node
node . r i g h t = None
else :
parent_node . r i g h t = node . l e f t
parent_node . r i g h t . p a r e n t = parent_node
node . l e f t = None
else :
i f node . r i g h t :
parent_node . l e f t = node . r i g h t
parent_node . l e f t . p a r e n t = parent_node
node . r i g h t = None
else :
parent_node . l e f t = node . l e f t
parent_node . l e f t . p a r e n t = parent_node
node . l e f t = None
del node
s e l f . r e b a l a n c e ( parent_node )
i f s w i t c h 1 . v a l u e == s e l f . r o o t . v a l u e :
s e l f . r o o t . v a l u e = node2 . v a l u e
s w i t c h 2 . v a l u e = temp_value
e l i f s w i t c h 2 . v a l u e == s e l f . r o o t . v a l u e :
switch1 . value = s e l f . root . value
s e l f . r o o t . v a l u e = temp_value
else :
s w i t c h 1 . v a l u e = node2 . v a l u e
s w i t c h 2 . v a l u e = temp_value
16
20:6587172736
s e l f . _delete_leaf_parent ( to_delete )
else :
to_switch = node . s u c c e s s o r ( )
s e l f . _switch_nodes ( node , to_switch )
def s u c c e s s o r ( s e l f , v a l u e ) :
node = s e l f . f i n d ( v a l u e )
return node and node . s u c c e s s o r ( )
def p r e d e c e s s o r ( s e l f , v a l u e ) :
node = s e l f . f i n d ( v a l u e )
return node and node . p r e d e c e s s o r ( )
def r e b a l a n c e ( s e l f , node ) :
while node i s not None :
update_height ( node )
i f h e i g h t ( node . l e f t ) >= 2 + h e i g h t ( node . r i g h t ) :
i f h e i g h t ( node . l e f t . l e f t ) >= h e i g h t ( node . l e f t . r i g h t ) :
s e l f . r i g h t _ r o t a t e ( node )
else :
s e l f . l e f t _ r o t a t e ( node . l e f t )
s e l f . r i g h t _ r o t a t e ( node )
e l i f h e i g h t ( node . r i g h t ) >= 2 + h e i g h t ( node . l e f t ) :
i f h e i g h t ( node . r i g h t . r i g h t ) >= h e i g h t ( node . r i g h t . l e f t ) :
s e l f . l e f t _ r o t a t e ( node )
else :
s e l f . r i g h t _ r o t a t e ( node . r i g h t )
s e l f . l e f t _ r o t a t e ( node )
node = node . p a r e n t
def l e f t _ r o t a t e ( s e l f , x ) :
y = x . right
y . parent = x . parent
i f y . p a r e n t i s None :
self . root = y
else :
i f y . parent . l e f t i s x :
y . parent . l e f t = y
e l i f y . parent . r i g h t i s x :
y . parent . r i g h t = y
x . right = y . l e f t
i f x . r i g h t i s not None :
x . r i g h t . parent = x
y. left = x
x . parent = y
update_height ( x )
update_height ( y )
def r i g h t _ r o t a t e ( s e l f , x ) :
y = x. left
17
21:1157205679
y . parent = x . parent
i f y . p a r e n t i s None :
self . root = y
else :
i f y . parent . l e f t i s x :
y . parent . l e f t = y
e l i f y . parent . r i g h t i s x :
y . parent . r i g h t = y
x . l e f t = y . right
i f x . l e f t i s not None :
x . l e f t . parent = x
y . right = x
x . parent = y
update_height ( x )
update_height ( y )
c l a s s Node :
"""The c l a s s d e f i n i n g a node . """
def c a l c u l a t e ( s e l f , x ) :
return s e l f . v a l u e . c a l c u l a t e _ y ( x )
def i n s e r t ( s e l f , node , x ) :
i f node i s None :
return
18
22:4148860838
i f node . c a l c u l a t e ( x ) < s e l f . c a l c u l a t e ( x ) :
i f s e l f . l e f t i s None :
node . p a r e n t = s e l f
s e l f . l e f t = node
else :
s e l f . l e f t . i n s e r t ( node , x )
else :
i f s e l f . r i g h t i s None :
node . p a r e n t = s e l f
s e l f . r i g h t = node
else :
s e l f . r i g h t . i n s e r t ( node , x )
c l a s s AVLTree :
"""The c l a s s d e f i n i n g an AVL t r e e . """
def __init__ ( s e l f ) :
s e l f . r o o t = None
s e l f .D = dict ( )
s e l f . current_x = None
def i n s e r t ( s e l f , v a l u e ) :
node = Node ( v a l u e )
i f s e l f . r o o t i s None :
s e l f . r o o t = node
else :
s e l f . r o o t . i n s e r t ( node , s e l f . current_x )
s e l f .D[ v a l u e ] = node
s e l f . r e b a l a n c e ( node )
def d e l e t e ( s e l f , v a l u e ) :
node = s e l f .D. g e t ( v a l u e )
i f node :
i f not ( node . l e f t or node . r i g h t ) :
s e l f . _ d e l e t e _ l e a f ( node )
del s e l f .D[ v a l u e ]
e l i f not ( node . l e f t and node . r i g h t ) :
s e l f . _ d e l e t e _ l e a f _ p a r e n t ( node )
del s e l f .D[ v a l u e ]
else :
s e l f . _delete_node ( node )
s w i t c h 1 = node1
s w i t c h 2 = node2
temp_value = s w i t c h 1 . v a l u e
i f s w i t c h 1 . v a l u e == s e l f . r o o t . v a l u e :
s e l f . r o o t . v a l u e = node2 . v a l u e
s w i t c h 2 . v a l u e = temp_value
e l i f s w i t c h 2 . v a l u e == s e l f . r o o t . v a l u e :
switch1 . value = s e l f . root . value
s e l f . r o o t . v a l u e = temp_value
19
23:1097344596
else :
s w i t c h 1 . v a l u e = node2 . v a l u e
s w i t c h 2 . v a l u e = temp_value
def f i n d ( s e l f , v a l u e ) :
return s e l f .D. g e t ( v a l u e )
def s u c c e s s o r ( s e l f , v a l u e ) :
node = s e l f .D. g e t ( v a l u e )
return node and node . s u c c e s s o r ( )
def p r e d e c e s s o r ( s e l f , v a l u e ) :
node = s e l f .D. g e t ( v a l u e )
return node and node . p r e d e c e s s o r ( )
# U t w o r z e n i e z b i o r u odcinkow w p o z y c j i o g o l n e j .
>>> segments = set ( )
>>> segments . add ( Segment ( 9 , 1 1 , 0 , 2 ) )
>>> segments . add ( Segment ( 4 , 0 , 1 1 , 7 ) )
>>> segments . add ( Segment ( 1 0 , 2 , 1 , 1 1 ) )
>>> segments . add ( Segment ( 2 , 6 , 7 , 1 ) )
# Wyznaczenie w s z y s t k i c h punktow p r z e c i e c i a .
>>> from bentleyottmann import BentleyOttmann
>>> i _ p o i n t s = BentleyOttmann ( segments ) . run ( )
>>> print ( i _ p o i n t s )
[ ... ]
# S p r a w d z e n i e c z y z b i o r odcinkow ma j a k i e k o l w i e k p r z e c i e c i e .
>>> from shamoshoey import ShamosHoey
>>> print ( ShamosHoey ( segments ) . run ( ) )
True
>>> segments2 = set ( )
>>> segments2 . add ( Segment ( 1 , 2 , 3 , 0 ) )
>>> segments2 . add ( Segment ( 2 , 3 , 4 , 4 ) )
>>> print ( ShamosHoey ( segments2 ) . run ( ) )
False
20
24:4915194270
# Wyznaczanie w s z y s t k i c h p r z e c i e c odcinkow pionowych i poziomych .
>>> from h o r i z o n t a l v e r t i c a l import H o r i z o n t a l V e r t i c a l
>>> i _ p o i n t s 2 = H o r i z o n t a l V e r t i c a l ( segments3 ) . run ( )
>>> print ( i _ p o i n t s 2 )
[ ... ]
# Wygenerowanie z b i o r u 10 punktow na p l a s z c z y z n i e .
>>> p l i s t = set ( [ Po in t ( random ( ) , random ( ) ) f o r _ in range ( 1 0 ) ] )
# I n t e r f e j s w s z y s t k i c h zaimplementowanych algorytmow
# do w y z n a c z a n i a pary n a j b l i z s z y c h punktow j e s t i d e n t y c z n y
# C l o s e s t P a i r ( p o i n t s ) . run ( )
# U t w o r z e n i e drzewa czworkowego na k w a d r a c i e 1 x1
# z p o j e m n o s c i a rowna 4 .
>>> qt = QuadTree ( R e c t a n g l e ( 0 , 0 , 1 , 1 ) , 4 )
21
25:7737684541
>>> print ( len ( qt . b o t t o m _ l e f t . p o i n t _ l i s t ) )
4
>>> print ( len ( qt . bottom_right . p o i n t _ l i s t ) )
0
# S z u k a n i e punktow n a l e z a c y c h do d a n e j p l a s z c z y z n y .
>>> qt . i n s e r t ( Po in t ( 0 . 4 , 0 . 6 ) )
True
>>> qt . i n s e r t ( Po in t ( 0 . 6 , 0 . 6 ) )
True
>>> p_in_range = qt . query ( R e c t a n g l e ( 0 . 2 , 0 . 1 , 0 . 7 , 0 . 7 ) )
>>> print ( p_in_range )
[ Po in t ( 0 . 4 , 0 . 6 ) ,
Po i nt ( 0 . 6 , 0 . 6 ) ,
Po i nt ( 0 . 2 , 0 . 1 ) ,
Po i nt ( 0 . 2 , 0 . 2 ) ]
# S z u k a n i e n a j b l i z s z e g o s a s i a d a danego punktu .
>>> print ( qt . n e a r e s t ( Po in t ( 0 . 7 , 0 . 5 ) ) )
Po i nt ( 0 . 6 , 0 . 6 )
26:2788114970
4. Algorytmy
W tym rozdziale zaprezentujemy implementację niektórych algorytmów
wykorzystywanych w geometrii obliczeniowej. Skupimy uwagę na specjalnej
grupie takich algorytmów, które wykorzystują technikę zamiatania.
23
27:4405874116
Początkowo Y -struktura jest pusta, ponieważ możemy sobie wyobrazić, że
miotła startuje w punkcie x = −∞. Algorytm rozpoczynamy od inicjalizacji
dwóch powyższych struktur. Następnie sprawdzamy, czy kolejka zdarzeń jest
pusta. Jeśli nie, to pobieramy kolejne zdarzenie i obsługujemy je w zależności
od typu.
— Początek odcinka s.
Dodajemy odcinek s do Y -struktury. Niech s1 i s2 będą kolejno poprzed-
nikiem i następnikiem odcinka s w aktualnym położeniu miotły. Jeśli
odcinek s1 istnieje, sprawdzamy, czy przecina się z odcinkiem s na prawo
od miotły. Wykryte przecięcie dodajemy do kolejki zdarzeń. Analogicznie
sprawdzamy przecięcie odcinka s2 z odcinkiem s.
— Koniec odcinka s.
Niech s1 i s2 będą kolejno poprzednikiem i następnikiem odcinka s w ak-
tualnym położeniu miotły. Usuwamy odcinek s z Y -struktury. Jeśli s1 i
s2 istnieją, sprawdzamy ich potencjalne przecięcie na prawo od miotły i
dodajemy do kolejki zdarzeń.
— Przecięcie dwóch odcinków s1 i s2 .
Dodajemy punkt zdarzenia do wynikowego zbioru punktów przecięć. Niech
s1 będzie odcinkiem znajdującym się nad odcinkiem s2 w aktualnym po-
łożeniu miotły. Zamieniamy ich kolejność w drzewie tak, aby odcinek
s2 był bezpośrednio nad odcinkiem s1 . Niech odcinek s02 będzie górnym
sąsiadem (następnikiem) odcinka s2 , natomiast odcinek s01 będzie dolnym
sąsiadem (poprzednikiem) odcinka s1 . Sprawdzamy potencjalne przecięcia
odcinka s2 z odcinkiem s02 i odcinka s1 z odcinkiem s01 na prawo od miotły
i dodajemy je do kolejki zdarzeń.
Po wyczerpaniu się zdarzeń z X-struktury otrzymujemy wszystkie moż-
liwe punkty przecięcia odcinków wejściowych.
24
28:1219954935
Kolejka zdarzeń musi być zainicjalizowana punktami przecięcia prostych są-
siadujących ze sobą dla x = −∞ [17].
B b2
c1 C
a1 A a2
b1 c2
l = −∞
X-struktura: a1 , b1 , c1 , b2 , c2 , a2
Y -struktura: ∅
B b2
c1 C
a1 A a2
b1 c2
l = a1
X-struktura: b1 , c1 , b2 , c2 , a2
Y -struktura: A
B b2
c1 C
a1 A a2
b1 i1 c2
l = b1
X-struktura: i1 , c1 , b2 , c2 , a2
Y -struktura: A, B
25
29:1001943008
B b2
c1 C
a1 A a2
b1 i1 c2
l = i1
X-struktura: c1 , b2 , c2 , a2
Y -struktura: B, A
B b2
c1 C
i2
a1 A a2
b1 i1 c2
l = c1
X-struktura: i2 , b2 , c2 , a2
Y -struktura: C, B, A
B b2
c1 C
i2
a1 A a2
b1 i1 i3 c2
l = i2
X-struktura: b2 , i3 , c2 , a2
Y -struktura: B, C, A
26
30:2411206760
B b2
c1 C
i2
a1 A a2
b1 i1 i3 c2
l = b2
X-struktura: i3 , c2 , a2
Y -struktura: C, A
B b2
c1 C
i2
a1 A a2
b1 i1 i3 c2
l = i3
X-struktura: c2 , a2
Y -struktura: A, C
B b2
c1 C
i2
a1 A a2
b1 i1 i3 c2
l = c2
X-struktura: a2
Y -struktura: A
27
31:1148721320
B b2
c1 C
i2
a1 A a2
b1 i1 i3 c2
l = a2
X-struktura: ∅
Y -struktura: ∅
from p r i o r i t y _ q u e u e import P r i o r i t y Q u e u e
from a v l t r e e 2 import AVLTree
from e v e n t s import Event
c l a s s BentleyOttmann :
""" B e n t l e y −Ottmann a l g o r i t h m . """
f o r segment in segment_set :
i f segment . pt1 . x > segment . pt2 . x :
segment = ~segment
s e l f . eq . push ( Event ( segment . pt1 , Event . LEFT, segment ) )
s e l f . eq . push ( Event ( segment . pt2 , Event . RIGHT, segment ) )
self . i l = [ ] # intersection l i s t
def run ( s e l f ) :
while not s e l f . eq . is_empty ( ) :
e v e n t = s e l f . eq . pop ( )
i f e v e n t . type == Event . LEFT :
s e l f . _handle_left_endpoint ( e v e n t )
e l i f e v e n t . type == Event . RIGHT :
s e l f . _handle_right_endpoint ( e v e n t )
e l i f e v e n t . type == Event . CROSSING :
s e l f . _ h an d l e _c r o s s in g ( e v e n t )
else :
r a i s e V a l u e E r r o r ( "unknown e v e n t " )
del s e l f . eq
del s e l f . s l
return s e l f . i l
def _handle_left_endpoint ( s e l f , e v e n t ) :
segment_e = e v e n t . segment
s e l f . s l . current_x = e v e n t . pt . x
28
32:9732897725
s e l f . s l . i n s e r t ( segment_e )
segment_above = s e l f . s l . s u c c e s s o r ( segment_e )
segment_below = s e l f . s l . p r e d e c e s s o r ( segment_e )
def _handle_right_endpoint ( s e l f , e v e n t ) :
segment_e = e v e n t . segment
s e l f . s l . current_x = e v e n t . pt . x
segment_above = s e l f . s l . s u c c e s s o r ( segment_e )
segment_below = s e l f . s l . p r e d e c e s s o r ( segment_e )
s e l f . s l . d e l e t e ( segment_e )
def _ h an d l e _ cr o s s i ng ( s e l f , e v e n t ) :
s e l f . i l . append ( e v e n t . pt )
s e l f . s l . current_x = e v e n t . pt . x
segment_e1 = e v e n t . segment_above
segment_e2 = e v e n t . segment_below
segment_above_e2 = s e l f . s l . s u c c e s s o r ( segment_e2 )
segment_below_e1 = s e l f . s l . p r e d e c e s s o r ( segment_e1 )
29
33:5105848250
i f p o i n t and p o i n t . x > e v e n t . pt . x :
s e l f . eq . push ( Event ( p o i n t ,
Event . CROSSING,
segment_above_e2 ,
segment_e2 ) )
s e l f . s l . i n s e r t ( segment1 )
s e l f . s l . i n s e r t ( segment2 )
Złożoność: Złożoność czasowa algorytmu wynosi O(n log n), złożoność pa-
mięciowa wynosi O(n). Zostało to potwierdzone w dodatku A. X-struktura
30
34:9375432673
po zainicjalizowaniu zawiera tylko 2n końców odcinków, a potem się zmniej-
sza. Ponadto nie przechowuje się listy punktów przecięcia.
c l a s s ShamosHoey :
""" Shamos−Hoey a l g o r i t h m . """
f o r segment in segment_set :
i f segment . pt1 . x > segment . pt2 . x :
segment = ~segment
s e l f . eq . push ( Event ( segment . pt1 , Event . LEFT, segment ) )
s e l f . eq . push ( Event ( segment . pt2 , Event . RIGHT, segment ) )
def run ( s e l f ) :
while not s e l f . eq . is_empty ( ) :
e v e n t = s e l f . eq . pop ( )
i f e v e n t . type == Event . LEFT :
i f s e l f . _handle_left_endpoint ( e v e n t ) :
return True
e l i f e v e n t . type == Event . RIGHT :
i f s e l f . _handle_right_endpoint ( e v e n t ) :
return True
else :
r a i s e V a l u e E r r o r ( "unknown e v e n t " )
del s e l f . eq
del s e l f . s l
return F a l s e
def _handle_left_endpoint ( s e l f , e v e n t ) :
segment_e = e v e n t . segment
s e l f . s l . current_x = e v e n t . pt . x
s e l f . s l . i n s e r t ( segment_e )
segment_above = s e l f . s l . s u c c e s s o r ( segment_e )
segment_below = s e l f . s l . p r e d e c e s s o r ( segment_e )
31
35:5636080614
i f segment_e . i n t e r s e c t ( segment_above ) :
return True
return F a l s e
def _handle_right_endpoint ( s e l f , e v e n t ) :
segment_e = e v e n t . segment
segment_above = s e l f . s l . s u c c e s s o r ( segment_e )
segment_below = s e l f . s l . p r e d e c e s s o r ( segment_e )
s e l f . s l . d e l e t e ( segment_e )
return F a l s e
32
36:6941679396
powodu wykorzystano tutaj standardową listę, na której operacja sortowania
działa w czasie O(n log n).
Algorytm pobiera kolejno zdarzenia z kolejki zdarzeń i obsługuje je w za-
leżności od typu:
X-struktura: s
t u
X-struktura: t, u
— Odcinek poziomy s.
Niech odcinek s będzie odcinkiem o początku w punkcie (a, b) i końcu
w punkcie (c, b), gdzie c > a. W takim wypadku należy znaleźć wszystkie
odcinki pionowe, których współrzędna x mieści się w przedziale [a, c]. Aby
to osiągnąć, dodajemy odcinek poziomy do naszej X-struktury, a następ-
nie szukamy wszystkie odcinki, które spełniają powyższą zależność. Dla
każdego znalezionego odcinka wyznaczamy punkt przecięcia z odcinkiem
s i dodajemy do listy wynikowej przecięć. Na końcu usuwamy odcinek s
z X-struktury.
t (a, b) (c, b)
s
r
X-struktura: t, r, u
33
37:4254172910
Złożoność: Złożoność czasowa algorytmu wynosi O((n + k) log n), złożo-
ność pamięciowa wynosi O(n). Testy w dodatku A potwierdziły złożoność z
literatury.
class HorizontalVertical :
""" I n t e r s e c t i o n s o f h o r i z o n t a l and v e r t i c a l l i n e segments . """
f o r segment in segment_set :
i f segment . pt1 . x > segment . pt2 . x :
segment = ~segment
i f segment . pt1 . x == segment . pt2 . x : # v e r t i c a l segment
s e l f . eq . append ( Event ( segment . pt1 ,
Event . LEFT, segment ) )
s e l f . eq . append ( Event ( segment . pt2 ,
Event . RIGHT, segment ) )
e l i f segment . pt1 . y == segment . pt2 . y : # h o r i z o n t a l segment
s e l f . eq . append ( Event ( segment . pt1 ,
Event .HORIZONTAL, segment ) )
else :
raise ValueError ( " h o r i z o n t a l or "
" v e r t i c a l segments a r e a l l o w e d " )
s e l f . eq . s o r t ( key=lambda e v e n t : e v e n t . pt . y )
self . i l = [ ] # intersection l i s t
def run ( s e l f ) :
""" P r o c e s s i n g e v e n t s . """
f o r e v e n t in s e l f . eq :
i f e v e n t . type == Event . LEFT :
s e l f . _handle_bottom_endpoint ( e v e n t )
e l i f e v e n t . type == Event . RIGHT :
s e l f . _handle_top_endpoint ( e v e n t )
e l i f e v e n t . type == Event .HORIZONTAL:
s e l f . _ h an d l e _ cr o s s i ng ( e v e n t )
else :
r a i s e V a l u e E r r o r ( "unknown e v e n t " )
del s e l f . eq
34
38:2894025259
del s e l f . s l
return s e l f . i l
def _handle_bottom_endpoint ( s e l f , e v e n t ) :
s e l f . s l . i n s e r t ( e v e n t . segment )
def _handle_top_endpoint ( s e l f , e v e n t ) :
s e l f . s l . d e l e t e ( e v e n t . segment )
def _ h an d l e _ cr o s s i ng ( s e l f , e v e n t ) :
segment_e = e v e n t . segment
x_min = segment_e . pt1 . x
x_max = segment_e . pt2 . x
s e l f . s l . i n s e r t ( segment_e )
node = s e l f . s l . s u c c e s s o r ( segment_e )
while node and node . v a l u e . pt1 . x < x_max :
p o i n t = segment_e . i n t e r s e c t i o n _ p o i n t ( node . v a l u e )
s e l f . i l . append ( p o i n t )
node = s e l f . s l . s u c c e s s o r ( node . v a l u e )
s e l f . s l . d e l e t e ( segment_e )
# EOF
35
39:9537392183
Listing 4.4. Funkcja find_two_closest_points().
def f i n d _ t w o _ c l o s e s t _ p o i n t s ( p o i n t _ l i s t ) :
""" Find two c l o s e s t p o i n t s i n O( n^2) time ( b r u t e f o r c e ) . """
dist = float (" i n f ")
p a i r = None
n = len ( p o i n t _ l i s t )
f o r i in xrange ( n ) :
f o r j in xrange ( i +1, n ) :
vec = p o i n t _ l i s t [ j ] − p o i n t _ l i s t [ i ]
new_dist = vec ∗ vec
i f new_dist < d i s t :
d i s t = new_dist
pair = point_list [ i ] , point_list [ j ]
return p a i r
Złożoność: Złożoność czasowa algorytmu wynosi O(n log n), złożoność pa-
mięciowa wynosi O(n). Wykazano to podczas testów, które można znaleźć w
dodatku A.
36
40:5565616079
Uwaga 1: Po każdym obiegu pętli mamy obliczoną najmniejszą odległość
pomiędzy wszystkimi punktami poniżej miotły.
class ClosestPair :
""" S o l v i n g t h e c l o s e s t p a i r problem . """
def __init__ ( s e l f , p o i n t _ l i s t ) :
""" I n i t i a l i z e s t r u c t u r e s . """
i f len ( p o i n t _ l i s t ) < 2 :
r a i s e V a l u e E r r o r ( "minimum 2 p o i n t s " )
def run ( s e l f ) :
""" F i n d i n g t h e c l o s e s t p a i r . """
i f len ( s e l f . p o i n t s ) == 2 :
return s e l f . c l o s e s t _ p a i r
top = 2
new_point = s e l f . p o i n t s [ top ]
bottom = 0
while s e l f . p o i n t s [ bottom ] . x <= new_point . x − s e l f . min_distance :
bottom += 1
f o r i in range ( bottom , top ) :
self . active_points . i n s e r t ( self . points [ i ] )
37
41:1112904539
p o i n t = node . v a l u e
n e i g h b o r s . append ( p o i n t )
else :
break
# t h r e e p o i n t s on l e f t s i d e
p o i n t = new_point
f o r _ in range ( 3 ) :
node = s e l f . a c t i v e _ p o i n t s . p r e d e c e s s o r ( p o i n t )
i f node i s not None :
p o i n t = node . v a l u e
n e i g h b o r s . append ( node . v a l u e )
else :
break
f o r p o i n t in n e i g h b o r s :
new_distance = ( new_point − p o i n t ) . l e n g t h ( )
i f new_distance < s e l f . min_distance :
s e l f . min_distance = new_distance
s e l f . c l o s e s t _ p a i r = p o i n t , new_point
top += 1
return s e l f . c l o s e s t _ p a i r
38
42:1927421938
p4
p2
p5
p3
p1
d d
p7
p6
p4
p2
d
p5
p3
p1
39
43:1109680094
p4
p2
d1
d2 p5
p3
d2 < d1
A więc najbliższą parą jest para <p2, p5>
from t o o l s 1 import f i n d _ t w o _ c l o s e s t _ p o i n t s
class ClosestPair :
""" S o l v i n g t h e c l o s e s t p a i r problem u s i n g d i v i d e and conquer . """
def __init__ ( s e l f , p o i n t _ l i s t ) :
i f len ( p o i n t _ l i s t ) < 2 :
r a i s e V a l u e E r r o r ( "minimum 2 p o i n t s " )
self . points = point_list
self . points . sort ()
s e l f . c l o s e s t _ p a i r = None
s e l f . min_distance = None
def run ( s e l f ) :
s e l f . c l o s e s t _ p a i r = s e l f . _ c l o s e s t ( 0 , len ( s e l f . p o i n t s ) −1)
s e l f . min_distance = s e l f . p a i r _ l e n g t h ( s e l f . c l o s e s t _ p a i r )
return s e l f . c l o s e s t _ p a i r
def _ c l o s e s t ( s e l f , l e f t , r i g h t ) :
i f ( r i g h t − l e f t ) <= 2 :
return f i n d _ t w o _ c l o s e s t _ p o i n t s ( s e l f . p o i n t s [ l e f t : r i g h t +1])
middle = ( l e f t + r i g h t ) // 2
c l o s e s t _ l e f t = s e l f . _ c l o s e s t ( l e f t , middle )
c l o s e s t _ r i g h t = s e l f . _ c l o s e s t ( middle + 1 , r i g h t )
40
44:5487688407
left_d = s e l f . pair_length ( c l o s e s t _ l e f t )
right_d = s e l f . p a i r _ l e n g t h ( c l o s e s t _ r i g h t )
i f l e f t _ d < right_d :
closest_pair = closest_left
current_d = l e f t _ d
else :
closest_pair = closest_right
current_d = right_d
strip = [ ]
f o r i in xrange ( l e f t , r i g h t +1): # O( n ) time
i f abs ( s e l f . p o i n t s [ middle ] . x − s e l f . p o i n t s [ i ] . x ) < current_d :
s t r i p . append ( s e l f . p o i n t s [ i ] )
i f len ( s t r i p ) < 2 :
closest_strip = closest_pair
else :
c l o s e s t _ s t r i p = s e l f . _ c l o s e s t _ o n _ s t r i p ( s t r i p , current_d )
i f current_d < s e l f . p a i r _ l e n g t h ( c l o s e s t _ s t r i p ) :
return c l o s e s t _ p a i r
else :
return c l o s e s t _ s t r i p
def _ c l o s e s t _ o n _ s t r i p ( s e l f , s t r i p , d i s t a n c e ) : # O( n l o g n ) time
min_distance = d i s t a n c e
s t r i p . s o r t ( key=lambda p o i n t : p o i n t . y )
closest_pair = strip [0] , strip [ 1 ]
f o r i in range ( len ( s t r i p ) ) :
f o r j in range ( i + 1 , len ( s t r i p ) ) :
i f s t r i p [ j ] . y − s t r i p [ i ] . y > min_distance :
break
new_distance = ( s t r i p [ i ] − s t r i p [ j ] ) . l e n g t h ( )
i f new_distance < min_distance :
min_distance = new_distance
closest_pair = strip [ i ] , strip [ j ]
return c l o s e s t _ p a i r
def p a i r _ l e n g t h ( s e l f , p a i r ) :
return ( p a i r [ 0 ] − p a i r [ 1 ] ) . l e n g t h ( )
41
45:1004672650
Listing 4.7. Klasa ClosestPair z modułu closestpair4 .
#! / u s r / b i n / python
from t o o l s 1 import f i n d _ t w o _ c l o s e s t _ p o i n t s
class ClosestPair :
""" S o l v i n g t h e c l o s e s t p a i r problem u s i n g d i v i d e and conquer . """
def __init__ ( s e l f , p o i n t _ l i s t ) :
i f len ( p o i n t _ l i s t ) < 2 :
r a i s e V a l u e E r r o r ( "minimum 2 p o i n t s " )
s e l f . P = set ( p o i n t _ l i s t )
s e l f . c l o s e s t _ p a i r = None
s e l f . min_distance = None
def run ( s e l f ) :
X = l i s t ( s e l f . P)
X. s o r t ( key=lambda p o i n t : point . x)
Y = l i s t ( s e l f . P)
Y. s o r t ( key=lambda p o i n t : point . y)
self . closest_pair = self . _ c l o s e s t ( s e l f . P , X, Y)
s e l f . min_distance = s e l f . pair_length ( s e l f . closest_pair )
return s e l f . c l o s e s t _ p a i r
def _ c l o s e s t ( s e l f , P , X, Y ) :
i f len (P) <= 3 :
return f i n d _ t w o _ c l o s e s t _ p o i n t s (X)
Y_left = [ ]
Y_right = [ ]
f o r p o i n t in Y: # O( n ) time
i f p o i n t in P _ l e f t :
Y_left . append ( p o i n t )
else :
Y_right . append ( p o i n t )
c l o s e s t _ l e f t = s e l f . _ c l o s e s t ( P_left , X_left , Y_left )
c l o s e s t _ r i g h t = s e l f . _ c l o s e s t ( P_right , X_right , Y_right )
left_d = s e l f . pair_length ( c l o s e s t _ l e f t )
right_d = s e l f . p a i r _ l e n g t h ( c l o s e s t _ r i g h t )
i f l e f t _ d < right_d :
closest_pair = closest_left
current_d = l e f t _ d
else :
closest_pair = closest_right
current_d = right_d
strip = [ ]
f o r p o i n t in Y: # O( n ) time
i f abs ( middle_point . x − p o i n t . x ) < current_d :
42
46:8913093406
s t r i p . append ( p o i n t )
i f len ( s t r i p ) < 2 :
closest_strip = closest_pair
else :
c l o s e s t _ s t r i p = s e l f . _ c l o s e s t _ o n _ s t r i p ( s t r i p , current_d )
i f current_d < s e l f . p a i r _ l e n g t h ( c l o s e s t _ s t r i p ) :
return c l o s e s t _ p a i r
else :
return c l o s e s t _ s t r i p
def _ c l o s e s t _ o n _ s t r i p ( s e l f , s t r i p , d i s t a n c e ) : # O( n ) time
min_distance = d i s t a n c e
closest_pair = strip [0] , strip [ 1 ]
f o r i in range ( len ( s t r i p ) ) :
f o r j in range ( i + 1 , len ( s t r i p ) ) :
i f s t r i p [ j ] . y − s t r i p [ i ] . y > min_distance :
break
new_distance = ( s t r i p [ i ] − s t r i p [ j ] ) . l e n g t h ( )
i f new_distance < min_distance :
min_distance = new_distance
closest_pair = strip [ i ] , strip [ j ]
return c l o s e s t _ p a i r
def p a i r _ l e n g t h ( s e l f , p a i r ) :
return ( p a i r [ 0 ] − p a i r [ 1 ] ) . l e n g t h ( )
43
47:1135196510
Struktura ta przechowuje punkty leżące na wyznaczonym prostokątnym
obszarze powierzchni dwuwymiarowej. Każdy węzeł takiego drzewa może
przechowywać maksymalnie k punktów. Próba dodania kolejnego punktu
skutkuje podziałem obszaru na cztery równe części i przenosi wszystkie punk-
ty z węzła, na którym następuje podział do jego potomków. Po tej operacji
nowy punkt może zostać dodany do jednego z nowo powstałych węzłów.
Dzięki temu nasze drzewo przechowuje wartości tylko w swoich liściach.
44
48:9996879518
Uwaga 1: Punkt może zostać dodany do drzewa czwórkowego tylko wtedy,
gdy jego współrzędne zawierają się w całkowitej powierzchni drzewa, która
jest podana podczas inicjalizacji struktury. W przeciwnym wypadku taki
punkt zostaje odrzucony.
c l a s s QuadTree :
"""The c l a s s d e f i n i n g a q u a d t r e e . """
def __str__( s e l f ) :
return "QuadTree ( { } , { } ) " . format ( s e l f . r e c t , s e l f . c a p a c i t y )
def i s _ d i v i d e d ( s e l f ) :
""" Test i f t h e q u a d t r e e i s d i v i d e d . """
return s e l f . t o p _ l e f t i s not None
def h e i g h t ( s e l f ) :
""" Return t h e h e i g h t o f t h e q u a d t r e e . """
i f self . is_divided ( ) :
t l = self . top_left . height ()
t r = s e l f . top_right . height ( )
bl = s e l f . bottom_left . height ( )
br = s e l f . bottom_right . h e i g h t ( )
return 1 + max( t l , t r , bl , br )
else :
return 1
def i n s e r t ( s e l f , p o i n t ) :
""" I n s e r t a p o i n t i n t o t h e q u a d t r e e . """
i f p o i n t not in s e l f . r e c t :
return F a l s e
i f len ( s e l f . p o i n t _ l i s t ) < s e l f . c a p a c i t y and not s e l f . i s _ d i v i d e d ( ) :
s e l f . p o i n t _ l i s t . append ( p o i n t )
return True
i f not s e l f . i s _ d i v i d e d ( ) :
self . subdivide ()
45
49:2477290224
# move v a l u e s t o l e a f nodes
while s e l f . p o i n t _ l i s t :
c u r r e n t _ p o i n t = s e l f . p o i n t _ l i s t . pop ( )
s e l f . i n s e r t ( current_point )
if self . top_left . i n s e r t ( point ) :
return True
if s e l f . top_right . i n s e r t ( point ) :
return True
if s e l f . bottom_left . i n s e r t ( point ) :
return True
if s e l f . bottom_right . i n s e r t ( p o i n t ) :
return True
def s u b d i v i d e ( s e l f ) :
""" S u b d i v i d i n g t h e c u r r e c t r e c t . """
t l , t r , bl , br = s e l f . r e c t . make4 ( )
s e l f . t o p _ l e f t = QuadTree ( t l , s e l f . c a p a c i t y )
s e l f . t o p _ r i g h t = QuadTree ( t r , s e l f . c a p a c i t y )
s e l f . b o t t o m _ l e f t = QuadTree ( bl , s e l f . c a p a c i t y )
s e l f . bottom_right = QuadTree ( br , s e l f . c a p a c i t y )
def query ( s e l f , q u e r y _ r e c t ) :
""" Find a l l p o i n t s t h a t appear w i t h i n a range . """
points_in_rect = [ ]
try :
s e l f . r e c t . i n t e r s e c t i o n ( query_rect )
except V a l u e E r r o r :
return [ ]
f o r pt in s e l f . p o i n t _ l i s t :
i f pt in q u e r y _ r e c t :
p o i n t s _ i n _ r e c t . append ( pt )
i f not s e l f . i s _ d i v i d e d ( ) :
return p o i n t s _ i n _ r e c t
c h i l d r e n = ( s e l f . t o p _ l e f t , s e l f . top_right ,
s e l f . bottom_left , s e l f . bottom_right )
f o r c h i l d in c h i l d r e n :
p o i n t s _ i n _ r e c t . extend ( c h i l d . query ( q u e r y _ r e c t ) )
return p o i n t s _ i n _ r e c t
def n e a r e s t ( s e l f , p o i n t , b e s t=None ) :
""" Find a n e a r e s t p o i n t . """
i f b e s t i s None :
i f len ( s e l f . p o i n t _ l i s t ) > 0 :
best = self . point_list [ 0 ]
distance = ( point − best ) . length ()
else :
distance = float ( ’ Inf ’ )
else :
distance = ( point − best ) . length ()
f o r pt in s e l f . p o i n t _ l i s t :
46
50:5804740668
new_distance = ( p o i n t −pt ) . l e n g t h ( )
i f new_distance < d i s t a n c e :
b e s t = pt
d i s t a n c e = new_distance
i f not s e l f . i s _ d i v i d e d ( ) :
return b e s t
# find b e s t c h il d re n order
c = self . rect . center ()
i f point . x > c . x : # right , l e f t
i f point . y > c . y : # top , bottom
c h i l d r e n = ( s e l f . top_right , s e l f . t o p _ l e f t ,
s e l f . bottom_right , s e l f . b o t t o m _ l e f t )
else : # bottom , t o p
c h i l d r e n = ( s e l f . bottom_right , s e l f . bottom_left ,
s e l f . top_right , s e l f . t o p _ l e f t )
else : # left , right
i f point . y > c . y : # top , bottom
c h i l d r e n = ( s e l f . t o p _ l e f t , s e l f . top_right ,
s e l f . bottom_left , s e l f . bottom_right )
else : # bottom , t o p
c h i l d r e n = ( s e l f . bottom_left , s e l f . bottom_right ,
s e l f . top_left , s e l f . top_right )
f o r c h i l d in c h i l d r e n :
best = c h i l d . n e a r e s t ( point , best )
return b e s t
51:7524008036
5. Podsumowanie
Jednym z podstawowych zagadnień w geometrii obliczeniowej jest pro-
blem przecinania się odcinków z danego zbioru. Prosty algorytm siłowy dzia-
łający w czasie O(n2 ) był już prezentowany, dlatego rozważyliśmy zaawan-
sowane algorytmy wykorzystujące technikę zamiatania płaszczyzny.
Algorytm Bentleya-Ottmana w czasie O((n + k) log n) znajduje wszyst-
kie k punktów przecięcia w zbiorze n odcinków. Algorytm Shamosa-Hoeya
w czasie O(n log n) sprawdza, czy w zbiorze odcinków jakaś para odcinków
przecina się. Oba algorytmy korzystają ze struktury danych drzewa AVL
z kluczami dynamicznymi, która jest nietrywialnym rozszerzeniem zwykłego
drzewa AVL. W obu algorytmach przyjęto założenie, że odcinki są w pozycji
ogólnej, czyli końce odcinków i ich punkty przecięcia nie mają tych samych
współrzędnych. W związku z tym rozważyliśmy dodatkowy problem przeci-
nania się odcinków pionowych i poziomych. Zaimplementowane rozwiązanie
działa w czasie O((n + k) log n).
W ramach pracy rozważono problem znajdowania pary najbliższych punk-
tów. Przedstawiono kilka metod rozwiązywania tego problemu: metodę siło-
wą z czasem O(n2 ), algorytm z techniką zamiatania i czasem O(n log n), dwa
algorytmy typu dziel i zwyciężaj z czasami O(n(log n)2 ) i O(n log n).
Z problemem znajdowania pary najbliższych punktów wiąże się problem
znajdowania punktu najbliższego do punktu danego z zewnątrz. W rozwią-
zaniu tego drugiego problemu i wielu innych pomaga struktura danych o na-
zwie drzewo czwórkowe (quadtree). W pracy przedstawiono implementację i
sprawdzono wydajność tej struktury danych.
Każdy algorytm został sprawdzony pod względem poprawności (moduł
unittest ), a także wydajności obliczeniowej (moduł timeit ).
48
52:4369109026
A. Testy algorytmów
Dodatek ten zawiera testy wydajnościowe wszystkich algorytmów, aby
potwierdzić ich złożoność znaną z literatury. Wyniki działania niektórych
z nich przedstawiono również w formie graficznej.
49
53:5315231855
A.1.1. Algorytm Bentleya-Ottmanna
n = 16, k = 15
50
40
30
20
10
0
0 10 20 30 40 50
Bentley-Ottmann - ladder (k = n - 1)
1
log(t) = a log(N) + b
data
0.5
fit
-1
-1.5
-2
-2.5
1 1.5 2 2.5 3 3.5 4
log(N)
50
54:1136021625
n = 500, k = 34
50
40
30
20
10
0
0 10 20 30 40 50
-0.5
-1
-1.5
1 1.5 2 2.5 3 3.5 4
log(N)
51
55:7008856903
n = 500, k = 282
16
14
12
10
0
0 2 4 6 8 10 12 14 16
0.5
-0.5
-1
-1.5
1 1.5 2 2.5 3 3.5 4
log(N)
52
56:6990484760
A.1.2. Algorytm Shamosa-Hoeya
Shamos-Hoey - ladder (k = n - 1)
-0.5
log(t) = a log(N) + b
data
-1
fit
-2.5
-3
-3.5
-4
1 1.5 2 2.5 3 3.5 4
log(N)
-1.5
-2
-2.5
1 1.5 2 2.5 3 3.5 4
log(N)
53
57:6467578222
Shamos-Hoey - box of matches (k = n)
0.5
log(t) = a log(N) + b
data
0 fit
-1.5
-2
-2.5
1 1.5 2 2.5 3 3.5 4
log(N)
54
58:1078047163
A.1.3. Algorytm dla odcinków pionowych i poziomych
n = 500, k = 16
50
40
30
20
10
0
0 10 20 30 40 50
-1
-1.5
-2
-2.5
-3
1 1.5 2 2.5 3 3.5 4
log(N)
55
59:5659617952
n = 500, k = 205
16
14
12
10
0
0 2 4 6 8 10 12 14 16
-0.5
-1
-1.5
-2
-2.5
1 1.5 2 2.5 3 3.5 4
log(N)
56
60:8522980564
A.2.1. Para najbliższych punktów - technika zamiatania
-1
-1.5
-2
-2.5
-3
1 1.5 2 2.5 3 3.5 4
log(N)
-1
-1.5
-2
-2.5
-3
1 1.5 2 2.5 3 3.5 4
log(N)
57
61:1066922625
Closest pair (divide and conquer v2)
0
log(t) = a log(N) + b
data
-0.5
fit
-2
-2.5
-3
-3.5
1 1.5 2 2.5 3 3.5 4
log(N)
QuadTree.insert()
2.5
t = a log(N) + b
data
2 fit
0.5
-0.5
1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6
log(N)
58
62:8952275371
A.3.2. Algorytm wyszukiwania punktów należących do pewnej
płaszczyzny
1000
800
600
400
200
0
0 200 400 600 800 1000
Points in range
0
naive
-0.5 QuadTree
-1
-1.5
log(time [s])
-2
-2.5
-3
-3.5
-4
-4.5
-5
1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6
log(N)
59
63:8749850442
A.3.3. Algorytm wyszukiwania najbliższego sąsiada pewnego
punktu
1000
800
600
400
200
0
0 200 400 600 800 1000
Nearest neighbor
0.5
naive
0 QuadTree
-0.5
-1
log(time [s])
-1.5
-2
-2.5
-3
-3.5
-4
-4.5
1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6
log(N)
64:2312692341
Bibliografia
[1] Wikipedia, Sweep line algorithm, 2019,
https://en.wikipedia.org/wiki/Sweep_line_algorithm.
[2] Wikipedia, Computational geometry, 2019,
https://en.wikipedia.org/wiki/Computational_geometry.
[3] Wikipedia, Bentley-Ottmann algorithm, 2019,
https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm.
[4] M. I. Shamos, D. Hoey, Geometric intersection problems, 17th Annual Sym-
posium on Foundations of Computer Science, pp. 208-215 (1976).
[5] Wikipedia, Fortune’s algorithm, 2019,
https://en.wikipedia.org/wiki/Fortune’s_algorithm.
[6] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein,
Wprowadzenie do algorytmów, Wydawnictwo Naukowe PWN, Warszawa 2012.
[7] Lech Banachowski, Krzysztof Diks, Wojciech Rytter, Algorytmy i struktury
danych, Wydawnictwo naukowe PWN, Warszawa 2018.
[8] M. Berg, M. Kreveld, M. Overmars, O. Schwarzkopf, Geometria obliczeniowa.
Algorytmy i zastosowania, WNT, Warszawa 2007.
[9] Franco P. Preparata, Michael Ian Shamos, Geometria obliczeniowa. Wprowa-
dzenie, Helion, Gliwice 2003.
[10] Python Programming Language - Official Website,
https://www.python.org/.
[11] Marcin Permus, Algorytmy geometryczne w języku Python, Praca licencjacka,
Uniwersytet Jagielloński, Kraków 2018.
[12] Wikibooks, Kolejka priorytetowa, 2019,
https://pl.wikibooks.org/wiki/Struktury_danych/Kolejki/Kolejka_
priorytetowa.
[13] Python Docs, Heap queue algorithm, 2019,
https://docs.python.org/2/library/heapq.html.
[14] Wikipedia, Drzewo AVL, 2019,
https://pl.wikipedia.org/wiki/Drzewo_AVL.
[15] Michiel Smid, The closest pair problem: A plane sweep algorithm, 2019,
https://people.scs.carleton.ca/~michiel/lecturenotes/ALGGEOM/
sweepclosestpair.pdf.
[16] Dan Sunday, Geometry Algorithms, 2019,
http://geomalgorithms.com/.
[17] Diane Souvaine, Line Segment Intersection Using a Sweep Line Algorithm,
Tufts University, 2005,
http://www.cs.tufts.edu/comp/163/notes05/seg_intersection_
handout.pdf.
[18] Lech Banachowski, Krzysztof Diks, Wojciech Rytter, Algorytmy i struktury
danych, Wydawnictwo Naukowe PWN, Warszawa 2018.
[19] GeeksforGeeks, Closest Pair of Points using Divide and Conquer algorithm,
2019,
https://www.geeksforgeeks.org/.
61
65:3582995133
[20] Wikipedia, Quadtree, 2019,
https://en.wikipedia.org/wiki/Quadtree.
[21] Patrick Surry, D3JS quadtree nearest neighbor algorithm, 2018,
http://bl.ocks.org/patricksurry/6478178.
[22] Michiel Smid, Computing intersections in a set of horizontal and vertical line
segments, 2019,
https://people.scs.carleton.ca/~michiel/lecturenotes/ALGGEOM/
horverintersect.pdf.
[23] Tyler Sandman, PyBST, GitHub repository, 2019,
https://github.com/TylerSandman/py-bst.
[24] Rosetta Code, AVL Tree, 2019,
https://rosettacode.org/wiki/AVL_tree#Python.
62
66:3776556081