• Nie Znaleziono Wyników

Algorytmy 2 Laboratorium: sortowanie przez kopcowanie, przez zliczanie, i kubełkowe

N/A
N/A
Protected

Academic year: 2021

Share "Algorytmy 2 Laboratorium: sortowanie przez kopcowanie, przez zliczanie, i kubełkowe"

Copied!
6
0
0

Pełen tekst

(1)

Algorytmy 2

Laboratorium: sortowanie przez kopcowanie, przez zliczanie, i kubełkowe

Przemysław Klęsk 11 listopada 2019

1 Cel

Celem zadania jest wykonanie implementacji trzech algorytmów sortującyh i porównanie ich wydajności.

Tymi algorytmami są:

• sortowanie przez kopcowanie (ang. heapsort ),

• sortowanie przez zliczanie (ang. counting sort ),

• sortowanie kubełkowe (ang. bucket sort ),

Pierwszy z wymienionych jest reprezentatnem grupy algorytmów, które sortują bazując na porównaniach liczb parami, i ma złożoność obliczeniową Θ(n log n) — asymptotycznie najlepszą możliwą w ramach tej grupy. Dwa pozostałe algorytmy nie są oparte na porównaniach i są uznawane za algorytmy liniowe co do złożoności, przy czym mają pewne ograniczenia i wymagają spełnienia pewnych założeń odnośnie wejściowych danych. Sortowanie przez zliczanie pozwala sortować tylko liczby całkowite i ma złożoność Θ(n + m), gdzie m to największa możliwa liczba w zbiorze wartości1. Jeżeli parametr m pozostaje ustalo- ny lub skaluje się co najwyżej liniowo wraz z n, to efektywna złożoność obliczeniowa jest liniowa. Z kolei sortowanie kubełkowe pozwala sortować liczby rzeczywiste2(lub dowolne obiekty z kluczami rzeczywisty- mi), ale dla osiągnięcia wydajnego działania wymaga, aby liczby wejściowe były rozłożone jednostajnie w ustalonym przedziale. Przedział ten zostaje podzielony na pewną liczbę kubełków o równych szero- kościach (np. b kubełków), realizowanych za pomocą krótkich list, do których „nadziewane” są liczby w jednym przebiegu po danych. Wynik końcowy powstaje poprzez złączenie kubełków (które muszą być uprzednio także posortowane pewnym zewnętrznym algorytmem, jeżeli zawierają więcej niż 1 element).

Prowadzi to do algorytmu o złożoności obliczeniowej Θ(n + b). Powszechnie przujmuje się b = n jako wybraną liczbę kubełków, ponieważ spełnienie założenia o rozkładzie jednostajnym pozwala oczekiwać,

1lub (w zależności od przyjętego wariantu) rozstęp zbioru wartości

2W implementacjach komputerowych tak naprawdę mamy do dyspozycji zmiennoprzecinkowe typy liczbowe będące podzbiorami zbioru liczb wymiernych. Niemniej, sam algorytm jako taki jest sformułowany dla liczb rzeczywistych.

(2)

że do każdego kubełka trafi średnio 1 liczba. A zatem otrzymujemy złożoność Θ(n + b) = Θ(2n), i tym samym Θ(n).

Należy zaznaczyć, że sortowanie przez kopcowanie może być zrealizowane w miejscu bez nakładów pamięciowych, zaś pozostałe dwa algorytmy wymagają dodatkowej pamięci proporcjonalnej odpowiednio do m i n. Jeżeli chodzi o zewnętrzny algorytm sortujący, pomocniczy dla bucket sort, to często wykorzy- stuje się do tego celu sortowanie przez wstawianie (ang. insertion sort), ale w szczególności może to być także np. heapsort.

2 Instrukcje, wskazówki, podpowiedzi

1. Całość implementacji tego zadania nie wymaga tworzenia nowych klas.

2. Implementacja sortowania przez kopcowanie powinna wykorzystywać kopiec binarny wykonany ja- ko własny kontener przy okazji wcześniejszego zadania laboratoryjnego (np. poprzez dołączenie odpowiedniego pliku nagłówkowego .h). Przy czym wymagane będą pewne rozszerzenia tej imple- mentacji opisane w dalszej części tej instrukcji.

3. Sortowanie przez zliczanie powinno być zrealizowane jako zwykła funkcja, do której jako argumenty przekazane będą: tablica liczb całkowitych (lub ogólniej wskaźnik na taką tablicę), rozmiar tablicy, oraz liczba m określająca rozstęp zbioru wartości. Można przyjąć założenie, że dziedziną dla liczby wejściowych jest zbiór {0, 1, . . . , m − 1}. Typ zwracanego wyniku to void — innymi słowy funkcja ma realizować sortowanie w miejscu.

