Problem plecakowy
Transkrypt
Problem plecakowy
Dokumentacja 3 kwietnia 2009 Problem plecakowy Algorytm dokładny oparty na metodzie programowania dynamicznego Optymalizacja w systemach dyskretnych Prowadzący: dr inż. Jarosław Pempera 15 Termin: ŚR/P/9 Paweł Porombka (148955) 1. PROBLEM PLECKOWY – ROZWIĄZNIE PROBLEM PLECAKOWY 1.1 Problem Plecakowy - (ang. Knapsack problem), jest jednym z typowych problemów dyskretnych. Bardzo często przytaczany jako przykład w literaturze i bardzo często właśnie na tym problemie tłumaczone są techniki programowania dynamicznego. Formalnie dyskretny problem plecakowy możemy sformułować następująco: Mamy zbór przedmiotów A: gdzie każdy przedmiot posiada dwie właściwości w postaci liczb całkowitych (dla nas nieujemnych): Jeżeli zdefiniujemy pewien plecak (pojemnik), który posiada skończoną pojemność P, oraz zbiór zmiennych binarnych możemy zdefiniować następujący problem optymalizacyjny, który będzie polegał na maksymalizacji funkcji celu postaci: przy spełnionych ograniczeniach: W ten sposób sformułowaliśmy dyskretny problem plecakowy. Oczywiście możemy rozważać zmienne , wówczas mówilibyśmy o ciągłym problemie plecakowym, jednakże program rozważa dyskretną formę problemu. Otrzymaliśmy zatem konkretny problem, który polega na znalezieniu takiej grupy przedmiotów (zbioru), spośród określonych wcześniej, których wartość będzie największa – uzyskamy największy zysk, ale które to przedmioty zmieszczą się do plecaka, czyli ich sumaryczna waga nie przekroczy dopuszczalnej wagi plecaka. Potocznie zwany problemem złodzieja okradającego jubilera, problem plecakowy jest praktycznym problemem, z którym borykają się choćby firmy transportowe. Wówczas polega on na optymalnym załadowaniu nie tylko przedmiotów o określonej wadze do środka transportu, ale również na odpowiednim rozłożeniu tych przedmiotów, by zmieściło się ich jak najwięcej. Jest to jednak problem bardziej złożony. 2 PROGRAMOWANIE DYNAMICZNE 1.2 Jednymi z interesujących technik programistycznych, są techniki programowania dynamicznego. Strategia programowania dynamicznego została wymyślona przez Richarda Bellman’a. Jej założenia są dość proste rozłożyć problem na mniejsze podproblemy, które dość łatwo możemy rozwiązać, jeżeli tylko spełniają one własność optymalnej podstruktury 1. Takie rozwiązania cząstkowe pozwalają systematycznie zbliżać się do optimum problemu, przy czym a kolejnych krokach wykorzystujemy najlepsze poprzednie rozwiązania. Metoda ta pozwala wykorzystać rekurencyjną zależność między rozwiązaniami w kolejnych krokach algorytmu i tym samym obliczać każdy problem tylko raz. Parametry wykorzystywane w programowaniu dynamicznym (najczęściej w liczbie dwóch) ustalamy arbitralnie, gdyż to my budujemy algorytm, który ma rozwiązać nasz problem. Zatem bardzo ważnym elementem okazuje się być odpowiedni wybór parametrów i dostosowanie ich do postaci wykorzystywanej w technice programowania dynamicznego. Zdarza się oczywiście, że liczba parametrów wzrasta a ich dobór jest dość skomplikowany, jednakże należy pamiętać, że w programowaniu dynamicznym najczęściej wykorzystanie zasobów (pamięci) wzrasta proporcjonalnie do iloczynu największych wartości parametrów, zatem i czas wykonywania się wydłuża. Z tych powodów rzadko stosuje się więcej niż dwa parametry, gdyż przestaje to być efektywne. W jaki sposób definiować problem dla programowania dynamicznego? Na początek należy poznać podstawowe pojęcie programowania dynamicznego: etap (stadium) – fragment procesu szukania rozwiązania, jest charakteryzowany przez pewien stan rozpatrywanego procesu. Z etap możemy przyjąć pewien odcinek czasu, czy drogi. Stadium może stanowić również pewien etap w myśleniu o procesie. Najogólniej rzecz ujmując etap polega na wprowadzeniu nowego wariantu rozwiązania. Dokładnie ilustruje to Rys.1. Widzimy tutaj zależność wykorzystania poprzedniego wyniku by wykonać kolejny krok. Oczywiście nie zawsze musimy korzystać z poprzedniego wyniku, może to być wiele wyników wstecz. S0 x1 S1 x2 S2 Sn-1 xn Sn Rys.1. Schemat wieloetapowego procesu decyzyjnego, który doskonale pokazuje sposób realizacji programowania dynamicznego. Si to wyniki poprzednich problemów (w zerowym kroku najczęściej 0), xi to kolejne decyzje podejmowane w każdym etapie. 1 Dotyczy problemów rozwiązywalnych algorytmami. Własność optymalnej podstruktury spełniają rozwiązania optymalne, które są funkcją rozwiązań optymalnych podproblemów. 3 W ten sposób znając pewne własności możemy podsumować teorię programowania dynamicznego podstawowymi dwoma stwierdzeniami, które są filarami całej techniki: a) n – wymiarowa funkcja celu powinna mieć postać sumy n funkcji celu, o jednej zmiennej decyzyjnej dla różnych etapów procesu, b) Wartość uzyskana na pewnym etapie zależy od stanu w etapie poprzednim, oraz decyzji uzyskanej w rozważanym stadium. Taka wartość nie powinna jednak zależeć od tego w jaki sposób (jaką drogą) zostało uzyskane rozwiązanie – tzw. własność Markowa. Do procesu spełniającego własność Markowa stosuje się podstawową własność strategii optymalnej Bellman’a: Polityka optymalna ma tę własność, że niezależnie od początkowego stanu i początkowej decyzji pozostałe decyzje muszą stanowić politykę optymalną ze względu na stan wynikający z pierwszej decyzji. 4 PROGRAMOWANIE DYNAMICZNE – PROBLEM PLECAKOWY 1.3 Spróbujmy rozważyć problem plecakowy, którym mamy się zająć, przy użyciu techniki programowania dynamicznego. Na początek należy przyjąć, które ze zmiennych zdefiniowanych w problemie ( Rozdz. I ) będą wykorzystane do stworzenia struktury wieloetapowej algorytmu. Biorąc pod uwagę, że problem ma zostać podzielony na podproblemy, które stanowią częściowe rozwiązanie i powinny być elementarne, spróbujmy przeanalizować funkcję celu: przy ograniczeniach: Pierwszą ze zmiennych z pewnością będzie numer kolejno wybieranego elementu - i, gdyż będziemy w stanie określić wagę i wartość tego elementu. Kolejna zmienna decyzyjna powinna zawężać lub poszerzać problem, zatem będzie to rozmiar plecaka P. Im mniejszy rozmiar tym prostsze znalezienie optymalnego upakowania. W ten sposób możemy już stworzyć odpowiednią tablicę (Rys.2.), która pozwoli realizować algorytm. 0 1 2 3 0 0 0 0 0 P 0 1 0 2 0 3 0 n 0 ← i-ty element OPT ← optimum ↑ Pojemność plecaka Rys.2. Przyjęta tablica rozwiązań cząstkowych dla programowania dynamicznego Umieszczone zera w pierwszym wierszu i kolumnie zostaną wyjaśnione później. Zatem poszukujemy takiego upakowania plecaka, by otrzymać jak największą wartość i nie przekroczyć ograniczeń. Z tego powodu w tabeli będziemy umieszczać kolejne optymalne wartości plecaka przy konkretnej pojemności plecaka oraz konkretny przedmiot , brany pod uwagę. Stąd definiujemy tabelę Qi,j która zawiera wartość plecaka (jego stan) w danym etapie procesu. Ściślej jest to funkcja celu, o nieco zmienionych ograniczeniach: 5 Należy już tylko ustalić w jaki sposób będziemy obliczali stany poszczególnych etapów (wartości komórek tabeli) procesu rozwiązywania problemu. Otóż zaproponujmy następujący algorytm rozwiązania stosujący wprowadzone oznaczenia, który ilustruje pseudokod na Rys.3. FOR BEGIN TO FOR BEGIN DO TO IF DO THEN := ELSE //(1) //(2) END END //rozwiązanie optymalne Rys.3. Algorytm rozwiązujący problem plecakowy, przy użyciu technik dynamicznych Analizując algorytm łatwo zauważyć skąd zera w pierwszej kolumnie i pierwszym wierszu. Otóż algorytm działa w ten sposób, że wykorzystuje znalezione wcześniej optymalne rozwiązanie dla danych etapów. Rozwiązanie składa się z dwóch kroków: 1. Jeżeli aktualny przedmiot nie zmieści się w bieżącym plecaku pi, to zwyczajnie przepisz poprzednie optymalne upakowanie z etapu wcześniejszego i-1 (1) 2. W przeciwnym wypadku sprawdź, które upakowanie będzie lepsze (większa wartość), to z poprzedniego etapu, czy to, które będzie zawierać aktualny element (2). W tej sytuacji dodajemy wartość aktualnego elementu, do stanu plecaka, który był optymalny w chwili, gdy pojemność plecaka była mniejsza o wagę bieżącego elementu (j – wi). W ten sposób odwołujemy się do innego najlepszego upakowania plecaka, które zostało już policzone. Zauważmy jak prosty jest algorytm a jego złożoność jest równa . Widzimy, zatem, że dla małych wielkości pojemności plecaka, oraz dla małej puli przedmiotów algorytm jest wielomianowy, jednakże w miarę wzrostu obu wartości staje się bardzo złożony, stąd nazwa algorytm pseudowielomianowy. Algorytmy te są o tyle praktyczne, że pozwalają na zalezienie rozwiązania danego problemu w czasie pseudowielomianowym. Sens tych technik kryje się właśnie w tej własności. Problemy, które dla innych algorytmów rozwiązań są NP-trudne, mają wykładniczą złożoność, dla pewnej grupy instancji problemów dają się rozwiązać w czasie wielomianowym. Jak się okazuje wiele problemów trudnych można rozwiązać dla praktycznych danych w czasie wielomianowym. 6 Analizując pseudo kod przedstawiony powyżej, możemy dojść do wniosku, że jest możliwe zredukowanie zużycia pamięci operacyjnej poprzez ograniczenie tablicy do jednego wektora głównego V1j i jednego pomocniczego V0j, gdzie oba odpowiadają kolumnom tabeli Qi,j. Wówczas otrzymamy następujący pseudokod: FOR BEGIN TO DO CHANGE //(1) FOR BEGIN TO IF DO THEN := ELSE //(2) //(3) END END //rozwiązanie optymalne Rys.4. Zoptymalizowany algorytm rozwiązujący problem plecakowy, przy użyciu technik dynamicznych Widzimy, że jedyną zmianą w naszym algorytmie jest dodanie nowego kroku (1), który zamienia nam miejscami dwa wektory. Możemy to oczywiście wykonać w innej formie, bez zamiany wektorów, a jedynie biorąc pod uwagę na przemian wektor V1 jako V0 i odwrotnie. W implementacji zastosowano język C#, który operując na referencjach nie wymaga tutaj żadnego nakładu obliczeniowego, stąd zamiana jest natychmiastowa. 7 2. ROZWIĄZANIE IMPELEMENTACYJNE W celu rozwiązania DPP (dyskretnego problemu plecakowego), czyli znalezienia rozwiązania optymalnego, w aplikacji wykorzystano programowanie dynamiczne. W klasie rozwiązującej ten problem zaimplementowano 2 formy funkcji rozwiązującej. Było to spowodowane ewolucją rozwiązań. W rezultacie udało się osiągnąć pojedynczy wektor o długości P, na którym pracuje algorytm zamiast tablicy 2D (n x P). Złożoność tak , jednakże po optymalizacji zajętość pamięci modyfikujemy z otrzymanego algorytm wynosi cały czas n x P ilości komórek tablicy do zaledwie P. CELE PROJEKTU 2.1 1. Rozwiązanie problemu plecakowego przy użyciu technik programowania dynamicznego. 2. Możliwość przetwarzania dużej liczby przedmiotów. 3. Określenie granicznego rozmiaru plecaka, z powodów wydajności. 4. Możliwość przetwarzania wsadowego plików z danymi (parametry linii komend). 5. Możliwość zapisu rozwiązania optymalnego do pliku. 6. Realizacja projektu w języku C# 3.0 (Windows Presentation Foundation) na platformie .NET. 7. Wizualizacja obiektów z możliwością edycji ich parametrów. 8. Realizacja okienka Splash-Screen 2. 2 Ten element traktujemy jako dodatkowy, nie musi zostać zrealizowany. 8 SPECYFIKACJA WYMAGAŃ 2.2 1. ROZWIĄZANIE - PROGRAMOWANIE DYNAMICZNE A. Rozwiązanie problemu plecakowego, zrealizowane przy użyciu technik programowania dynamicznego. B. Optymalizacja algorytmu w taki sposób, by nie było potrzeby tworzenia nierzadko ogromnej tablicy danych na potrzeby programowania dynamicznego. C. Wynikiem powinna być pojedyncza liczba określająca wartość (sumę wartości wszystkich przedmiotów), przy optymalnie upakowanym plecaku. 2. WYDAJNOŚĆ A. Ograniczenie liczby przedmiotów do 100 000 obiektów. B. Ograniczenie rozmiaru plecaka do 500. C. Ograniczenie grafiki rastrowej w interfejsie programu, na korzyść grafiki wektorowej ze względu na charakter WPF. D. Pewna ograniczona (najlepiej stała 10..20) liczba przedmiotów, wykorzystywana do wizualizacji (patrz pkt. 4.A.). 3. PRZETWARZANIE WSADOWE A. Możliwość przetwarzania wsadowego plików z gotowymi danymi opisującymi przedmioty oraz plecak. B. Dane w plikach wejściowych są w określonym formacie, opisanym poniżej a) Plik jest w formacie tekstowym UTF-8/ASCII b) Plik zbudowany jest z dwóch kolumn c) Nagłówek pliku: - Pierwsza kolumna N = ilość wszystkich przedmiotów Druga kolumna W = pojemność plecaka (maksymalna waga) d) Kolejne N wierszy (i = 1…N): C. Pierwsza kolumna waga i-tego przedmiotu Druga kolumna wartość i-tego przedmiotu Pliki wyjściowe to pliki w formacie UTF-8, zawierające jedną liczbę – optymalne rozwiązanie problemu. D. Podczas przetwarzania wsadowego nie powinno pojawiać się okienko aplikacji. E. Po skończeniu przetwarzania pliku wejściowego, zapisywany jest wynik w pliku wyjściowym, a program zostaje zamknięty. 9 4. JĘZYK PROGRAMOWANIA C# 3.0/XAML (WPF), PLATFORMA .NET A. Wykonanie ciekawego interfejsu graficznego z prezentacją wizualną pewnej puli przedmiotów, których parametry (waga i wartość) mogą być modyfikowane w prosty sposób. Pula tych przedmiotów nie musi być wczytywana z pliku, może być zdefiniowana w programie. B. Musi być możliwość modyfikowania zdefiniowanych wartości w.w. przedmiotów oraz pojemności plecaka, w celu arbitralnego parametryzowania problemu. C. Program powinien wykorzystywać MS Framework 3.0/3.5. 5. REALIZACJA OKIENKA POWITALNEGO SPLASH-SCREEN 3 A. Okienko pojawiające się przed załadowaniem właściwego kodu programu oraz głownego okna aplikacji. B. 3 Zabrania się umieszczania animacji i dużej ilości grafiki na tym okienku, ze względu na jego przeznaczenie. Musi się ono błyskawicznie załadować do pamięci. Ten punkt specyfikacji wymagań jest opcjonalny 10 SPECYFIKACJA PROJEKTU 2.3 1. ROZWIĄZANIE PRZY UŻYCIU TECHNIK PROGRAMOWANIA DYNAMICZNEGO 4 A. Optymalizacja algorytmu do postaci wykorzystującej tylko jeden wektor danych w pamięci, zamiast dwuwymiarowej tablicy rozwiązań cząstkowych, co powoduje znaczne zmniejszenie zajętości pamięci B. Za wynik przyjmujemy liczbę znajdującą się w przetwarzanym wektorze w ostatniej jego komórce. W przypadku analizowania tablicy 2D byłby to element znajdujący się w prawym dolnym rogu tabeli. 2. UWZGLĘDNIENIE OGRANICZEŃ WYDAJNOŚCIOWYCH A. Ograniczenie liczby przedmiotów do 100 000 obiektów oraz pojemności plecaka do 500, realizuje konkretna funkcja TryAddParameters(…). Polega to wyłącznie na weryfikacji wejściowych parametrów pod względem zgodności z założeniami. 3. PRZETWARZANIE WSADOWE PLIKÓW Z DANYMI A. Przetwarzanie wsadowe zostaje włączone tylko i wyłącznie, gdy liczba parametrów linii komend pliku aplikacji jest równa 3. Pierwszy parametr to typ operacji jaki chcemy wykonać, następne dwa to kolejno plik wejściowy i wyjściowy. Argumenty w linii poleceń wyglądają następująco: program.exe -wsadowo plik_wejściowy.txt wynik.txt W tej sytuacji zostanie przetworzony plik plik_wejściowy.txt, a wynik zapisany w pliku wynik.txt. B. Podczas przetwarzania wsadowego nie pojawia się okienko aplikacji (nie jest w ogóle ładowane), natomiast pod koniec przetwarzania otrzymujemy komunikat w postaci okna dialogowego (podsumowanie i wynik). Po kliknięciu przycisku OK., program zostaje zamknięty. 4. REALIZACJA PROJEKTU W JĘZYKU C# 3.0/XAML (TECHNOLOGIA WPF), PLATFORMA .NET A. Program został wykonany w oparciu o MS Framework 3.5. B. Interfejs graficzny został zrealizowany w postaci pojedynczego okna głównego, w którego obszarze roboczym pracujemy. Kolejne „okienka” stanowią zwyczajnie kolejną warstwę (siatkę), która pojawia się (animacja) po konkretnym zdarzeniu. W ten sposób uzyskano dość efektowną aplikację. C. Wizualizacja obiektów polega na przedstawieniu zbioru ikonek (piłka, lornetka, itd.), które w rzeczywistości są komponentami stworzonymi na potrzeby aplikacji. Każdy komponent zawiera dodatkowe właściwości: wagę, wartość oraz nazwę danego przedmiotu. W ten sposób są one identyfikowane (patrz. Dokumentacja Interfejsu Graficznego). 4 Nie do końca jeden, gdyż zawsze musi być dodatkowy wektor tymczasowy. Oba wektory są przetwarzane na zmianę. Co zapobiega przepisywaniu wartości z jednego do drugiego. 11 D. Modyfikacja obiektów odbywa się w prosty sposób, klikając dwukrotnie na daną ikonkę. Wówczas mamy możliwość zmodyfikowania również pojemności plecaka. Więcej na ten temat w części Dokumentacja Interfejsu Graficznego. 5. REALIZACJA OKIENKA POWITALNEGO SPLASH-SCREEN A. Udało się zrealizować okienko pojawiające się podczas ładowania głównej części aplikacji. B. Okienko Splash-Screen, musi wywoływać ładowanie pozostałej części aplikacji w osobnym wątku STA (interfejsu graficznego). Wynika to ze struktury realizacji interfejsu w WPF. C. Po utworzeniu obiektu aplikacji głównej oraz załadowaniu bibliotek pojawia się nam okienko główne aplikacji, natomiast Splash-Screen znika. D. Zgodnie z przyjętymi ograniczeniami, okno powitalne nie realizuje żadnych skomplikowanych animacji, a jedynie jest wariacją dwóch obrazków plecaka z przedmiotami (jeden to odblask na podłodze) oraz tekst LOADING… . 12 MODEL ROZWIĄZANIA 2.4 Do modelowania problemu oraz aplikacji wykorzystano narzędzia do projektowania w języku UML: a) Poseidon for UML 6.0, b) Visual Paradigm for UML 6.4. Modelowanie podzielono na kilka etapów, których realizacją były diagramy poszczególnych problemów. 2.4.1. OGÓLNY MODEL SYSTEMU Na rys.5. przedstawiono prosty diagram obrazujący ogólną strukturę całego systemu. Rys.5. Diagram zaimplementowanego systemu 13 2.4.2. DIAGRAM DZIAŁANIA ALGORYTMU Na rys.6. przedstawiono diagram algorytmu rozwiązującego problem plecakowy. Na diagramie uwzględniono oznaczenia oryginalne z implementacji, opisane w części 2.5. Rys.6. Diagram działania algorytmu 14 2.4.3. DIAGRAM DZIAŁANIA INTERFEJSU Rys.7. Diagram działania interfejsu użytkownika na zasadzie diagramu akcji UML 15 2.4.4. DIAGRAM PODSTAWOWYCH KLAS APLIKACJI Rys.8. Diagram klas utworzonych na potrzeby systemu 16 DOKUMENTACJA KODU 2.5 class KnapsackSolver Klasa zawierająca zaimplementowany algorytm rozwiązania problemu plecakowego, przy użyciu technik programowania dynamicznego. Klasa jest nieskomplikowana, a jej obsługa wyjątkowo prosta. Na potrzeby projektu została ona zamknięta w osobnym pliku, przy czym postanowiono, że nie zostanie utworzona w postaci biblioteki DLL. const int BagLimit = 500; Pole prywatne (stała typu liczba całkowita), przechowuje maksymalną dopuszczalną wartość plecaka, która została poddana weryfikacji: TryAddParameters(…). const int ObjectsLimit = 100000; Pole prywatne (stała typu liczba całkowita), przechowuje maksymalną dopuszczalną liczbę przedmiotów w puli, która została poddana weryfikacji: TryAddParameters(…). private int[] _Weights Pole prywatne (tablica liczb całkowitych), określające wagi wszystkich przedmiotów wczytane do instancji klasy. Indeksacja od 0.._ItemsCount. To pole zostaje przypisane w chwili dodania poprawnych danych metodą TryAddParameters(…), bądź wymuszenia dodania danych przy użyciu ForceAddParameters(…). 17 private int[] _Values Pole prywatne (tablica liczb całkowitych), określające wszystkie wartości (ceny) dodanych przedmiotów. Indeksacja od 0.._ItemsCount. To pole zostaje przypisane w chwili dodania poprawnych danych metodą TryAddParameters(…), bądź wymuszenia dodania danych przy użyciu ForceAddParameters(…). private int _TotalWeight Pole prywatne (liczba całkowita), określające maksymalną dopuszczalną wagę plecaka. To pole zostaje przypisane w chwili dodania poprawnych danych metodą TryAddParameters(…), bądź wymuszenia dodania danych przy użyciu ForceAddParameters(…). private int _ItemsCount Pole prywatne (liczba całkowita), określające ilość przedmiotów, które zostały dodane do puli w instancji klasy. To pole zostaje przypisane w chwili dodania poprawnych danych metodą TryAddParameters(…), bądź wymuszenia dodania danych przy użyciu ForceAddParameters(…). Z zewnątrz klasy pole jest dostępne jako właściwość tylko do odczytu: ItemsCount. private int[,] _Results Pole prywatne (dwuwymiarowa tablica liczb całkowitych), zawierające wyniki w postaci tablicy kroków algorytmu programowania dynamicznego. Pole jest inicjalizowane tylko po wywołaniu metody pierwszej wersji rozwiązywania algorytmu: SolveProblem1(). Z zewnątrz klasy pole jest dostępne jako właściwość tylko do odczytu: Results. Rozwiązanie jest przechowywane naturalnie w prawym dolnym rogu tablicy (jest to rozwiązanie optymalne). 18 public int[,] Results Właściwość publiczna (dwuwymiarowa tablica liczb całkowitych), pozwalająca na dostęp do tablicy rozwiązań cząstkowych algorytmu dynamicznego. Odpowiada prywatnemu polu: _Results. Właściwość tylko-do-odczytu. public int ItemsCount Właściwość publiczna (liczba całkowita), pozwalająca na dostęp do liczby określającej ilość przedmiotów zaakceptowanych w instancji klasy. Odpowiada prywatnemu polu: _ ItemsCount. Właściwość tylko-do-odczytu. public readonly int BagSizePercentage Właściwość publiczna (liczba całkowita), pozwalająca na dostęp do liczby określającej, jaka może być maksymalna suma wag wszystkich dodanych przedmiotów w stosunku do pojemności plecaka. Wartość ta jest określana z góry podczas tworzenia instancji obiektu klasy i wyrażona jest w procentach wielkości plecaka. Domyślnie ustawiana na jest na 70(%) i nie może zostać zmodyfikowana. Pozwala nałożyć pewne wymogi co do parametrów przedmiotów. Ta wartość m.in. jest wykorzystywana podczas weryfikacji danych metodą TryAddParameters(…). Właściwość tylko-do-odczytu. public KnapsackSolver(int BagSizePercent) Publiczny konstruktor parametryzujący. Tworzy instancję obiektu klasy. 19 Parametry: int BagSizePercent - określa procentowy udział wielkości przedmiotów, w wielkości plecaka (patrz BagSizePercentage) Konstruktor ustawia podaną wartość jednorazowo, później właściwość jest już niemodyfikowalna. public KnapsackSolver() Publiczny konstruktor domyślny. Tworzy instancję obiektu klasy, ustawiając od razu domyślna wartość parametru BagSizePercentage na 70(%). Konstruktor ustawia podaną wartość jednorazowo, później właściwość jest już niemodyfikowalna. public Boolean TryAddParameters( int[] ItemsWeights, int[] ItemsValues, int BagMaxWeight ) Publiczna metoda. Pozwala na weryfikację dodawanych danych, które będą przetwarzane przy użyciu metod rozwiązujących w dwóch wersjach: SolveProblem1() oraz SolveProblem2(). Parametry: int[] ItemsWeights - Wektor określający wagi wszystkich przedmiotów dodawanych do puli elementów w klasie (patrz _Weights). int[] ItemsValues - Wektor określający wartości wszystkich przedmiotów dodawanych do puli elementów w klasie (patrz _Values). int BagMaxWeight - Liczba całkowita określająca maksymalny rozmiar plecaka, 20 czyli jego pojemność (patrz _TotalWeight). Wynik: Boolean – zwracany wynik to true, gdy dodano poprawnie nowe parametry, lub false, gdy parametry są nie poprawne i nie zostały dodane. Dzięki tej metodzie jest możliwość weryfikacji, czy dany problem jest już rozwiązanym gdyż pula przedmiotów o sumarycznej wadze mniejszej, równej pojemności plecaka może w całości mieścić się w nim. Co za tym idzie nie ma tutaj sensu rozwiązywanie algorytmu. Kolejnym wariantem jest sprawdzenie ilości przedmiotów oraz wielkości plecaka, które są ograniczone zgodnie ze specyfikacją. public void ForceAddParameters( int[] ItemsWeights, int[] ItemsValues, int BagMaxWeight ) Publiczna metoda. Pozwala na dodanie nowych danych o przedmiotach do puli klasy, bez weryfikacji ich poprawności. Dalej dane te są przetwarzane przez jedną z metod rozwiązujących: SolveProblem1() oraz SolveProblem2(). Parametry: int[] ItemsWeights - Wektor określający wagi wszystkich przedmiotów dodawanych do puli elementów w klasie (patrz _Weights). int[] ItemsValues - Wektor określający wartości wszystkich przedmiotów dodawanych do puli elementów w klasie (patrz _Values). int BagMaxWeight - Liczba całkowita określająca maksymalny rozmiar plecaka, czyli jego pojemność (patrz _TotalWeight). 21 Metoda ta zwyczajnie wymusza wykonanie algorytmu dla wszystkich podanych danych. Jest wykorzystywana podczas przetwarzania wsadowego. public int[,] SolveProblem1() Publiczna metoda rozwiązująca. Metoda, która znajduje rozwiązanie optymalne metodą techniki programowania dynamicznego, z wykorzystaniem dwuwymiarowej tablicy cząstkowych rozwiązań. Ma to związek z charakterem programowania dynamicznego. Metoda opracowana na potrzeby prezentacji wyników w postaci tablicy, oraz dla potrzeb interfejsu (mniej wydajna niż SolveProblem2()). Wynik: int[,] – zwracany wynik to dwuwymiarowa tablica liczb całkowitych, która jest wspomnianą tablicą cząstkowych rozwiązań problemu. Prawy dolny róg tabeli (komórka) zawiera znalezione optymalne rozwiązanie. Ze względów wydajnościowych nie jest ona raczej stosowana, do przetwarzania wsadowego wykorzystuje się metodę w wersji 2. public int SolveProblem2() Publiczna metoda rozwiązująca. Metoda, która znajduje rozwiązanie optymalne metodą techniki programowania dynamicznego, z wykorzystaniem jednowymiarowej tablicy (wektora) cząstkowych rozwiązań. Ma to związek z charakterem programowania dynamicznego. Metoda stanowi ulepszenie metody w wersji 1 (SolveProblem1()) w sferze wydajnościowej, zajętości pamięci oraz szybkości. Tak naprawdę w metodzie jest wykorzystywany jeszcze jeden wektor pomocniczy, zatem operacje odbywają się na dwóch identycznych wektorach zamienianych miejscami. Wadą tej techniki jest niemożność odzyskania sekwencji dodanych do plecaka przedmiotów, przy sensownej wydajności. Wynik: 22 int – zwracany wynik to optymalne upakowanie plecaka, a dokładnie optymalne rozwiązanie, którym jest wartość plecaka tak upakowanego. class FileOperations Klasa pozwalająca na wczytywanie plików z danymi dotyczącymi problemu. Format danych opisano w specyfikacji programu. public static Boolean ReadDataFromFile( String FileName, out int[] w_i, out int[] c_i, out int W ) Publiczna metoda statyczna. Wczytuje parametry algorytmu z pliku tekstowego o określonym formacie (UTF-8), oraz zwraca te parametry w postaci argumentów oznaczonych operatorem out. Parametry: String FileName - Łańcuch znaków opisujący ścieżkę do pliku wraz z jego nazwą, int[] w_i - Wektor określający wagi poszczególnych przedmiotów. Operator out definiuje, że podany parametr jest zwracany w tym miejscu, zatem pod tą zmienną zostanie podstawiony wynik, int[] c_i - Wektor określający wartości poszczególnych przedmiotów, int[] W - Pojemność plecaka. poszczególnych przedmiotów. Operator out definiuje, że podany parametr jest zwracany w tym miejscu, zatem pod tą zmienną 23 zostanie podstawiony wynik. Wynik: Boolean – zwracany wynik to true, gdy wczytano poprawnie nowe parametry, lub false, gdy plik FileName nie istniał. Metoda ta jest wykorzystywana podczas przetwarzania wsadowego do wczytywania danych z pliku wejściowego. 24 INTERFEJS (UI) 2.6 Ponieważ interfejs został wykonany w jednej z nowszych technologii firmy Microsoft, postanowiono, że opis tegoż przedsięwzięcia powinien również stanowić część dokumentacji. Platforma .NET pozwala na niezwykle rozwijanie się w sferze nowych technologii. Tego typu technologia jest Windows Presentation Foundation (WPF), która stanowy śmiały krok na przód w dziedzinie projektowania interfejsów. Ten wyjątkowy pakiet dostępny w bibliotekach MS Framework od wersji 3.0, posiada niesamowite możliwości. Teraz okienko staje się przestrzenią dla animacji zarówno 2D jak i 3D, a efekty graficzne są tutaj na bardzo wysokim poziomie. Cała technologia jest oparta o MS DirectX od wersji 9.0c, co powoduje, że wspiera wszelką grafikę wektorową. 2.6.1. SPLASH SCREEN Ponieważ, jak wspomniano w specyfikacji projektu, WPF wymaga załadowania kilku bibliotek DirectX, oraz wygenerowania pewnej liczby elementów graficznych, pierwsze uruchomienie aplikacji zajmuje trochę czasu. Z tego powodu w projekcie po raz pierwszy uwzględniono okno powitalne, które aktualnie stosuje praktycznie w każdej aplikacji. Okienko pojawia się tuż po uruchomieniu aplikacji, i znika w chwili załadowania się okienka głównego z wygenerowaną i przygotowaną grafiką. Wówczas pojawia się główne okienko programu. Jest praktycznie jedyna rola tego elementu graficznego (Rys.9.) aplikacji. Rys.9. Splash Screen aplikacji KnapsackProblem 25 2.6.2. OKNO GŁÓWNE APLIKACJI Interfejs programu stanowi główne okno aplikacji przedstawione na Rys.10. Rys.10. Główne okienko aplikacji Po lewej widzimy zbiór przykładowych przedmiotów w postaci ikon. Każdy przedmiot stanowi osobny komponent UI, który posiada trzy właściwości: Nazwę (String), Wagę (int), Wartość (int). Dzięki nim klikając na konkretny element dwukrotnie jesteśmy w stanie w prosty sposób modyfikować te właściwości, wraz ze zmianą pojemności plecaka (Rys.11.) 26 Rys.11. Modyfikacja parametrów dostępnych przedmiotów Oznaczenia na rysunku dotyczą w tym przypadku obiektu „Lornetka”. Oczywiście nazwa przedmiotu może również zostać zmodyfikowana w tym okienku. Klikając OK zatwierdzamy wprowadzone zmiany. 2.6.3. ROZWIAZANIE PROBLEMU Gdy skonfigurowaliśmy przedmioty w wymagany sposób możemy przystąpić do obejrzenia wyników. Ponieważ obiektów jest dość mało, obliczenia trwają ułamek sekundy. W tym celu wystarczy kliknąć na plecak (Rys.12.), co powoduje uruchomienia algorytmu. Rys.12. Przycisk rozwiązujący problem 27 Wynik otrzymujemy w postaci wartości plecaka, który został optymalnie upakowany (Rys. 13.). Rys.13. Wynik obliczeń W przypadku domyślnych ustawień przedmiotów wartość ta wynosi 38. Dodatkowo, ponieważ algorytm wykorzystuje drugą wersję metody rozwiązującej, mamy możliwość obejrzenia tablicy cząstkowych wyników, które zostały wykorzystane podczas rozwiązywania. W tym celu klikamy fioletową tabelę w prawym górnym rogu otwartego okienka (Rys. 14.). Rys.14. Przycisk pokazujący tabelę rozwiązań cząstkowych 28 Pojawi się wówczas okienko z tabelą, której kolejno numerowane wiersze odpowiadają kolejnym rozmiarom plecaka, natomiast pierwszy wiersz to indeksy kolumn – numery przedmiotów (pula). Rozwiązanie optymalne znajduje się w prawym dolnym rogu tabeli (Rys. 15.). Rys.15. Wynik obliczeń w postaci tabeli rozwiązań cząstkowych Poszczególne okienka zamyka się z reguły klikając na nie, jednakże w przypadku tabeli korzystamy z przycisku w kształcie litery „X”. 29 3. WYDAJNOŚĆ I WYMAGANIA WYMAGANIA APLIKACJI 3.1 Program został napisany z uwzględnieniem następujących wymogów 1. 2. 3. 4. 5. 6. System Microsoft Windows XP SP3 lub nowszy Zainstalowany MS Framework 3.5 SP1 Komputer z procesorem co najmniej 800MHz Z minimalną ilością pamięci operacyjnej 256MB Miejscem na dysku twardym ok. 2MB Dodatkowo system koniecznie musi posiadać Microsoft DirectX 9.0c lub nowszy (Win XP posiada już zainstalowany ten zestaw modułów) WYDAJNOŚĆ 3.2 Aplikacja została przetestowana pod względem realizacji wielokrotnego rozwiązywania problemu dostępnymi dwoma sposobami, w celu wykazania różnicy. Testy wykonano na dość szybkim komputerze Athlon Core 2 Duo 4.2GHz, pamięci operacyjnej 2GB/800MHz, dysku twardym ATA, oraz systemie operacyjnym MS Windows XP spełniającym stawiane wcześniej wymagania. Zależność czasu wykonywania algorytmów od ilości dostępnych przedmiotów Pojemnoość plecaka P = 150 14 12 Algorytm wykorzystujący tablicę 2D danych cząstkowych 10 8 6 4 Algorytm zoptymalizowany, wykorzystuje jeden wektor danych 2 2000 1900 1800 1700 1600 1500 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 0 100 Czas wykonywania algorytmów [ms] 16 Ilość dostępnych przedmiotów n 30 Zależność czasu wykonywania algorytmów od rozmiaru plecaka Ilość przedmiotów n = 100 2,5 Algorytm zoptymalizowany, wykorzystuje jeden wektor danych 2 1,5 1 0,5 620 590 560 530 500 470 440 350 320 290 260 230 200 170 140 110 80 50 0 410 Algorytm wykorzystujący tablicę 2D danych cząstkowych 380 Czas wykonywania algorytmów [ms] 3 Rozmiar plecaka P Przedstawione wykresy ilustrują wpływ poszczególnych parametrów na czasy wykonywania algorytmów. Widzimy, że oba wykresy wyraźnie wraz ze wzrostem rozmiaru plecaka i ilości przedmiotów zaczynają „rozbiegać” się w stronę nieskończoności. Jest to spowodowane różnicami w algorytmach, które testowano. Przedstawione dwa podejścia wyraźnie ilustrują przewagę drugiej metody nad pierwszą. Optymalizacja zajętości pamięci spowodowała również przyspieszenie algorytmu. W ogólnym rozrachunku są to dość duże zmiany, skąd wniosek, że optymalizacja działa bardzo dobrze. Dla formalności testy realizowano poprzez wczytywanie danych z plików wejściowych. Początkowo wygenerowano odpowiednie zestawy danych, następnie wczytywano je i przetwarzano. Podczas pomiaru czasu uwzględniono tylko czas wykonywania się algorytmu, zaniechano pomiaru całościowego wraz z odczytem danych, gdyż byłoby to nie obiektywne (różne prędkości dysków twardych itp.) 31 4. BIBLIOGRAFIA 1. Sysło M., Deo N., Kowalik J. Algorytmy optymalizacji dyskretnej. PWN Warszawa 1995 2. Wikipedia wolna encyklopedia: http://pl.wikipedia.org/wiki/problem_plecakowy 3. Studia informatyczne beta: http://wazniak.mimuw.edu.pl Opracowania z zakresu programowania dynamicznego i zaawansowanego 4. Wykład z programowania dynamicznego: http://www.home.umk.pl/~bruzdaj/pliki/Programowanie_Dynam-Wyklad.pdf 32