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

Podobne dokumenty