4. Należy przygotować dwa warianty (przeciążenia) funkcji realizującej sortowanie kubełkowe z roz- różnieniem typu danych wejściowej tablicy:

- wariant przeznaczony tylko do sortowania tablic liczb całkowitych tj. int* array,

- wariant wykorzystujący mechanizm szablonu (template <typename T>) pozwalający sorto- wać tablice obiektów dowolnego typu tj. T* array.

Wariant pierwszy tak naprawdę zawęża możliwości sortowania kubełkowego i ma być zrealizowany jako osobny przypadek tylko na potrzeby niniejszego zadania laboratoryjnego w celu możliwości przeprowadzenia porównania z sortowaniem przez zliczanie (na równych warunkach). W przypadku drugiego wariantu należy jako argumenty (oprócz tablicy oraz liczb n i m) przesłać dodatkowo wskaźniki na dwie funkcje: jedną decydującą o kluczu sortowania w ramach typu T (uwaga: klucz w ogólności zmiennoprzecinkowy), drugą stanowiącą komparator dwóch obiektów typu T (komparator będzie wykorzystany przez pomocniczy algorytm sortujący kubełki).

5. W eksperymentach porównujących wydajność algorytmów sortujących przydatne może być m.in.:

- użycie funkcji kopiującej pamięć memcpy(...) w celu powielenia wejściowych tablic,

(3)

- losowanie liczb z szerokiego zakresu np. poleceniem: ((rand() << 15) + rand()) % m, gdzie modpowiada rozważanemu wcześniej parametrowi m,

- w razie ewentualnej potrzeby zamiany całkowitych liczb losowych na zmiennoprzecinkowe (w przypadku sortowania kubełkowego) można wykonać dzielenie np.: rand() / (double) RAND MAXlub ((rand() << 15) + rand()) / (double) pow(2, 30).

- stworzenie funkcji zwracającej skrótową napisową reprezentację tablicy (w celu późniejszego wypisu na ekran).

3 Rozszerzenie implementacji kopca binarnego

Implementację kopca binarnego wykonaną przy okazji innego zadania laboratoryjnego należy rozszerzyć o następujące elementy:

- dodatkowy konstruktor pozwalający „wstrzyknąć” do środka kopca pewną zaalokowaną na zewnątrz tablicę (poprzez wskaźnik i podanie jej rozmiaru); konstruktor nie będzie więc alokował własnej tablicy, a powinien jedynie dokonać naprawy „wstrzykniętej” tablicy (w miejscu) w celu spełenienia warunku kopca;

- dwa warianty funkcji wykonujące naprawę kopca wg podejść top-down oraz bottom-up (konstruktor z poprzedniego punktu będzie uruchamiał jeden z nich wybrany np. za pomocą argumentu typu bool) — te warianty będą także podlegały sprawdzeniu wydajności w eksperymentach;

- funkcję sort(...) realizująca sortowanie przez kopcowanie w miejscu.

Uwaga: w zależności od przyjętego rozwiązania programistycznego, dwa ostatnie punkty powyżej mo- gą wymagać przekazania jako argument komparatora (wskaźnik na funkcję) lub przeciążenia operatora porównania.

4 Zawartość funkcji main() — dwa eksperymenty

Należy przygotować dwie wersje głównej funkcji programu (np. main ints() i main objects()). Bę- dą one porównywały wydajność sortowania dla odpowiednio: liczb całkowitych (porównywane wszystkie trzy algorytmy) oraz dowolnych obiektów z pewnym kluczem zmiennoprzecinkowym (porównywane tyl- ko heapsort i bucket sort). W obydwu eksperymentach rozmiary sortowanych tablic mają zmieniać się (kolejnymi rzędami wielkości) od 101 aż do 107. Wartość parametru m można ustalić na: 107 dla pierw- szego eksperymentu, oraz 1.0 dla drugiego eksperymentu po uprzednim znormalizowaniu liczb losowych do przedziału [0, 1] (i przechowaniu wyniku w typie double).

(4)

Poniższy listing pokazuje poglądowy schemat pierwszego eksperymentu:

