Laboratorium 2 Badanie złożoności obliczeniowej algorytmów na przykładzie sortowania Cel ćwiczenia Celem ćwiczenia jest praktyczne zapoznanie się z pojęciem złożoności obliczeniowej na przy kładzie wybranych algorytmów sortowania: bąbelkowego, przez wstawianie, przez selekcję, Shella oraz sortowania szybkiego. Dodatkowo badana może być stabilność i wykorzystanie stosu systemowego przez algorytmy. Struktura programu Wykorzystywany w ćwiczeniu program aisd2 zawiera implementacje większości omawianych na wykładzie metod sortowania, przy czym niektóre występują w kilku wersjach. Po wykonaniu każdego algorytmu program wypisuje następujące informacje: posortowany ciąg wejściowy, czas działania algorytmu w mikrosekundach, liczbę przypisań kluczy (zamiana kluczy miejscami wymaga trzech przypisań), liczbę porównań kluczy, liczbę porównań indeksów tablicy (sprawdzających np. czy osiągnięty został początek lub koniec tablicy), maksymalną głębokość stosu systemowego wyłącznie w funkcjach zaimplementowanych rekurencyjnie. Choć kod programu jest rozbity na 8 plików, wykonanie ćwiczenia wymaga modyfikacji tylko kilku fragmentów tych plików. Oczywiście po wprowadzeniu dowolnej zmiany należy ponownie skompilować cały projekt, wpisując make clean; make. Oto elementy kodu, które będą modyfikowane w ćwiczeniu: 1. W funkcji main (pliku main.cpp) należy zdefiniować następujące ustawienia: a. Czy po posortowaniu ciąg ma zostać wyświetlony (zmienna Ekran). Pozwala ocenić poprawność algorytmu, co jest konieczne w zadaniach wymagających modyfikacji kodu. Poza tym dla danych o dwóch kluczach (typ BiData) pozwala oszacować stabilność algorytmu. W pozostałych przypadkach sugeruje się nadanie zmiennej Ekran wartości false, zwłaszcza w przypadku dużych zbiorów danych. b. Ziarno generatora liczb pseudolosowych Srand. Wykonywanie ćwiczenia należy rozpocząć od nadania mu wartości równej swojemu numerowi indeksu. Należy pamiętać, że kolejne uruchomienia programu z niezmienionym ziarnem dają w efekcie identyczne sekwencje pseudolosowe. c. Liczność zbioru danych. Sensowny zakres zmian tego parametru jest silnie uzależniony od klasy złożoności badanego algorytmu. d. FactorM długość ciągu wejściowego, przy której algorytm QuickSortFactorM przestaje wywoływać się rekurencyjnie. Ciągi takie są sortowane przez wstawianie. To samo dotyczy algorytmu QuickSortMediana. Pozostałe funkcje sortujące nie korzystają z tego parametru. 2. W pliku sortowanie.h zdefiniowane są wszystkie funkcje sortujące oraz pomocnicze. Zaimplementowa ne zostały następujące metody sortowania: a. przez selekcję (wybór), 1/9
b. pięć wersji algorytmu sortowania przez wstawianie w kolejności od najprostszej (nieadaptacyjnej) aż do zawierającej najwięcej adaptacji, c. sortowanie bąbelkowe, d. sortowanie Shella, e. trzy wersje algorytmu sortowania szybkiego: podstawowa (QuickSort), sortująca niewielkie tablice przez wstawianie (QuickSortFactorM) oraz wersja druga rozbudowana o szacowanie mediany da nych za pomocą mediany z trzech elementów (QuickSortMediana). Każda funkcja rozpoczyna się od przywrócenia początkowego uporządkowania zbioru danych i inicjali zacji zmiennych pomocniczych a kończy się sporządzeniem podsumowania. Te czysto techniczne czyn ności z reguły nie są interesujące z punktu widzenia wykonującego ćwiczenie. Właściwy algorytm sor tujący jest wyróżniony w kodzie i zajmuje z reguły nie więcej niż 10 wierszy. Część zadań polega na modyfikacji wybra - nych algorytmów. Kod zawiera również wywołania makrodefinicji odpowiedzialnych za rejestrację kluczowych operacji, np. POROWNANIE_KLUCZY. Poleceń tych nie należy usuwać, nawet jeśli nie zamierzamy rejestrować tych operacji. Jeśli natomiast implementujemy własną wersję algorytmu, przy każdej operacji typu porównanie czy przypisanie należy zadbać o wywołanie tych makrodefinicji. 3. Również w pliku sortowanie.h zdefiniowane są funkcje BadanieAlgorytmów (osobno dla danych o kluczach całkowitoliczbowych AlgorytmySortowaniaINT oraz dla danych o dwóch kluczach AlgorytmySortowaniaBI). Domyślnie wywołują one kolejno wszystkie dostępne funkcje sortujące. Należy pamiętać, że z uwagi na duże rozbieżności w złożoności czasowej badanych algorytmów, przy dużych zbiorach danych należy wykomentować wywołania najmniej wydajnych algorytmów, aby nie spowalniały wykonania całego programu. 4. Plik aisde_macros.h zawiera m.in. makrodefinicje służące zliczaniu kluczowych operacji (patrz powyż szy punkt Cel ćwiczenia). Należy zdawać sobie sprawę, że samo zliczanie operacji przypisania, porów nania czy wzrostu stosu jest również operacją, a zatem zwiększa czas wykonywania programu. Dlatego jeśli celem danego zadania jest wyznaczenie czasu działania algorytmu a nie (tylko) liczby porównań i przypisań, należy zrezygnować z rejestrowania tych operacji. Można tego dokonać, komentując wiersze #define REJE- STRUJ_... na początku pliku. W tym przypadku makrodefinicjom typu POROWNANIE_KLUCZY nie zostaną przypisane żadne instrukcje. Przebieg ćwiczenia Funkcja main (plik main.cpp, Rys. 1) zawiera następujące parametry zadeklarowane w programie jako zmienne globalne: Ekran zarządza wypisywaniem danych na ekran), Srand ustawia warunki początkowe dla generatora liczb pseudolosowych (w trakcie ćwiczenia każdy student przypisuje tej zmiennej swój numer albumu), FactorM ustawia współczynnik przełączenia algorytmów w sortowaniu metodą QuickSort, Licznosc ustawia liczność zbioru do sortowania. int main(){ Ekran=false; Srand=10; //tu wpisz numer swojego albumu FactorM=20; Licznosc=1234; try{ InitINT(); SortInt->BadanieAlgorytmow(); } catch (Except e){ Error(e); } getchar(); CleanUp(); return 1; Rys. 1. Podstawowe parametry symulacji kontrolowane przez użytkownika (plik main.cpp) 2/9
Dla wskazanych przez prowadzącego algorytmów sortowania (uzupełnić w notatniku) należy przystosować kod przykładowego programu do zaplanowanych pomiarów i symulacji. W zadaniach wymagających modyfikacji algorytmów konieczna jest weryfikacja poprawności działania własnych metod. Przykładowe zadania do wykonania na ćwiczeniu (prowadzący może podać inne): 1. Zbadać złożoność obliczeniową wskazanych algorytmów sortowania, wziąwszy pod uwagę początkowe uporządkowanie sortowanego zbioru danych. 2. Wskazać, zaimplementować i przetestować możliwe rozwiązania prowadzące do poprawy wydajności badanych algorytmów sortowania. 3. Określić stabilność wskazanych algorytmów sortowania. Przeanalizować możliwość stabilizacji badanych w ćwiczeniu niestabilnych algorytmów sortowania. 4. Napisać procedury generujące różne rozkłady statystyczne danych do sortowania. Następnie sprawdzić, jak wydajność wybranych algorytmów sortowania zależy od rozkładu danych wejściowych. 5. Określić zależność wydajności algorytmu sortowania metodą Shella od rodzaju zastosowanego ciągu. Oprócz ciągu już używanego w programie należy zaimplementować przynaj mniej dwa ciągi: znane z literatury lub własne. W tym celu wystarczy stworzyć własną klasę dziedzi czącą po klasie GeneratorCiagow, co w praktyce wymaga jedynie zdefiniowania funkcji wirtualnej DodajElementOIndeksie(int). Zaczątek takiej klasy, nazwany MojGeneratorCiagu, znajduje się na końcu pliku generator_shella.h. Obiekt własnej klasy będzie następnie wykorzystany jako zmienna generator w funkcji AlgorytmySortowania<OBJ>::Shella (patrz plik sortowanie.h). 6. Eksperymentalnie dobrać optymalną wartość FactorM (dla QuickSortFactorM i QuickSortMediana). 7. W funkcji QuickSortFactorM podtablice o rozmiarze mniejszym niż pewna założona wartość M nie są już dalej rekurencyjnie dzielone funkcją Rozdziel, lecz sortowane przez wstawianie. Możliwe jest jednak nieco inne podejście, w którym po dojściu do określonego rozmiaru podtablicy następuje powrót na wyższy poziom rekurencji. Po zakończeniu funkcji QuickSortFactorM cała tablica pozostaje więc wciąż nieposortowana, choć jest podzielona na odcinki, z których każdy zawiera elementy większe od odcinka poprzedniego. Ostatnim krokiem musi więc być posortowanie całej tablicy innym algorytmem, np. przez wstawianie. Proszę zaimplementować taką wersję algorytmu Quicksort i sprawdzić popraw - ność implementacji sortując niewielką tablicę. Następnie proszę porównać wydajność tej metody z QuickSort w wersji najprostszej oraz QuickSortFactorM dla różnych wielkości zbiorów danych i różnych wartości M. 8. Sercem algorytmu sortowania szybkiego jest funkcja Rozdziel, przenosząca do odpowiednich części tablicy elementy mniejsze i większe od rozdzielającego. Wszystkie wersje sortowania szybkie go użyte w programie wykorzystują książkową implementację tej funkcji, w której tablica danych jest analizowana jednocześnie z dwóch stron. Jeśli znakami + i oznaczymy klucze, o których wiemy, że są odpowiednio większe i mniejsze od rozdzielającego, znakiem? klucze jeszcze nie przeanalizowane, zaś literą R element rozdzielający, to stan tabeli na pewnym etapie wykonania funkcji Rozdziel można zapisać symbolicznie w taki sposób, jak na Rys. 2 a): 3/9
a) b)??? + + + R + + +??? R indeks: 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 c) d) + + +?? R + + +?? R 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 Rys. 2. Stan tablicy danych podczas sortowania szybkiego: a) implementacja użyta w ćwiczeniu, b) i kolejne proponowana implementacja, c) gdy kolejny analizowany element (indeks 6) okaże się mniejszy od rozdzielającego, powinien zostać zamieniony z pierwszym z elementów większych od rozdzielającego (indeks 3), d) stan po zamianie W książce T. Cormen et al. Wprowadzenie do algorytmów opisana jest inna prostsza! implementacja tej funkcji. Tabela jest analizowana wyłącznie od lewej do prawej strony, a stan tabeli można zapi - sać jak na Rys. 2 b). Jeśli kolejny analizowany klucz pierwszy oznaczony znakiem zapytania okaże się większy od elementu rozdzielającego, to znaczy, że znajduje się po właściwej stronie przeanalizow - anego odcinka tabeli, tzn. wśród dużych kluczy. Jeśli jednak okaże się mniejszy, to musi zostać przeniesiony do części tabeli zawierającej małe klucze. Najprościej dokonać tego zamieniając go miejsca - mi z pierwszym z dużych kluczy. Stan przed tą zamianą i po niej widać odpowiednio na Rys. 2 c) i d). Po dojściu do końca tablicy element rozdzielający jest wstawiany na swoje miejsce podobnie, jak w dotychczasowej implementacji. Proszę zaimplementować opisaną wersję sortowania szybkiego, zweryfikować jej poprawność, a następnie porównać jej wydajność z podstawową wersją algorytmu. 9. Funkcja QuickSort wywołuje się rekurencyjnie najpierw dla lewej części tablicy (zawierającej elementy mniejsze od rozdzielającego), a następnie dla jej prawej części. Jedną z wad tej standardowej imple - mentacji jest ryzyko silnego rozrostu stosu systemowego, co w skrajnych przypadkach może doprowa - dzić do naruszenia ochrony pamięci i przedwczesnego zakończenia programu. Na podstawie analizy kodu należy stwierdzić, jakie wstępne uporządkowanie n-elementowej tablicy danych grozi rozrostem stosu do głębokości porównywalnej z n. Następnie należy tak zmodyfikować funkcję Quicksort, by głębokość stosu była w przybliżeniu niezależna od wstępnego uporządkowania danych. Należy wy - kreślić zależność głębokości stosu w funkcji n dla danych losowych, uporządkowanych rosnąco i malejąco dla obu wersji Quicksort oryginalnej i zmodyfikowanej. Jakimi funkcjami można przybliżyć tę zależność? Dla dużych zbiorów danych sprawdzić, jak wprowadzona modyfikacja wpływa na czas wykonania programu. Analiza wyników, wnioski, sprawozdanie Na ocenę z ćwiczenia bezpośrednio wpływają: 1. Zawartość notatnika z ćwiczenia (w wersji papierowej) Na początku ćwiczenia należy wydrukować umieszczony na końcu niniejszej instrukcji Notatnik. Będzie on stanowił bieżący zapis przebiegu ćwiczenia i wyników pracy. 2. Zawartość oddanego w terminie sprawozdania (wyłącznie w postaci elektronicznej w formacie PDF). Sprawozdanie powinno zawierać następujące elementy: a) cel ćwiczenia (własnymi słowami), b) w przypadku zadań wymagających modyfikacji kodu listing zmienionej funkcji ( z wyraźnym wyróżnieniem własnych modyfikacji) wraz z opisem zmian i ich uzasadnieniem, 4/9
c) stosowne wykresy, np. wykresy czasów wykonania programu w zależności od wersji badanego algoryt - mu w funkcji długości sortowanych danych, wykresy dynamiczne sortowania (patrz ostatni punkt Uwag końcowych i Rys. 3.) itp., d) podsumowanie ćwiczenia czyli wnioski: konkretne, na temat, samodzielne, twórcze. 3. Samodzielność i pomysłowość wykonania ćwiczenia. Sprawozdanie powinno zostać nadesłane pocztą elektroniczną w terminie podanym przez prowadzącego zajęcia (zazwyczaj 7 dni). Pod rygorem nie sprawdzania sprawozdania dodatkowo niezbędne jest wypełnienie przez studentów następujących wymogów formalnych: jedyny przyjmowany format to PDF, przy czym tekst nie może być wklejony jako rysunek, nazwa pliku zawierającego sprawozdanie musi mieć formę AISDE_2_Nazwisko_Imię.pdf. temat maila ze sprawozdaniem musi pasować do wzorca: AISDE_2_Nazwisko_Imię. Wymagania do kolokwium wstępnego: znajomość badanych algorytmów sortowania, znajomość przykładowego programu (pełne kody źródłowe zamieszczone są na internetowej stronie przedmiotu AISDE http://vlsi.imio.pw.edu.pl/aisde). Uwagi końcowe Dla zapewnienia identycznych warunków pracy algorytmów sortujących w ramach danej serii pomiaro - wej, do sortowania różnymi metodami powinny być wykorzystywane w zawsze te same dane wejściowe. Wszystkie pomiary czasu powinny być wykonywane na tej samej maszynie a przynajmniej na maszynach o identycznych parametrach, np. na dwóch różnych komputerach laboratoryjnych. Należy wziąć to pod uwagę, planując wykonanie części ćwiczenia w domu. Po wygenerowaniu pomiarowej serii danych, dane te są przechowywane w zmiennej Dane_kopia. Do sortowania wykorzystywane są dane przechowywane w zmiennej Dane. Prywatna metoda OdswiezDane kopiuje dane ze zmiennej Dane_kopia do zmiennej Dane. Jednym z możliwych sposobów wizualizacji procesu sortowania są wykresy dynamiczne. Na osi rzędnych znajdują się wartości kluczy, zaś na osi odciętych ich pozycje. Wykresy dynamiczne dla kilku początkowych etapów sortowania szybkiego przedstawiono na Rys. 3. a b c d e Rys. 3. Wykresy dynamiczne sortowania metodą QuickSort Literatura 1. R. Sedgewick Algorytmy w C++, Wydawnictwo RM 1999 2. A. Drozdek C++ algorytmy i struktury danych, Wydawnictwo Helion 2004 3. T. Cormen et al. Wprowadzenie do algorytmów, Wyd. Naukowe PWN 2013 4. J. Grębosz Symfonia C++ Wydawnictwo Oficyna Kallimach 1997 5. J. Grębosz Pasja C++ Wydawnictwo Oficyna Kallimach 1997 5/9
Dodatek struktura programu Klasa AISD implementuje niezbędne metody, służące do pomiarów czasu, wydruku raportów i komunikacji z użytkownikiem. Zostały one zaimplementowane jako metody szablo nowej klasy wirtualnej AlgorytmySortowania (Rys. 4), pochodnej od używanej w ćwiczeniach 2 i 3 klasy AISD (Rys. 5). template <class OBJ> class AlgorytmySortowania:public AISD<OBJ>{ protected: int Rozdziel(int L=0, int R=0); // quicksort (QS) podział zbioru void QuickSortRekur(int L,int R); // QS standardowy void QuickSortRekurFactorM(int L,int R,int M); // QS + poprawka na małe zbiory void QuickSortRekurMediana(int L,int R,int M); // QS + mediana z 3 void PrzezWstawianie2QS(int L, int R); // sortowanie przez wstawianie wykorzystywane jako końcowy etap QS int BisekcjaDoWstawiania(int L, int R, OBJ Element); // algorytm bisekcji do sortowania przez wstawianie AlgorytmySortowania(int LiczZbior):AISD<OBJ>(LiczZbior){ ~AlgorytmySortowania(){ Parametry* PrzezSelekcje (int L=0, int R=0); Parametry* PrzezWstawianie1(int L=0, int R=0); // podstawowa wersja algorytmu Parametry* PrzezWstawianie2(int L=0, int R=0); // algorytm z jedną poprawką Parametry* PrzezWstawianie3(int L=0, int R=0); // algorytm z dwiema poprawkami Parametry* PrzezWstawianie4(int L=0, int R=0); // algorytm z dwiema poprawkami i poprawką wykładową Parametry* PrzezWstawianie5(int L=0, int R=0); // algorytm z trzema poprawkami i poprawką wykładową Parametry* Babelkowe (int L=0, int R=0); Parametry* Shella (int L=0, int R=0); Parametry* QuickSort (int L, int R); Parametry* QuickSortFactorM(int L, int R, int M); Parametry* QuickSortMediana(int L, int R, int M); Rys. 4. Funkcje sortujące (publiczne, u dołu) i pomocnicze (chronione, u góry) plik sortowanie.h template <class OBJ> class AISD{ protected: OBJ *Dane; OBJ *DaneKopia; int LicznoscZbioru; Parametry ZA; //ZlozonoscAlgorytmu - Parametry void OdswiezDane(); // przepisuje Dane_kopia do Dane void ZA_Init(); // Inicjalizacja struktury danych ZA long PobierzCzas(); inline void Zamien(OBJ &A,OBJ &B); // zamienia wartosci argumentow A i B inline void ZamienWarunkowo(OBJ &A,OBJ &B); // zamienia wartosci argumentow gdy B<A AISD(int); ~AISD(); OBJ *GetDane(); OBJ *GetDaneKopia(); int GetLicznosc(); void Raport(char*); void virtual ZapiszStanDoPliku(char*){ // void virtual WczytajDaneZPliku(char*){ //implementacja IO zalezna od OBJ void virtual WypiszStanNaEkran(bool){ // void virtual BadanieAlgorytmow(){ // Rys. 5. Funkcje zapewniające inicjalizację struktur, komunikację z użytkownikiem, pomiar czasu itp. (plik aisd.h) Klasa AlgorytmySortowania składa się z części publicznej (public) i chronionej (protected). W publicznej części klasy szablonowej AlgorytmySortowania zadeklarowane są metody i zmienne bezpośrednio dostępne dla użytkownika. Należą do nich wszystkie metody implementujące poszczególne algorytmy sortowania: W chronionej (protected) części klasy szablonowej AlgorytmySortowania zadeklarowano metody pomocnicze dla zaimplementowanych algorytmów sortujących. Są to metody: OdswiezDane, Rozdziel, (wykorzystywana przez algorytmy QuickSort), QuickSortRekur, PobierzCzas, QuickSortRekurFactorM, QuickSortRekurMediana, PrzezWstawianie2QS. Dla zapewnienia integralności danych Dane, ich wywołanie jest możliwe jedynie z wnętrza obiektów tej samej klasy i klas pochodnych. Każda z metod sortowania na bieżąco aktualizuje informacje prze - chowywane w strukturze kontrolnej ZlozAlg odziedziczonej po klasie macierzystej AISD. Po zakończeniu pracy każda metoda sortowania zwraca wskaźnik na strukturę kontrolną ZlozAlg (Rys. 6): typedef struct{ unsigned long CzWyk; // czas wykonania algorytmu unsigned long GlSto; // bieżąca głębokość stosu unsigned long GlStoMax;// maksymalna głębokość stosu unsigned long LiZam; // liczba zamian danych unsigned long LiPor; // liczba porównań danych } Parametry; Rys. 6. Struktura Parametry, w której rejestrowane są kluczowe operacje sortowania (plik aisd.h) 6/9
Każda z metod sortujących domyślnie zakłada, że dane do posortowania umieszczone są w tablicy wskazywanej przez Dane: prywatny składnik szablonu klasy macierzystej AISD, inicjalizowany w czasie pracy jej konstruktora. Argumentami wywołania poszczególnych metod sortowania są numery pierwszego ( L) i ostatniego (R) elementu początkowego zbioru danych do posortowania. Obecność tych parametrów w wywołaniu metody umożliwia posortowanie całości lub wybranego fragmentu danych. Przy wywołaniu metod sortowania bez poda - nia argumentów domyślnie przyjmowane jest sortowanie pełnego zbioru danych o liczności LicznośćZbioru (chroniony składnik klasy AISD). W publicznej części klasy AISD oprócz konstruktora i destruktora zaimplementowano wirtualne metody umożliwiające operacje na plikach z danymi ( ZapiszStanDoPliku i WczytajDaneZPliku), a także wyświetlenie tych danych (WypiszStanNaEkran). Ich argumenty to odpowiednio: nazwa pliku do odczytu/zapisu danych i zmienna typu bool decydująca o wypisaniu lub nie wypisaniu danych na ekran. Zdefiniowanie klas AISD i AlgorytmySortowania w postaci szablonów daje możliwość zastosowania ich do operacji na różnych, w tym również złożonych typach danych określonych parametrem OBJ szablonu. Wymaga to jedynie określenia typu tych danych (np. w postaci klasy, struktury, tablicy, listy etc.) i odpowiedniego przeciążenia związanych z nim operatorów: porównania ( < i > ) oraz przypisania ( = ). AlgorytmySortowania to szablon klasy wirtualnej. Nie można więc utworzyć takiego obiektu. Niezbędne jest więc zdefiniowanie klasy potomnej posiadającej własne wersje metod wirtualnych WypiszStanNaEkran, ZapiszStanDoPliku i WczytajDaneZPliku. Dla potrzeb ćwiczenia utworzono klasy AlgorytmySortowaniaINT i AlgorytmySortowaniaBI, pochodne szablonu klasy AlgorytmySortowania, dostosowane do danych typu int i BiData (Rys. 7). class AlgorytmySortowaniaINT:public AlgorytmySortowania<int>{ AlgorytmySortowaniaINT(int LiczZbior):AlgorytmySortowania<int>(LiczZbior){ ~AlgorytmySortowaniaINT(){ void ZapiszStanDoPliku(const std::string &); void WczytajDaneZPliku(const std::string &); void WypiszStanNaEkran(bool); void BadanieAlgorytmow(); class AlgorytmySortowaniaBI:public AlgorytmySortowania<struct BiData>{ AlgorytmySortowaniaBI(int LiczZbior):AlgorytmySortowania<struct BiData>(LiczZbior){ ~AlgorytmySortowaniaBI(){ void ZapiszStanDoPliku(const std::string &); void WczytajDaneZPliku(const std::string &); void WypiszStanNaEkran(bool); void BadanieAlgorytmow(); Rys. 7. Klasy wyprowadzone z AlgorytmySortowania wraz z implementacjami funkcji wirtualnych (plik sortowanie.h) Przykładową klasę BiData, wykorzystaną jako argument szablonu klasy AlgorytmySortowania, przedstawiono na Rys. 8. Klasa ta jest przeznaczona do badania stabilności algorytmów sortowania. class BiData{ int i; // pole danych: klucz 1 char str[16]; // pole danych: klucz 2 BiData(); // konstruktor BiData(int i, char *str); // konstruktor void Set(int i,char *str); int GetI(); char *GetStr(); bool operator<(bidata Dane); // przeciążony operator porównania bool operator>(bidata Dane); // przeciążony operator porównania BiData operator=(bidata Dane); // przeciążony operator przypisania int operator=(int Dane); // przeciążony operator przypisania char* operator=(char *Dane); // przeciążony operator przypisania Rys. 8. Klasa BiData o dwóch kluczach: int i char* (plik dane.h). Zwraca uwagę przeciążenie operatora przypisania W programie wykorzystywanym podczas ćwiczenia zaimplementowano przykładowe algorytmy sortowania dla danych typów int i BiData. Wykorzystano tu odpowiednio obiekty SortINT i SortBI. SortINT to obiekt klasy AlgorytmySortowaniaINT, utworzonej z szablonu AlgorytmySortowania dla podstawowego typu danych int. SortBI to obiekt klasy AlgorytmySortowaniaBI, utworzonej z szablonu AlgorytmySortowania dla złożonego typu 7/9
danych BiData. Obiekty SortINT i SortBI są tworzone, inicjalizowane i wypełniane danymi w funkcjach inicjalizujących: InitINT i InitBI. Losowe dane do posortowania są generowane przez funkcje Wypelnij_INT1 i Wypelnij_BI1 (Rys. 9). void WypelnijINT_1(){ int i; for(i=0;i<sortint->getlicznosc();i++) SortINT->GetDaneKopia()[i]=(int)(rand()*(double)SortINT->GetLicznosc()/(double)RAND_MAX); memcpy(sortint->getdane(),sortint->getdanekopia(),sortint->getlicznosc()*sizeof(int)); } void WypelnijBI_1(){ int i; char (*stringi)[16]=new char[16][16]; strcpy(stringi[0],"a");... strcpy(stringi[15],"s"); for(i=0;i<sortbi->getlicznosc();i++){ SortBI->GetDaneKopia()[i]=(int)(rand()*(double)SortBI->GetLicznosc()/(double)RAND_MAX); SortBI->GetDaneKopia()[i]=stringi[rand()%15]; } memcpy(sortbi GetDane(), SortBI GetDaneKopia(), SortBI->GetLicznosc()*sizeof(BiData)); delete stringi; } Rys. 9. Przykład losowej inicjalizacji kluczy numerycznych i tekstowych obiektu klasy BiData (plik main.cpp) Opracowanie Weryfikacja dr inż. Grzegorz Janczyk, dr inż Dominik Kasprowicz {janczyk, dkasprow}@imio.pw.edu.pl dr inż. Adam Wojtasik aw@imio.pw.edu.pl 8/9
Algorytmy i Struktury Danych NOTATNIK AISDE LAB 2 Imię i nazwisko, Grupa dziekańska Nr Indeksu Data wykonania ćwiczenia Data oddania protokołu Punkty do wykonania 1 2 3 4 5 6 7 8a 8b 9 10 Sortowanie przez selekcję Sortowanie przez wstawianie 1 Sortowanie przez wstawianie 2 Sortowanie przez wstawianie 3 Sortowanie przez wstawianie 4 Algorytmy do zbadania Sortowanie przez wstawianie 5 Sortowanie bąbelkowe Sortowanie Shella Sortowanie quick sort Sortowanie quick sort factor-m Sortowanie quick sort - mediana Algorytm sortowania Stabilność Złożoność Największa liczność zbioru testującego Uwagi NOTATNIK NALEŻY ODDAĆ PROWADZĄCEMU ZAJĘCIA W CHWILI ZAKOŃCZENIA ĆWICZENIA