|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Należy znaleźc rozwiązanie ![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Faza 1 algorytmu: redukcja macierzy A i B. ![]()
Element maksymalny jest redukowany w pierwszej kolejności. W tym celu szukamy elementu minimalnego w wierszu i* :
![]()
elementu minimalnego w kolumnie j* :
![]()
jeżeli teraz
![]() to redukujemy elementy wiersza i* o wartość mi*l i podstawiamy |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Szeregując indeksy j w porządku nie rosnących wartości BBj utwórz wektor BB.
![]()
Szeregując indeksy i w porządku nie malejących wartości AAi utwórz wektor AA.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Algorytm w sposób systematyczny sprawdza do n(n-1)/2 (ponieważ |N2(p)| = n(n-1)/2 ) rozwiązań z otoczenia sukcesora w kolejności ustalonej arbitralnie lub określonej za pomocą wybranej reguły. Sprawdzanie rozwiązań z otoczenia sukcesora jest realizowane do chwili otrzymania pierwszego rozwiązania lepszego od sukcesora, które zostaje nowym sukcesorem i proces szukania jest kontynuowany w otoczeniu nowego sukcesora. Jeśli w całym otoczeniu sukcesora algorytm nie znajdzie rozwiązania lepszego, to sukcesor jest rozwiązaniem lokalnie optymalnym i proces szukania kończy się. Koszt obliczeń wartości funkcji celu rozwiązań p' należących do otoczenia N2(p) redukuje się, jeżeli oblicza się tylko różnicę wartości funkcji celu sukcesora p i p':
![]()
Jeżeli df > 0, to permutacja p' jest rozwiązaniem lepszym od sukcesora p. Wyliczenie przyrostu df wymaga wykonania (2n-2) operacji mnożenia oraz (5n-4) operacji dodawania. Stąd algorytm Heidera jest zaliczany przez wielu autorów do najbardziej efektywnych algorytmów podwójnej zmiany. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Górna część macierzy GLT zawiera pamięć krótkoterminową. Pamięta, że elementy (5,8) nie mogą być przestawiane w procesie poszukiwania lepszego rozwiązania przez okres 5 iteracji. Podobnie elementy (1,4) przez 2 iteracje, elementy (1,7) przez 3, elementy (2,8) przez 4, a elementy (3,6) przez 1 iterację. Po każdej kolejnej iteracji czas zabronienia przestawienia danej pary jest zmniejszany o 1. Dolna część macierzy DLT zawiera pamięć długoterminową. Pamięta wszystkie przestawienia par elementów, po każdym przestawieniu pary elementów zwiększa komórkę pamięci dla tej pary o 1. Para (3,2) przestawiana była już 3-krotnie, pary (4,1), (7,1), (9,1), (8,2), (9,7) tylko raz, itd. Główna przekątna macierzy nie jest dostępna. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Pierwszy przycisk od lewej Otwórz umożliwia wczytanie problemu z pliku. Naciśnięcie przycisku powoduje otwarcie okienka dialogowego, które umożliwia przeglądanie katalogów i plików na dyskach i wybór pliku z danymi do otwarcia.
![]()
Pliki z danymi powinny mieć następującą strukturę:
Pierwszą liczbą do odczytania z pliku powinien być rozmiar problemu, następnie kolejne liczby to elementy macierzy A, następnie elementy macierzy B, i ewentualnie elementy macierzy C. Naciśnięcie przycisku OK zamyka okienko dialogowe otwarcia pliku i jeśli format pliku będzie niezgodny z przedstawionym powyżej, program zgłosi błąd: Niepoprawny format pliku. Jeśli natomiast struktura pliku będzie poprawna dane zostaną wczytane pod odpowiednie zmienne tablicowe, główne okno programu powiększy się i będzie wyglądać jak na rysunku poniżej.
![]()
U góry znajduje się pole opisu, które zawiera informację o tym z jakiego pliku został wczytany problem i jaki jest jego rozmiar.
![]()
Okienko dialogowe drukowania wyświetla aktualny stan drukarki, jej nazwę, umożliwia wywołanie okna właściwości drukarki, w którym w zależności od typu i oprogramowania drukarki można dokonać m.in. zmiany jakości papieru, jakości wydruku, i wiele innych opcji; posiada pole do wpisania ilości kopii do druku. Naciśnięcie przycisku OK powoduje wydrukowanie bieżącego okna programu.
Następne trzy przyciski umożliwiają podgląd macierzy z danymi, tzn. naciśnięcie przycisku Macierz A powoduje wyświetlenie w oknie programu danych w macierzy A, podobnie działają przyciski Macierz B i Macierz C.
![]()
Następny przycisk Algorytm powoduje wyświetlenie okna algorytmu opisanego powyżej podczas opisu przycisku Otwórz.
Kolejny przycisk Start uruchamia algorytm Tabu Search i jest aktywny tylko wtedy gdy widoczne jest okno algorytmu. Po naciśnięciu przycisku na grafie rysowany jest przebieg funkcji celu, obok po lewej wyświetlana jest wartość aktualna funkcji celu, poniżej zmienia się wskaźnik postępu algorytmu do momentu gdy ilość iteracji osiągnie wartość parametru K. Wtedy w dole okna wyświetlany jest znaleziony wektor rozwiązania minimalnego.
![]()
Przycisk Historia wyświetla okno, w którym znajdują się wszystkie wywołania algorytmu, tzn. okno to zawiera listę, w której nowy rekord dopisywany jest po każdorazowym wywołaniu algorytmu. Rekord zawiera następujące informacje o danym wywołaniu algorytmu: plik skąd pochodzą dane, rozmiar problemu, czy problem zawiera macierz A, czy macierz B, czy macierz C, wartości parametrów K, T i alfa, wartość funkcji celu startową i minimalną, plik do którego zapisano kolejne wartości funkcji celu podczas poszukiwania rozwiązania.
![]()
Następny przycisk Pomoc wyświetla pomoc programu. Pomoc programu zawiera podstawowe informacje o problemie QAP i procedurze Tabu Search oraz instrukcję obsługi programu.
Ostatnim dostępnym przyciskiem jest przycisk Koniec, który kończy działanie programu.
![]() Za pomocą menu rozwijanego można decydować o tym co ma być widoczne w oknie programu. Tzn. można wyświetlić menu główne programu, które normalnie jest niewidoczne, można wyłączyć pasek przycisków lub tylko etykiety przycisków, można włączyć lub wyłączyć wyświetlanie okna głównego, można włączyć lub wyłączyć wyświetlanie paska statusu. Pasek statusu zawiera podpowiedzi lub informację o obiekcie, nad którym znajduje się aktualnie wskaźnik myszy. Gdy pasek statusu jest niewidoczny, te same informacje wyświetlane są w oknie algorytmu nad grafem. Menu główne zawiera te same opcje co przyciski na pasku narzędzi. Można dodatkowo wyświetlić wizytówkę programu, zawierającą podstawowe informacje o programie i autorze. Są to wszystkie podstawowe informacje o obsłudze programu. W następnym punkcie przedstawiono szczegółowo w jaki sposób zrealizowano interfejs graficzny i opisano dokładnie funkcje obsługi poszczególnych zdarzeń np. naciśnięcie przycisku. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
void __fastcall TMainForm::OnCreate(TObject *Sender)
W pierwszym wierszu funkcji, obsłudze zdarzenia OnHint formularza głównego przypisywana jest funkcja o tej samej nazwie OnHint. Zdarzenie OnHint jest generowane podczas przemieszczania kursora myszki nad danym komponentem i powoduje wyświetlanie podpowiedzi jeśli taka została wcześniej zdefiniowana dla tego komponentu. Domyślnie podpowiedzi wyświetlane są w pasku statusowym i nie trzeba definiować własnej funkcji obsługi zdarzenia OnHint. W programie zdecydowano się aby podpowiedzi wyświetlane były za pomocą etykiety LabelOpisTS, należącej do strony TabSheetTS, gdy pasek statusowy jest niewidoczny. Poniżej znajduje się zawartość funkcji OnHint.
void __fastcall TMainForm::OnHint(TObject *Sender)
Jeśli pasek statusowy jest niewidoczny wyświetlaj podpowiedzi za pomocą etykiety LabelOpisTS. W przeciwnym przypadku wyświetlaj je w pasku statusowym.
void TMainForm::OpisHistorii(void)
W trzecim wierszu funkcji OnCreate zerowana jest zmienna globalna ileTS, która zlicza kolejne wywołania algorytmu Tabu Search, tzn. kolejne naciśnięcie przycisku Start. Wprowadzona została w celu automatycznej numeracji plików, do których zapisywane są kolejne wartości funkcji celu podczas obliczeń algorytmu.
void __fastcall TMainForm::MenuOpenClick(TObject *Sender)
W pierwszym wierszu deklarowana jest zmienna typu całkowitego o nazwie rozmiar, która jest zmienną lokalną tej funkcji i ma za zadanie pamiętać rozmiar wczytanego problemu. W drugim wierszu wywoływane jest okienko dialogowe otwarcia pliku. Możliwość otwarcia pliku została ograniczona do plików z rozszerzeniem .dat poprzez wprowadzenie odpowiedniego filtru we właściwości filter komponentu okienka dialogowego. W przypadku kliknięcia przycisku Anuluj nie są wykonywane żadne instrukcje i funkcja MenuOpenClick() ulega zakończeniu. Jeśli plik z danymi został wybrany i kliknięto przycisk OK. wykonywany jest dalszy ciąg instrukcji. Tworzony jest obiekt file klasy ifstream i automatycznie otwierany plik o nazwie zapisanej we właściwości FileName okienka dialogowego otwarcia pliku. Ponieważ konstruktor obiektu file klasy ifstream wymaga parametru typu wskaźnika znakowego char*, a właściwość FileName jest typu obiekt klasy AnsiString dlatego wykorzystano standardowš metodę c_str(), która zapewnia odpowiednią konwersję. Następnie odczytana zostaje pierwsza liczba z pliku, która informuje o rozmiarze zdefiniowanego w pliku problemu. Dalej sprawdzane jest czy zmienna globalna pProblem, będąca wskaźnikiem typu QAP, zawiera adres obiektu. Jeśli tak obiekt jest niszczony i zerowany wskaźnik. Następnie dynamicznie przydzielana jest pamięć dla obiektu typu QAP o adresie wpisanym pod wskaźnik pProblem, za pomocą operatora new. Wywoływany jest konstruktor klasy QAP z jednym parametrem, w którym przekazywany rozmiar problemu. W następnym wierszu wywoływana jest funkcja WczytajMacierze(file) obiektu QAP, która odczytuje dane z pliku. Dokładny opis funkcji znajduje się w dalszej części pracy, gdy omawiane są dokładnie poszczególne klasy. Funkcja WczytajMacierze(file) zwraca ilość odczytanych macierzy z pliku. Jest to wykorzystywane w kolejnych wierszach do ustawienia zmiennych typu bool MacierzA, MacierzB, MacierzC, obiektu QAP odpowiednio na wartość true lub false, oraz w zależności od tego które macierze zostały odczytane, odpowiednie przyciski na pasku narzędziowym są blokowane.
void TMainForm::ButtonActivate(bool _aktywne)
Następnie jeśli widok programu ograniczał się do paska narzędzi okno programu zostaje powiększone, i widoczna jest strona TabSheetTS komponentu PageControl. W kolejnym wierszu sprawdzane jest czy istnieje obiekt TS, poprzez sprawdzenie czy wskaźnik pTabu zawiera adres obiektu , czy wartość NULL. Jeśli obiekt TS istniał jest niszczony i zerowany wskaźnik. Następnie dynamicznie przydzielana jest pamięć dla obiektu TS, za pomocą operatora new, wywoływany jest konstruktor klasy TS, oraz adres obiektu w pamięci przypisywany jest pod zmienną pTabu. W kolejnym wierszu uaktualniana jest informacja o problemie za pomocą etykiety LabelOpisProblemu na stronie TabSheetTS. Zmieniona zostaje nazwa pliku, z którego pochodzą dane, oraz rozmiar problemu. Metoda Repaint() zapewnia prawidłowe wyświetlenie nowych danych. Kolejne wiersze zerują dane w komponencie StringGridPI, który wyświetla wektory rozwiązań startowy i minimalny. Również zawartość grafu jest czyszczona. Zapewniają to kolejne wiersze. Następnie blokowany jest przycisk "Start", do momentu wybrania algorytmu konstrukcyjnego i ustalenia rozwiązania startowego. Wtedy przycisk "Start" jest udostępniany. Są to wszystkie czynności wykonywane w funkcji MenuOpenClick() jeśli wykonanie funkcji WczytajMacierze(file) powiodło się. W przeciwnym przypadku kasowany jest obiekt o adresie przechowywanym pod wskaźnikiem pProblem i wyświetlane jest okienko z informacją: Niewłaściwy format pliku !. Na koniec zamykany jest plik i niszczony obiekt file. Po wykonaniu funkcji MenuOpenClick() przydzielona została pamięć dla dwóch obiektów. Pierwszy obiekt powstały na bazie klasy QAP zawierający dane o problemie oraz drugi obiekt utworzony na podstawie klasy TS, przygotowany do obsługi algorytmu TabuSearch.
void __fastcall TMainForm::MenuPrintClick(TObject *Sender)
Funkcja ta posiada tylko jeden wiersz, w którym wywoływane jest okienko dialogowe drukowania. Jeśli został naciśnięty przycisk OK, wtedy wywoływana jest metoda Print(), która powoduje wydrukowanie bieżącego okna. W zależności, która strona komponentu PageControl jest aktywna, taka zostanie wydrukowana.
void __fastcall TMainForm::MenuAMatrixClick(TObject *Sender)
void __fastcall TMainForm::MenuBMatrixClick(TObject *Sender)
void __fastcall TMainForm::MenuCMatrixClick(TObject *Sender)
W pierwszym i drugim wierszu funkcji włączane jest okno główne programu jeśli było nieaktywne. Wszystkie powyższe funkcje różnią się trzecim wierszem, w którym ustalana jest aktualna strona komponentu PageControl, czyli która macierz ma być wyświetlona. Instrukcja w Ostatnim wierszu blokuje przycisk Start, który jest uaktywniany jedynie po wyborze nowego algorytmu konstrukcyjnego.
void __fastcall TMainForm::ToolButtonTSClick(TObject *Sender)
Funkcja posiada tylko dwa wiersze, instrukcja w pierwszym włącza okno główne programu jeśli było niewidoczne, instrukcja w drugim ustala aktywną stronę komponentu PageControl na TabSheetTS.
void __fastcall TMainForm::ToolButtonStartClick(TObject *Sender)
W pierwszej części funkcji deklarowane są zmienne: dwie typu całkowitego, pierwsza o nazwie i wykorzystywana do indeksowania w pętli for, druga o nazwie rozmiar, która jest inicjowana wartością zwracaną przez funkcję ZwrocRozmiar() obiektu QAP; trzy obiekty typu String, które są inicjowane wartościami parametrów K,T,a pobranymi z pól edycyjnych znajdujących się na stronie TabSheetTS.; jeden obiekt typu Wektor o nazwie Pii; dwie zmienne typu całkowitego, k przechowująca ilość iteracji, t przechowująca wartość okresu karencji dla danej pary; jedną zmienną typu float o nazwie alfa przechowującą wartość parametru alfa. Następnie wykonywane są instrukcje następujące po instrukcji try. Jeśli wykonanie którejś z tych instrukcji nie powiedzie się, zostanie zgłoszony wyjątek, przechwycony przez instrukcję catch, i wykonane zostaną instrukcje obsługi wyjątku, tzn. wyświetlone zostanie okienko z informacją: Niewłaściwy parametr !. Oznacza to przechwycenie wyjątku o niemożliwości wykonania konwersji za pomocą instrukcji StrToInt() lub StrToFloat(). Konwersje te są dokonywane w pierwszych wierszach następujących po instrukcji try. Następnie nowe parametry k, t, alfa otrzymane po konwersji tekstu z pól edycyjnych są wpisywane do obiektu TS za pomocą funkcji zdefiniowanych dla tego obiektu: NoweK(), NoweT(), NoweAlfa(). W następnym wierszu wywoływana jest funkcja AlgorytmTS() obiektu TS, która zawiera ciało algorytmu TabuSearch. Funkcja ta zostanie omówiona w dalszej części pracy. Po zakończeniu obliczeń algorytmu TabuSearch wywoływana jest funkcja DodajDoHistorii(). Program zapamiętuje każde wywołanie algorytmu wraz z parametrami jego wywołania, i zapisuje je jako kolejny rekord w tabeli na stronie TabSheetHistoria. Dokonuje tego właśnie funkcja DodajDoHistorii().
void TMainForm::DodajDoHistorii()
W pierwszym wierszu deklarowane są trzy obiekty a, b, c klasy String. Następnie w zależności od zawartości zmiennych MacierzA, MacierzB, MacierzC, obiektu QAP, obiektom a, b, c, przypisywane są cišgi znaków Tak lub Nie. Następnie dla aktualnego rekordu do kolejnych kolumn tabeli wpisywane są poszczególne wartości. W pierwszym polu rekordu wpisywana jest nazwa pliku, z którego pochodzą dane, w drugim polu rozmiar problemu n pobrany wcześniej za pomocą funkcji ZwrocRozmiar() obiektu QAP. W kolejnych trzech polach wpisywana jest zawartość obiektów a, b, c, następne trzy pola zawierają wartości parametrów K, T, a pobranych z obiektu TS za pomocą funkcji ZwrocK(), ZwrocT(), ZwrocAlfa(), kolejne dwa pola to wartości funkcji celu startowa i minimalna pobrane również z obiektu TS za pomocš funkcji ZwrocQst() i ZwrocQmin(). Następne pole jest wypełniane ciągiem znaków zwracanym przez funkcję ZwrocAlgorytmSt() obiektu QAP, ostatnie pole rekordu zawiera nazwę pliku gdzie zostały zapisane kolejne wartości funkcji celu, nazwa ta wpisywana jest za pomocą funkcji ZwrocNazwePliku() obiektu TS. W przedostatnim wierszu funkcji DodajDoHistorii() tworzony jest nowy rekord w tabeli StringGridHistoria, a w ostatnim wierszu jest on numerowany kolejną wartością.
void __fastcall TMainForm::ToolButtonHistoriaClick(TObject *Sender)
Funkcja wyświetla okno główne programu jeśli było niewidoczne, przełącza aktywną stronę w komponencie PageControl na TabSheetHistoria oraz blokuje przycisk Start.
void __fastcall TMainForm::MenuExitClick(TObject *Sender)
Funkcja sprawdza czy istnieją obiekty QAP i TS. Jeśli tak są one niszczone. Następnie wywoływana jest metoda Terminate(), która kończy działanie programu.
void __fastcall TMainForm::MenuMatricesClick(TObject *Sender)
Funkcja ma za zadanie sprawdzać w momencie kliknięcia polecenia Macierze menu głównego, która strona komponentu PageControl jest aktywna, i ustawić znacznik w menu rozwijanym dla polecenia Macierze menu głównego na odpowiedniej pozycji.
![]()
Funkcja MenuWidokClick() obsługuje zdarzenie związane z wybraniem polecenia Widok menu głównego. Polecenie Widok zarządza widokiem programu, tzn. włącza lub wyłącza poszczególne elementy okna programu: pasek narzędzi, menu główne, okno główne programu (komponent PageControl), pasek statusu. Funkcja MenuWidokClick() ma za zadanie sprawdzać, które z elementów są widoczne w momencie wybrania polecenia Widok i na tej podstawie uaktualnić znaczniki obok elementów widocznych lub niewidocznych w menu rozwiniętym po wybraniu polecenia Widok.
![]()
void __fastcall TMainForm::MenuWidokClick(TObject *Sender)
Wybierając polecenie Widok z menu głównego wykonana zostaje funkcja MenuWidokClick() po czym rozwinięte zostaje podmenu pokazane na rysunku 10. Wybranie polecenia Pasek przycisków powoduje wykonanie funkcji obsługi zamieszczonej poniżej.
void __fastcall TMainForm::MenuPasekprzyciskowClick(TObject *Sender)
Funkcja ta włącza lub wyłącza widoczność drugiej bandy komponentu CoolBar na której umieszczono pasek z przyciskami. Dodatkowo funkcja posiada zabezpieczenie przed wyłączeniem paska przycisków, wtedy gdy menu główne jest niewidoczne. W takim przypadku funkcja automatycznie włącza widoczność menu głównego, a dokładnie bandy pierwszej (indeksowanie od 0) komponentu CoolBar.
void __fastcall TMainForm::MenuMenuglowneClick(TObject *Sender)
Funkcja ta wywoływana jest po wybraniu polecenia Menu glówne z podmenu zamieszczonego na rysunku 10. Włącza lub wyłącza widoczność menu głównego, posiada takie samo zabezpieczenie jak funkcja MenuPasekprzyciskowClick().
void __fastcall TMainForm::MenuOknodanychClick(TObject *Sender)
Funkcja MenuPasekstatusowyClick() wykonywana jest w momencie wybrania polecenia Pasek statusowy z podmenu zamieszczonego na rysunku 10. Włącza lub wyłącza widoczność komponentu StatusBar.
void __fastcall TMainForm::MenuPasekstatusowyClick(TObject *Sender)
Funkcja MenuTekstnaprzyciskachClick() ma za zadanie włączać lub wyłączać etykiety tekstowe na przyciskach paska narzędziowego.
void __fastcall TMainForm::MenuTekstnaprzyciskachClick(TObject *Sender)
Powyższa funkcja dodatkowo po wyłączeniu wyświetlania etykiet tekstowych na przyciskach, zmienia rozmiar przycisków. Do rozmiaru przycisków automatycznie dostosowuje się wielkość paska narzędziowego, poprzez ustawienie jego właściwości AutoSize na true.
void __fastcall TMainForm::StatusBarDblClick(TObject *Sender)
Funkcja ta ma za zadanie wyłączyć widzialność paska statusowego po dwukrotnym kliknięciu prawym przyciskiem myszy w obszarze paska.
void __fastcall TMainForm::ComboBoxClick(TObject *Sender)
Funkcja powyższa w pierwszym wierszu deklaruje dwie zmienne lokalne typu całkowitego, pierwsza o nazwie i wykorzystywana jako indeks w pętli for, druga rozmiar inicjowana w tym samym wierszu wartością zwracaną przez funkcję ZwrocRozmiar() obiektu QAP. W drugim wierszu deklarowany jest lokalny obiekt o nazwie alg klasy String, inicjowany tekstem wybranym z listy rozwijanej ComboBox, w trzecim wierszu deklarowany jest lokalny obiekt klasy Wektor o nazwie Pii. Następnie wywoływana jest funkcja AlgorytmStartowy obiektu QAP z parametrem alg. Funkcja ta zawiera trzy algorytmy generujące rozwiązanie startowe, w zależności od parametru alg wywoływany jest odpowiedni algorytm. Dokładny opis działania tej funkcji przedstawiony jest w dalszej części pracy podczas opisu funkcji i składowych obiektów. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class Wektor
Pola prywatne klasy to wskaźnik pwektor typu całkowitego przechowujący adres obszaru w pamięci zarezerwowanego dla utworzonego obiektu tej klasy, oraz zmienna typu całkowitego n przechowująca rozmiar wektora. Przedstawiony na listingu 26 plik nagłówkowy Wektor.h zawiera jedynie strukturę klasy Wektor. Dokładna budowa funkcji, czyli jej implementacja znajduje się w pliku Wektor.cpp. Poniżej przedstawiono jedynie budowę funkcji konstruującej obiekty typu Wektor.
Wektor::Wektor(int _n)
W pierwszym wierszu funkcji przydzielona dynamicznie zostaje pamięć dla tablicy elementów typu całkowitego o wymiarze przekazanym do funkcji przez parametr _n. Adres obszaru pamięci zostaje wpisany do zmiennej pwektor. W drugim wierszu funkcji zainicjowana zostaje zmienna prywatna klasy n wartością przekazaną przez parametr _n. W następnych dwóch wierszach obiekt klasy Wektor zostaje zainicjowany wartościami 0 przy pomocy pętli for. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class Tablica
Powyższa struktura to plik nagłówkowy klasy o nazwie Tablica.h. Natomiast definicje funkcji tzn. budowa znajdują się w pliku Tablica.cpp. Poniżej przedstawiono jedynie budowę konstruktora klasy Tablica.
Tablica::Tablica(int _n)
W pierwszym wierszu przy pomocy operatora new przydzielana jest dynamicznie pamięć dla tablicy wskaźników typu całkowitego o rozmiarze przekazanym w parametrze funkcji _n. Adres w pamięci pod którym przechowywana jest tablica wskaźników wpisany jest pod zmienną ptab. Następnie przy pomocy pętli for przydzielona zostaje pamięć dla tablicy elementów typu całkowitego, przy czym w każdej iteracji adres przydzielonego obszaru w pamięci jest wpisywany pod kolejny element z utworzonej wcześniej dynamicznie tablicy wskaźników. Kolejne dwie pętle for inicjują elementy tablicy wartościami 0. W ostatnim wierszu funkcji rozmiar przechowywany w zmiennej lokalnej _n przepisywany jest do zmiennej prywatnej obiektu o nazwie n. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class QAP
Klasa posiada pięć pól prywatnych, pierwsze to zmienna typu całkowitego rozmiar przechowująca rozmiar problemu jakiego ten obiekt dotyczy. Trzy kolejne pola to obiekty klasy Tablica służące do przechowywania macierzy odczytanych z pliku, definiujących aktualny problem. Kolejne pole to obiekt klasy Wektor przechowujący wektor rozwiązania. Wektor ten ulega zmianie podczas pracy algorytmu Tabu Search ze względu na zamianę poszczególnych par elementów wektora P. Ostatnie pole to obiekt AlgStartowy klasy String przechowujący nazwę wybranego z listy algorytmu startowego. Pierwsza publiczna funkcja składowa to konstruktor z jednym parametrem przekazującym rozmiar problemu. Funkcja ta wywołuje bezpośrednio konstruktory klasy Tablica oraz Wektor i utworzone obiekty przypisuje obiektom prywatnym klasy QAP.
{
W pierwszym wierszu funkcji przy pomocy instrukcji while zliczane są wszystkie znaki w pliku. Działanie to ma na celu ustalenie ile macierzy z danymi znajduje się w pliku. Prawidłowo zdefiniowany problem posiada dwie lub trzy macierze. Program wychodzi z pętli po napotkaniu znaku końca pliku. W drugim wierszu funkcji wskaźnik plikowy ustawiany jest na początek pliku. Następnie odczytywana jest pierwsza liczba z pliku czyli rozmiar problemu. Jest to informacja niepotrzebna w tej funkcji i nie jest wykorzystywana. Pobieranie danych zaczyna się od drugiej liczby. W zależności od ilości zliczonych liczb w pliku, czyli od wartości zmiennej ile , odczytywane są kolejno liczby i wpisywane jako kolejne elementy prywatnych obiektów A,B,C. W zależności od ilości zdefiniowanych macierzy w pliku funkcja zwraca ilość odczytanych macierzy.
bool QAP::AlgorytmStartowy(String _alg)
Gdy parametrem funkcji jest ciąg znaków losowy rozwiązanie startowe generowane jest na podstawie losowania kolejnych elementów wektora. Algorytm działa w ten sposób, że losuje liczbę, jeśli jest to pierwsza wylosowana liczba to wpisuje ją jako pierwszy element wektora rozwiązania startowego. Jeśli jest to kolejna wylosowana liczba, algorytm sprawdza, czy wcześniej nie wylosowano już takiej liczby. Jeśli wektor zawiera już taki element, następuje nowe losowanie do momentu uzyskania liczby różniącej się od wszystkich wylosowanych dotąd elementów wektora.
else if(_alg == "algorytm 1"){
W dwóch pierwszych wierszach przydzielana jest dynamicznie pamięć dla tablicy struktur typu Para i adres obszaru przydzielonej pamięci wpisany jest pod zmienne wskaźnikowe rA i rB. Każda para zawiera dwa pola. W tym przypadku pierwsze pole i pary rA wykorzystane zostało do przechowania sumy elementów w poszczególnych wierszach, drugie pole oznacza numer wiersza. Dwie pierwsze pętle for realizują sumowanie elementów w wierszach. Następnie przy pomocy funkcji qsort z biblioteki standardowej Stdlib systemu BilderC++ powstałe sumy sortowane są w kolejności rosnącej.
Ostatnia pętla for generuje wektor rozwišzania startowego w taki sposób, że minimalna wartość pola i pary rA przypisywana jest maksymalnej wartości pola i pary rB. Powoduje to uśrednienie wartości funkcji celu, a tym samym szybsze znalezienie rozwiązania optymalnego. W tej samej pętli zwalniana jest pamięć dla tablic struktur rA i rB.
else if(_alg == "algorytm 2"){
Powyższy algorytm różni się od algorytmu przedstawionego na listingu 31 jedynie tym, że zlicza sumy elementów poszczególnych kolumn macierzy A i B, a nie wierszy. Jeśli macierze są symetryczne, wektor rozwiązania startowego wygenerowanego na podstawie algorytmu 1 jest taki sam jak wektor utworzony przez algorytm 2.
void QAP::WypiszMacierze(void)
Funkcja Permutacja(Para &p) ma za zadanie przestawić dwa elementy (przekazane jako parametr) w wektorze rozwiązania P. Funkcja posiada jedną zmienną lokalną służącą do zapamiętania jednego elementu pary przekazanej jako parametr. Jest to konieczne do prawidłowej zamiany dwóch elementów.
void QAP::Permutacja(Para &p)
Funkcja Q() liczy wartość funkcji celu. Poniżej przedstawiono jej strukturę.
double QAP::Q(void)
Powyższa funkcja deklaruje dwie zmienne lokalne typu double, które przechowują wartości sum pomocniczych funkcji celu. Pierwsza zawiera sumę zliczonš na podstawie dodawania odpowiednich elementów macierzy C, druga zawiera sumę zliczoną na podstawie dodawania poszczególnych iloczynów elementów macierzy A i B. Funkcja zawiera jeszcze dwie zmienne lokalne typu całkowitego służące jako indeksy w pętli for.
int QAP::ZwrocRozmiar(void)
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class TS
Klasa posiada jeden konstruktor z parametrem, w którym przekazywany jest adres obiektu, gdzie zdefiniowano problem QAP. Funkcja UstalProblem() ma za zadanie, zainicjować trzy pola prywatne klasy TS. Poniżej przedstawiono jej budowę.
void TS::UstalProblem(void)
Obiekt prywatny PIst inicjowany jest wektorem rozwiązania startowego otrzymanego z obiektu QAP poprzez publiczną funkcję ZwrocPI() klasy QAP. Zmienna prywatna Qst inicjowana jest wartością funkcji celu obliczoną za pomocą publicznej funkcji Q() dostępnej w klasie QAP. Zmienna prywatna Qmin inicjowana jest wartością zmiennej Qst, ponieważ na początku wartość minimalna funkcji celu jest wartością startową.
bool TS::AlgorytmTS(void)
Pierwsza część funkcji przedstawiona została powyżej. Deklarowane są jedna zmienna typu całkowitego k zawierająca numer bieżącej iteracji, dwie zmienne typu bool: NoweQprim przyjmująca wartość true gdy znaleziona aktualna wartość funkcji celu jest wyznaczona na podstawie formuły Qprim; NoweQminodQprim() przyjmująca wartość true gdy znaleziono nową minimalną wartość funkcji celu Qmin na podstawie formuły Qprim; trzy struktury gwiazdka, prim, loop typu Para do pamiętania pary przestawionych elementów tzn. gwiazdka pamięta parę elementów, po których przestawieniu wystąpiło polepszenie wartości funkcji celu obliczonej przy pomocy formuły Qgwiazdka, prim pamięta parę elementów, po których przestawieniu wystąpiło polepszenie wartości funkcji celu obliczonej przy pomocy formuły Qprim, loop służy jako para pomocnicza wykorzystywana w pętlach for. W dalszej części deklarowane są trzy zmienne lokalne typu double, do pamiętania wartości funkcji celu Qakt, Qprim, Qqwiazdka. Następnie deklarowana jest zmienna typu całkowitego rozmiar inicjowana wartością rozmiaru problemu z obiektu QAP. Następnie maksymalna wartość wskaźnika postępu ustawiana jest na wartość jaką posiada zmienna K. W kolejnym wierszu tworzona jest nazwa pliku z następnym numerem otrzymanym ze zmiennej globalnej ileTS oraz otwierany jest plik o tej nazwie do zapisu, gdzie zapisana zostaje wartość funkcji celu startowa, oraz zwiększony zostaje licznik wywołań algorytmu.
for(k=1; k<(K+1); k++){ <-- początek pętli głównej
W dalszej części uruchamiany jest algorytm, który działa w pętli for. Ilość iteracji jest równa wartości zmiennej K. Pierwsze pięć wierszy pętli odpowiedzialne jest za wizualizację procesu poszukiwania rozwiązania, na stronie TabSheetTS komponentu PageControl. Zmieniana jest wartość wskaźnika postępu na wartość przechowywaną w zmiennej k, następnie kreślona jest wartość funkcji celu w oknie grafu, oraz wyświetlana za pomocą etykiety LabelQmin.
Qprim = Problem->Q();
Pierwsza część algorytmu zgodnie z jego strukturą opisaną w punkcie 1.3 pracy dotyczy wyznaczenia najlepszego rozwiązania w otoczeniu. Realizuje to część algorytmu zamieszczona na listingu 43. Zmienna Qprim inicjowana jest wartością funkcji celu. Zmienna Qgwiazdka taką samą wartością, gdyż w pierwszej iteracji każdy element listy tabu ma wartość zero, więc wartość drugiego członu wzoru jest równa zero. Następne działania odbywają się w dwóch pętlach for, które mają za zadanie przestawić parami wszystkie elementy wektora rozwiązania za pomocą funkcji Permutacja() i po każdym przestawieniu obliczać aktualną wartość funkcji celu za pomocą funkcji Q() obiektu klasy QAP. Następnie sprawdzane są dwa warunki określone w kroku 2 algorytmu opisanym w punkcie 1.3, a realizowane przez pętlę if / else if. Jeśli nastąpiła poprawa wartości funkcji celu to zapamiętywana jest para elementów, po przestawieniu których nastąpiło polepszenie wartości funkcji celu. Jeśli nastąpiło polepszenie wartości funkcji celu Qgwiazdka, to pamiętana jest para gwiazdka, jeśli nastąpiła poprawa wartości Qprim to pamiętana jest para prim. Na koniec każdej iteracji elementy przestawione wracają na swoją pozycję początkową. Poniżej przedstawiono dalszą część algorytmu (krok 3), w którym następuje poprawa rozwiązań.
Problem->Permutacja(gwiazdka);
Za pomocą funkcji Permutacja() przestawiane są dwa elementy pamiętane w zmiennej gwiazdka. Następnie obliczana jest wartość funkcji celu i jeśli jest lepsza od obecnej wartości minimalnej to zapamiętany zostaje wektor rozwiązania w zmiennej PImin . Następnie przestawione elementy wracają na swoją pozycję. Jeśli wystąpiła zmiana wartości funkcji celu Qprim to obliczona zostaje aktualna wartość funkcji celu po przestawieniu elementów pamiętanych w zmiennej prim i jeśli jest lepsza od obecnej wartości Qmin zostaje zapamiętany nowy wektor rozwiązania w zmiennej PImin. Następnie przestawione elementy wracają na swoją pozycję. Poniżej znajduje się ostatnia część algorytmu, w której następuje korekta stanu pamięci, czyli uaktualnienie zawartości listy tabu.
for(loop.i=0;loop.i<(rozmiar-1);loop.i++)
Za pomocą dwóch pętli for przeszukiwana jest górna część listy tabu i wartość każdego elementu górnej listy tabu większego od zera jest zmniejszana o jeden. Tzn. okres zablokowania dla danej pary w każdej iteracji jest zmniejszany o jeden. Następnie za pomocš instrukcji if else dokonywana jest dalsza korekta listy tabu. Jeśli poprawa rozwiązań nastąpiła na skutek przestawienia pary elementów prim (instrukcja if) to pozycja na górnej liście tabu dla tych elementów jest inicjowana wartością parametru T, jest to okres karencji dla danej pary, który jest wprowadzany za pomocą pola edycyjnego umieszczonego na stronie TabSheetTS; przestawiane są dwa elementy w wektorze rozwiązania za pomocą funkcji Permutacja() obiektu klasy QAP; zwiększana jest o jeden wartość dolnej listy tabu dla elementów pary prim, tzn. licznik wystąpienia danej pary zwiększany jest o jeden. Jeśli poprawa rozwiązania nastąpiła na skutek przestawienia elementów pary gwiazdka to wykonywane są opisane powyżej operacje jednak dla pary gwiazdka (instrukcja else). Ostatnia instrukcja w głównej pętli algorytmu zapisuje aktualną wartość funkcji celu do pliku. Gdy licznik w głównej pętli for osiągnie wartość K , wykonywanie instrukcji w pętli zostaje zakończone i tym samym jest to koniec algorytmu Tabu Search. Funkcja AlgorytmTS() w trzech ostatnich wierszach zamyka plik do zapisu, ustawia wskaźnik postępu ajgorytmu na wartość zero, i zwraca wartość logiczną true. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
struct Para
Funkcja PorownajPary() porównuje dwie struktury typu Para według pola i struktur. Zwraca wartość dodatnią jeśli struktura przekazana w pierwszym parametrze do funkcji posiada większą wartość w polu i od struktury przekazanej w drugim parametrze lub funkcja zwraca wartość ujemną jeśli pole i struktury przekazanej w pierwszym parametrze ma wartość mniejszą od pola i struktury przekazanej przez drugi parametr do funkcji.
int PorownajPary(const void *arg1, const void *arg2)
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]()
Jak widać z powyższego wykresu zmiana parametru T ma wpływ na przebieg poszukiwania rozwiązania w problemie Kwadratowego Zagadnienia Przydziału za pomocą algorytmu Tabu Search. Dla T = 25 minimalna wartość funkcji celu wynosi 16036, dla T = 15 znaleziona wartość funkcji celu dla najlepszego rozwiązania wynosi 15576, dla T = 10 Qmin = 16048, dla T = 5 Qmin = 16008.
Jak widać parametr ten służy do różnicowania procesu poszukiwania najlepszego rozwiązania w algorytmie Tabu search. Drugim parametrem, który zmieniano podczas testowania algorytmu był parametr Alfa. Na poniższym wykresie przedstawiono przebiegi wartości funkcji celu dla różnych wartości parametru Alfa.
![]()
Parametr Alfa jest karą za częste dokonywanie przestawienia pary elemntów w procesie poszukiwania najlepszego rozwiązania. Z powyższego wykresu zauważono, że gdy kara jest mała algorytm szybko zmierza do pewnego minimum, lecz nie jest to rozwiązanie zadowalające. Gdy zwiększono karę do 1500 stwierzono, że algorytm wolniej zmierza do minimum, przy czym w tym przypadku znalezione rozwiązanie jest lepsze niż gdy kara wynosiła 500.
Dalej zwiększając karę zaobserwowano podobny efekt powolnego zmierzania do minimum. W ostatnim przypadku gdy kara wynosi 3000 przyjęto zdecydowanie zbyt małą ilość iteracji. Najlepsze rozwiązanie znaleziono dla kary równej 1500. Dla wszystkich przypadków okres karencji dla danej pary wynosił 5.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() ![]() |