int m a i n _ i n t s () {

srand (0) ;

const int M A X _ O R D E R = 7; // m a k s y m a l n y rzad w i e l k o s c i s o r t o w a n y c h d a n y c h const int m = ( int ) pow (10 , 7) ; // s o r t o w a n e l i c z b y ze z b i o r u {0 , ... , m - 1}

for ( int o = 1; o <= M A X _ O R D E R ; o ++) {

const int n = ( int ) pow (10 , o ) ; // r o z m i a r t a b l i c y z d a n y m i

int * array1 = new int [ n ] ; for ( int i = 0; i < n ; i ++) {

int r a n d _ v a l = ... // tu l o s o w a n i e l i c z b y c a l k o w i t e j array1 [ i ] = r a n d _ v a l ;

}

... // s k r o t o w y w y p i s t a b l i c y do p o s o r t o w a n i a ( np . p e w n a l i c z b a p o c z a t k o w y c h e l e m e n t o w ) int * array2 = new int [ n ] ;

int * array3 = new int [ n ] ;

memcpy ( array2 , array1 , n * sizeof ( int ) ) ; // p i e r w s z a k o p i a memcpy ( array3 , array1 , n * sizeof ( int ) ) ; // d r u g a k o p i a

// s o r t o w a n i e p r z e z z l i c z a n i e ( do w y k o n a n i a w m i e j s c u ) c l o c k _ t t1 = clock () ;

c o u n t i n g _ s o r t ( array1 , n , m ) ; c l o c k _ t t2 = clock () ;

... // w y p i s p o m i a r u c z a s u i s k r o t o w e j p o s t a c i w y n i k o w e j t a b l i c y

// s o r t o w a n i e p r z e z k o p c o w a n i e ( do w y k o n a n i a w m i e j s c u ) t1 = clock () ;

binary_heap < int >* bh = new binary_heap < int >( array2 , n , in t_cmp , true ) ; // k o n s t r u k t o r k o p c a z m o z l i w o s c i a p r z e k a z a n i a z e w n e t r z n e j t a b l i c y ( o s t a t n i a r g u m e n t w s k a z u j e k i e r u n e k n a p r a w y: top - down lub bottom - up )

bh - > sort ( i n t _ c m p ) ; t2 = clock () ;

... // w y p i s p o m i a r u c z a s u i s k r o t o w e j p o s t a c i w y n i k o w e j t a b l i c y

// s o r t o w a n i e k u b e l k o w e ( do w y k o n a n i a w m i e j s c u ) t1 = clock () ;

b u c k e t _ s o r t ( array3 , n , m ) ; // s z c z e g o l n a w e r s j a b u c k e t sort t y l k o dla l i c z b c a l k o w i t y c h t2 = clock () ;

... // w y p i s p o m i a r u c z a s u i s k r o t o w e j p o s t a c i w y n i k o w e j t a b l i c y

... // s p r a w d z e n i e z g o d n o s c i t a b l i c array1 , array2 , a r r a y 3 i w y p i s i n f o r m a c j i o z g o d n o s c i na e k r a n

delete [ ] array1 , array2 , array3 ; }

}

(5)

Poniższy listing pokazuje poglądowy schemat drugiego eksperymentu:

int m a i n _ s o m e _ o b j e c t s () {

const int M A X _ O R D E R = 7; // m a k s y m a l n y rzad w i e l k o s c i s o r t o w a n y c h d a n y c h

const double m _ d o u b l e = ( double ) pow (2 , 30) ; // m i a n o w n i k przy u s t a l a n i u l o s o w e j l i c z b y z m i e n n o p r z e c i n k o w e j

for ( int o = 1; o <= M A X _ O R D E R ; o ++) {

const int n = ( int ) pow (10 , o ) ; // r o z m i a r t a b l i c y z d a n y m i

s o m e _ o b j e c t ** array1 = new s o m e _ o b j e c t * [ n ] ; for ( int i = 0; i < n ; i ++)

{

s o m e _ o b j e c t * so = new s o m e _ o b j e c t () ;

so - > f i e l d _ 1 = (( rand () << 15) + rand () ) / m _ d o u b l e ; // p r z y k l a d o w y s p o s o b w y l o s o w a n i a pola typu d o u b l e ( k t o r e b e d z i e s t a n o w i l o k l u c z s o r t o w a n i a )

so - > f i e l d _ 2 = ’ a ’ + rand () % 26; // p r z y k l a d o w y s p o s o b w y l o s o w a n i a pola typu char array1 [ i ] = so ;

}

... // s k r o t o w y w y p i s t a b l i c y do p o s o r t o w a n i a ( np . p e w n a l i c z b a p o c z a t k o w y c h e l e m e n t o w ) s o m e _ o b j e c t ** array2 = new s o m e _ o b j e c t * [ n ] ;

memcpy ( array2 , array1 , n * sizeof ( s o m e _ o b j e c t *) ) ; // k o p i a

// s o r t o w a n i e p r z e z k o p c o w a n i e c l o c k _ t t1 = clock () ;

binary_heap < s o m e _ o b j e c t * >* bh = new binary_heap < s o m e _ o b j e c t * >( array1 , n , s o m e _ o b j e c t s _ c m p , true ) ; // k o n s t r u k t o r k o p c a z m o z l i w o s c i a p r z e k a z a n i a z e w n e t r z n e j t a b l i c y ( o s t a t n i a r g u m e n t w s k a z u j e k i e r u n e k n a p r a w y: top - down lub bottom - up )

bh - > sort ( s o m e _ o b j e c t s _ c m p ) ; t2 = clock () ;

... // w y p i s p o m i a r u c z a s u i s k r o t o w e j p o s t a c i w y n i k o w e j t a b l i c y

// s o r t o w a n i e k u b e l k o w e t1 = clock () ;

bucket_sort < s o m e _ o b j e c t * >( array2 , n , 1.0 , s o m e _ o b j e c t _ k e y _ d o u b l e , s o m e _ o b j e c t s _ c m p ) ; // t r z e c i a r g u m e n t wskazuje , ze l i c z b y sa z p r z e d z i a l u [0 , 1]

t2 = clock () ;

... // w y p i s p o m i a r u c z a s u i s k r o t o w e j p o s t a c i w y n i k o w e j t a b l i c y

... // s p r a w d z e n i e z g o d n o s c i t a b l i c array1 , a r r a y 2 i w y p i s i n f o r m a c j i o z g o d n o s c i na e k r a n

delete [ ] array1 , array2 ; }

}

