AAL - Ubrania

Transkrypt

AAL - Ubrania
Analiza Algorytmów
AAL-3-LS ubrania
Paweł Kubik
Treść
Posiadasz kolekcje n ubrań. O każdej parze dwóch ubrań potrafisz powiedzieć, czy pasują
do siebie. Należy rozmieścić ubrania w jak największej liczbie szaf w taki sposób, aby wybierając po jednej dowolnej rzeczy z każdej szafy, mieć gwarancję, że pasują do siebie.
Interpretacja
Problem można sprowadzić do poziomu teorii grafów. Przyjmijmy, że ubrania to wierzchołki grafu, a relacja pasowania do siebie to krawędź pomiędzy wierzchołkami. Problem sprowadza
się wówczas do znalezienia największej liczby takich rozłącznych zbiorów wierzchołków (dalej
klastrów), że dowolni reprezentanci tych klastrów będą razem tworzyć graf pełny. Klastry te
są odpowiednikami szaf.
Alternatywnie można przyjąć, że krawędź między dwoma wierzchołkami istnieje wtedy,
kiedy ubrania są w relacji niepasowania. Poprzednią interpretację można sprowadzić do tej
postaci zastępując graf jego dopełnieniem. Problem sprowadza się w takim przypadku do znalezienia takich klastrow, aby ich dowolni reprezentanci nie mieli żadnej wspólnej krawędzi.
Wynika to z analogii do poprzedniej postaci problemu, oraz bezpośrednio z treści zadania.
Istnieją dwa rodzaje przypadków brzegowych (opisane dla I-szego podejścia):
1. graf pełny, gdzie każdy wierzchołek powinien być przypisany do oddzielnego klastra
2. graf o pustym zbiorze krawędzi, gdzie wszystkie wierzchołki muszą się znaleźć w jednym
klastrze
Wszystkie pozostałe przypadki znajdują się pomiędzy powyższymi z czego wynika, że zawsze
istnieje rozwiązanie problemu.
Rozwiązanie
Dla ułatwienia przyjmijmy postać problemu w której krawędzie odpowiadają niepasowaniu pary ubrań. Jeśli para wierzchołków jest połączona krawędzią to koniecznie muszą się one
znaleźć w tym samym klastrze. W przeciwnym wypadku należeliby do zbioru reprezentantów
niespełniających kryteriów. Relacja przynależności do tego samego klastra jest przechodnia,
co oznacza, że każda spójna składowa grafu musi zawierać się w jednym klastrze.
Warunkiem wystarczającym do maksymalizacji liczby klastrów jest umieszczenie każdej
spójnej składowej w oddzielnnym klastrze.
Podsumowując problem można sprowadzić do znalezienia spójnych składowych grafu. Jeśli
jednak założyć, że na wejściu podany jest graf, w którym krawędzie oznaczają pasowanie ubrań,
to należy najpierw znaleźć jego dopełnienie.
Implementacja
Implementacja rozwiązuje wariant problemu w którym krawędzie oznaczają pasowanie do
siebie ubrań. Graf jest reprezentowany jako listy sąsiedztwa. Dla uproszczenia każdemu wierzchołkowi przyporządkowywana jest unikalna liczba ze zbioru N ∪ {0}. Rozwiązanie składa się
z dwóch kroków - szukania dopełnienia grafu, oraz szukania spójnych składowych dopełnienia.
W przypadku par uporządkowanych monotonicznie kolekcji można uzyskać ich sumę, różnicę oraz przecięcie w czasie liniowym. Jeśli zatem listy sąsiedztwa są uporządkowane względem
numerów wierzchołków, to istnieje możliwość znalezienia dopełniania grafu w czasie O(n2 ),
poprzez działania różnicy na listach sąsiedztwa i uporzątkowanej kolekcji wszystkich wierzchołków. Faktyczny koszt tej operacji jest niezależny od gęstości grafu.
Operacja może zostać wykonana in-place, a zatem ma stały koszt pamięciowy, w praktyce
jednak funkcja z biblioteki <algorithm> wymaga dodatkowego bufora, co daje koszt liniowy.
W obu przypadkach jest on zaniedbywalny z perspektywy całego problemu.
Do odnalezienia spójnych składowych grafu użyto algorytmu przeszukiwania wszerz (BFS)
o złożoności obliczeniowej i pamięciowej O(|V | + |E|). Złożoność tego kroku i tak jest ograniczona przez rozmiar danych wejściowych, więc nie ma sensu szukać lepszego rozwiązania.
Koszt odnajdywania dopełnienia grafu jest dominujący, zatem całkowita asymptotyczna
złożoność rozwiązania to O(n2 ). W pesymistycznym przypadku jest to również złożoność obsługi danych wejściowych, zatem można ją uznać za satysfakcjonującą.
Kod
Cały kod projektu znajduje się w podkatalogu src. Podkatalog src/test zawiera ręczne
testy jednostkowe, które nie są linkowane z głównym plikiem wykonywalnym. W podkatalogu
res znajdują się przykładowe pliki wejściowe *.in, oraz skrypty pomocne przy analizowaniu
wyników. Pliki wykonywalne i pośrednie znajduja się w katalogu build.
Projekt korzysta z systemu budowania premake4, który pozwala wygenerować pliki konfiguracyjne komplilacji dla różnych środowisk - m. in. GNU make. Aby zbudować projekt
wygenerowany dla tego narzędzia należy wywołać polecenie make w katalogu build. Plik wykonywalny nazywa się ubrania.
Klasy:
* Graph - zawiera implementację grafu przy użyciu list sąsiedztwa. Przypisuje każdemu
wierzchołkowi wartość liczbową i przechowuje mapowanie nazwa ↔ liczba. Zawiera metodę invert, która przekształca dany graf w jego dopełnienie.
* Problem - odpowiada za reprezentację problemów i ich rozwiązań. Jeden z konstruktorów
pozwala na wygenerowanie losowego problemu dla zadanej liczby wierzchołków i gęstości
grafu. Drugi z konstruktorów pozwala na użycie parsera w celu odczytu problemu z pliku.
Zawiera metodę solve, która generuje rozwiązanie wraz z czasami wykonania poprzez
wywołanie Graph::invert oraz Problem:: clusterize . Wyniki i pomiary czasu mogą być
potem odczytane, albo zapisane do pliku poprzez parser. Metoda clusterize odpowiada
za odnalezienie spójnych składowych.
* DotParser - pozawala na prosty odczyt i zapis grafu w formacie dot. Obsługiwana jest
tylko część funkcjonalności tego języka zademonstrowana w plikach przykładowych.
* Profiler - klasa pomocnicza pozwalająca na wykonywanie serii losowych testów.
Skrypty pomocnicze:
* drawall.sh - generuje plik .svg dla każdego pliku .out w aktualnym katalogu
* match.py (skrypt python 3) - dla zadanego pliku powstałego powstałego w wyniku polecenia ”ubrania time ... ”, generuje ciąg współczynników zgodności oceny teoretycznej
z pomiarem czasu i wypisuje go na standardowe wyjście.
* plot .py (skrypt python 3) - jak match.py, ale zamiast wypisywać wartości tworzy wykres, który zapisuje w formacie SVG
Pomiary czasu wykonania
W celu sprawdzenia oszacowanej złożoności wykonano serię testów dla różnych gęstości
grafów przy użyciu trybu wykonania z pomiarem czasu:
. / u b r a n i a time 10 5000 0 . 2 r e s / t i m e s 2 0 . c s v
. / u b r a n i a time 10 5000 0 . 5 r e s / t i m e s 5 0 . c s v
. / u b r a n i a time 10 5000 0 . 8 r e s / t i m e s 8 0 . c s v
Wykresy wygenerowane dla powyższych poleceń:
Rysunek 1: Dla grafu o gęstości 20%
Rysunek 2: Dla grafu o gęstości 50%
Rysunek 3: Dla grafu o gęstości 80%
Poza przewidywalnym błędem dla małych wartości liczby wierzchołków współczynnik zgodności jest zbliżony do 1.
Możliwości ulepszenia rozwiązania
Pesymistyczna złożoność rozwiązania jest ograniczona przez rozmiar danych, ale można
poprawić złożoność optymistyczną, która obecnie jest taka sama. Operowanie na dopełnieniu
grafu powoduje, że czas wykonania jest symetryczny ze względu na gęstość grafu - grafy gęste
są wymagające ze względu na obsługę wejścia, a grafy rzadkie mają gęste dopełnienia.
Jednym ze sposobów jest częściowe generowanie dopełnienia grafu w locie podczas szukania
spójnych składowych, zamiast robienia tego w dwóch krokach.
1. Stwórz zbiór wszystkich wierzchołków W
2. Dla każdego wierzchołka v
(a) Oblicz różnicę W oraz zbioru sąsiadów v
(b) (wykonaj kroki oryginalnego algorytmu wykorzystując wyżej obliczony zbiór jako
zbiór sąsiadów v)
(c) Odejmij od W odwiedzone wierzchołki
Metoda opiera się na spostrzeżeniu, że można zignorować krawędzie, które łączą wierzchołki przedzielone do tego samego klastra, ponieważ nie wnoszą one żadnej informacji. Dla
każdego wierzchołka należy obliczyć różnicę zbioru pozostałych wierzchołków i jego sąsiadów
o koszcie proporcjonalnym do licznośći większego z tych zbiorów, dlatego złożoność tej metody
to O(|V | + |E|).

Podobne dokumenty