5 Sprawdzenie antyplagiatowe — przygotowanie wiadomości e-mail do wysłania

1. Kod źródłowy programu po sprawdzeniu przez prowadzącego zajęcia laboratoryjne musi zostać przesłany na adres algo2@zut.edu.pl.

2. Plik z kodem źródłowym musi mieć nazwę wg schematu: nr albumu.algo2.nr lab.main.c (plik

(6)

może mieć rozszerzenie .c lub .cpp). Przykład: 123456.algo2.lab06.main.c (szóste zadanie laboratoryjne studenta o numerze albumu 123456). Jeżeli kod źródłowy programu składa się z wielu plików, to należy stworzyć jeden plik, umieszczając w nim kody wszystkich plików składowych.

3. Plik musi zostać wysłany z poczty ZUT (zut.edu.pl).

4. Temat maila musi mieć postać: ALGO2 IS1 XXXY LAB06, gdzie XXXY to numer grupy (np. ALGO2 IS1 210C LAB06).

5. W pierwszych trzech liniach pliku z kodem źródłowym w komentarzach muszą znaleźć się:

- informacja identyczna z zamieszczoną w temacie maila (linia 1), - imię i nazwisko autora (linia 2),

- adres e-mail (linia 3).

6. Mail nie może zawierać żadnej treści (tylko załącznik).

7. W razie wykrycia plagiatu, wszytkie uwikłane osoby otrzymają za dane zadanie ocenę 0 punktów (co jest gorsze niż ocena 2 w skali {2, 3, 3.5, 4, 4.5, 5}).

Cytaty

Powiązane dokumenty

Gdy tak się stanie, w ciągu wynikowym będziemy mieli scalone dwa ciągi o liczbie wyrazów będącej sumą elementów ciągów scalanych. Po scaleniu dwóch pierwszych

Sposób obliczania czasu został przedstawiony na przykładzie sortowania przez selekcję, (przez wstawianie), sortowania

1/2 Francja Grecja Albania Egipt Cypr Hiszpania Belgia Dania. 1/3 Francja Albania Grecja Egipt Cypr Hiszpania

Oprócz sortowania przez łączenie zstępujące typu „dziel i rządź” istnieje sortowanie wstępujące (nierekurencyjne) typu „łącz i zwyciężaj, które jest

Zastosuj kod programu genTest.cpp do wygenerowania serii liczb wejsciowych. Za pomoca kodu sortTest.cpp utw´orz wzorcowy output posortowanych serii, kod u˙zywa funkcji

Sortowanie w miejscu nie wymaga wykorzystania dodatkowej pamięci zależnej od ilości sortowanych danych, lecz tylko pewną stałą jej ilość.. Sortowanie stabilne zapewnia, iż

Sortowanie takiego pliku kart omawianą metodą polega na tym, że gracz stopniowo dokłada karty do uporządkowanej części kart (początkowo zawierającej jedną kartę)

• Ostatnim krokiem jest zamiana miejscami elementów tablicy: pierwszego i poprzedzającego wskazywany przez zmienną granica – chcemy, aby element osiowy był