PROBLEM SORTOWANIA STABILNEGO NA PRZYKŁADZIE JĘZYKA PHP 5 PROBLEM OF STABLE SORTING ON THE PHP5 EXAMPLE



Podobne dokumenty
Algorytmy i struktury danych. Wykład 4

ARYTMETYKA BINARNA. Dziesiątkowy system pozycyjny nie jest jedynym sposobem kodowania liczb z jakim mamy na co dzień do czynienia.

Samodzielnie wykonaj następujące operacje: 13 / 2 = 30 / 5 = 73 / 15 = 15 / 23 = 13 % 2 = 30 % 5 = 73 % 15 = 15 % 23 =

Strategia "dziel i zwyciężaj"

Podstawowe algorytmy i ich implementacje w C. Wykład 9

4. Funkcje. Przykłady

Metody numeryczne Technika obliczeniowa i symulacyjna Sem. 2, EiT, 2014/2015

Struktury Danych i Złożoność Obliczeniowa

LABORATORIUM PROCESORY SYGNAŁOWE W AUTOMATYCE PRZEMYSŁOWEJ. Zasady arytmetyki stałoprzecinkowej oraz operacji arytmetycznych w formatach Q

Kod U2 Opracował: Andrzej Nowak

2 Arytmetyka. d r 2 r + d r 1 2 r 1...d d 0 2 0,

Pętle. Dodał Administrator niedziela, 14 marzec :27

Analiza algorytmów zadania podstawowe

Luty 2001 Algorytmy (7) 2000/2001

Podstawy Programowania C++

DYDAKTYKA ZAGADNIENIA CYFROWE ZAGADNIENIA CYFROWE

Arytmetyka komputera. Na podstawie podręcznika Urządzenia techniki komputerowej Tomasza Marciniuka. Opracował: Kamil Kowalski klasa III TI

Rekurencja. Przykład. Rozważmy ciąg

Zadania do wykonania. Rozwiązując poniższe zadania użyj pętlę for.

Operatory AND, OR, NOT, XOR Opracował: Andrzej Nowak Bibliografia:

operacje porównania, a jeśli jest to konieczne ze względu na złe uporządkowanie porównywanych liczb zmieniamy ich kolejność, czyli przestawiamy je.

Wstęp do informatyki- wykład 1 Systemy liczbowe

Programowanie w VB Proste algorytmy sortowania

INFORMATYKA SORTOWANIE DANYCH.

Kod uzupełnień do dwóch jest najczęściej stosowanym systemem zapisu liczb ujemnych wśród systemów binarnych.

Algorytmy sortujące. sortowanie kubełkowe, sortowanie grzebieniowe

1.1. Pozycyjne systemy liczbowe

Podstawowe operacje arytmetyczne i logiczne dla liczb binarnych

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Przykłady zastosowań funkcji tekstowych w arkuszu kalkulacyjnym

Podstawy Informatyki

Zadanie 1. Zmiana systemów. Zadanie 2. Szyfr Cezara. Zadanie 3. Czy liczba jest doskonała. Zadanie 4. Rozkład liczby na czynniki pierwsze Zadanie 5.

Złożoność obliczeniowa zadania, zestaw 2

Algorytmy i struktury danych Sortowanie IS/IO, WIMiIP

WHILE (wyrażenie) instrukcja;

Efektywna metoda sortowania sortowanie przez scalanie

WHILE (wyrażenie) instrukcja;

DZIAŁANIA NA UŁAMKACH DZIESIĘTNYCH.

Wstęp do Informatyki

Systemy zapisu liczb.

Algorytmy sortujące i wyszukujące

LISTA 1 ZADANIE 1 a) 41 x =5 podnosimy obustronnie do kwadratu i otrzymujemy: 41 x =5 x 5 x przechodzimy na system dziesiętny: 4x 1 1=25 4x =24

METODY KOMPUTEROWE W OBLICZENIACH INŻYNIERSKICH

1. Operacje logiczne A B A OR B

Zapis liczb binarnych ze znakiem

SCHEMAT ROZWIĄZANIA ZADANIA OPTYMALIZACJI PRZY POMOCY ALGORYTMU GENETYCZNEGO

Wielkości liczbowe. Wykład z Podstaw Informatyki dla I roku BO. Piotr Mika

Wstęp do informatyki- wykład 2

ARCHITEKRURA KOMPUTERÓW Kodowanie liczb ze znakiem

wagi cyfry pozycje

Dzielenie sieci na podsieci

Definicja. Ciąg wejściowy: Funkcja uporządkowująca: Sortowanie polega na: a 1, a 2,, a n-1, a n. f(a 1 ) f(a 2 ) f(a n )

Sortowanie przez wstawianie Insertion Sort

Zaawansowane algorytmy i struktury danych

Wielkości liczbowe. Wykład z Podstaw Informatyki. Piotr Mika

Podstawy Informatyki dla Nauczyciela

Wstęp do Informatyki zadania ze złożoności obliczeniowej z rozwiązaniami

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

Stan wysoki (H) i stan niski (L)

Wykład 2. Informatyka Stosowana. 9 października Informatyka Stosowana Wykład 2 9 października / 42

Rekurencja (rekursja)

Wykład 2. Informatyka Stosowana. 10 października Informatyka Stosowana Wykład 2 10 października / 42

Algorytmy i struktury danych. Co dziś? Tytułem przypomnienia metoda dziel i zwyciężaj. Wykład VIII Elementarne techniki algorytmiczne

Metoda znak-moduł (ZM)

ARCHITEKTURA KOMPUTERÓW Liczby zmiennoprzecinkowe

Sortowanie. LABORKA Piotr Ciskowski

Technologie Informacyjne

6. Pętle while. Przykłady

Wprowadzania liczb. Aby uniknąć wprowadzania ułamka jako daty, należy poprzedzać ułamki cyfrą 0 (zero); np.: wpisać 0 1/2

Podsieci IPv4 w przykładach. mgr inż. Krzysztof Szałajko

Podstawy programowania. Wykład: 4. Instrukcje sterujące, operatory. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Pętla for. Wynik działania programu:

Arytmetyka liczb binarnych

Zestaw 3. - Zapis liczb binarnych ze znakiem 1

1259 (10) = 1 * * * * 100 = 1 * * * *1

Wstęp do programowania. Reprezentacje liczb. Liczby naturalne, całkowite i rzeczywiste w układzie binarnym

Opis: Instrukcja warunkowa Składnia: IF [NOT] warunek [AND [NOT] warunek] [OR [NOT] warunek].

INŻYNIERIA BEZPIECZEŃSTWA LABORATORIUM NR 2 ALGORYTM XOR ŁAMANIE ALGORYTMU XOR

Podstawy Informatyki

Programowanie w języku Python. Grażyna Koba

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

Algorytmy przeszukiwania wzorca

- - Ocena wykonaniu zad3. Brak zad3

ARCHITEKTURA SYSTEMÓW KOMPUTEROWYCH

Podział sieci na podsieci wytłumaczenie

Systemy liczbowe. 1. Przedstawić w postaci sumy wag poszczególnych cyfr liczbę rzeczywistą R = (10).

Wprowadzenie do architektury komputerów systemy liczbowe, operacje arytmetyczne i logiczne

Jeszcze o algorytmach

Podstawy informatyki. Informatyka stosowana - studia niestacjonarne. Grzegorz Smyk. Wydział Inżynierii Metali i Informatyki Przemysłowej

Funkcja kwadratowa. f(x) = ax 2 + bx + c = a

Informatyka 1. Wyrażenia i instrukcje, złożoność obliczeniowa

; B = Wykonaj poniższe obliczenia: Mnożenia, transpozycje etc wykonuję programem i przepisuję wyniki. Mam nadzieję, że umiesz mnożyć macierze...

BŁĘDY OBLICZEŃ NUMERYCZNYCH

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

Liczby rzeczywiste. Działania w zbiorze liczb rzeczywistych. Robert Malenkowski 1

Transkrypt:

WYŻSZA SZKOŁA PRZEDSIĘBIORCZOŚCI I ADMINISTRACJI W LUBLINIE KIERUNEK: INFORMATYKA PROBLEM SORTOWANIA STABILNEGO NA PRZYKŁADZIE JĘZYKA PHP 5 PROBLEM OF STABLE SORTING ON THE PHP5 EXAMPLE Autor: Wojciech Brüggemann Nr albumu: 11576 Promotor: dr Beata Pańczyk Lublin 2011

Oświadczenie kierującego pracą. Oświadczam, iż niniejsza praca została przygotowana pod moim kierunkiem i stwierdzam, że spełnia ona warunki do przedstawienia jej w postępowaniu o nadanie tytułu zawodowego... (data).. (podpis) Oświadczenie autora pracy. Świadomy odpowiedzialności prawnej oświadczam, iż niniejsza praca dyplomowa została napisana przeze mnie samodzielnie i nie zawiera treści uzyskanych w sposób niezgodny z obowiązującymi przepisami. Oświadczam również, że przedstawiona praca nie była wcześniej podmiotem procedur związanych z uzyskaniem tytułu zawodowego w wyższej uczelni. Ponadto oświadczam, iż niniejsza wersja pracy jest identyczna z załączoną wersją elektroniczną... (data).. (podpis) 2

SPIS TREŚCI SPIS TREŚCI SPIS TREŚCI... 3 1. WSTĘP... 4 2. PRZEGLĄD ALGORYTMÓW SORTUJĄCYCH... 5 3. POSZUKIWANIE ROZWIĄZANIA STABILNEGO ALGORYTMU SORTUJĄCEGO.. 7 3.1. Poszukiwanie rozwiązań w Internecie... 7 3.2. Merge Sort... 7 3.2.1. Opis... 7 3.2.2. Funkcja scalająca zbiory... 9 3.3. Radix Sort... 12 3.3.1. Opis... 12 3.3.2. Counting Sort... 13 3.3.3. Optymalizacja związana z pętlą foreach... 16 3.3.4. Optymalizacja związana z tablicą C... 17 3.3.5. Implementacja dla liczb całkowitych dodatnich... 19 3.3.6. Implementacja dla liczby całkowitych dodatnich i ujemnych... 25 3.3.7. Implementacja dla liczby zmiennoprzecinkowych... 28 3.3.8. Implementacja dla łańcuchów... 29 3.3.9. Implementacja uniwersalna... 36 4. WYNIKI TESTÓW... 37 4.1. Weryfikacja stabilności algorytmów... 37 4.2. Testy wydajnościowe... 37 4.2.1. Informacje ogólne... 37 4.2.2. Testy wydajnościowe dla liczb całkowitych dodatnich... 37 4.2.3. Testy wydajnościowe dla liczb całkowitych dodatnich i ujemnych... 39 4.2.4. Testy wydajnościowe dla liczb zmiennoprzecinkowych... 40 4.2.5. Testy wydajnościowe dla łańcuchów... 42 5. PODSUMOWANIE... 46 5.1. Podsumowanie testów... 46 5.2. Osiągnięcia własne dyplomanta... 47 6. BIBLIOGRAFIA... 48 7. ZAŁĄCZNIKI... 49 7.1. Program autorski dostępny jest na stronie:... 49 7.2. Budowa programu... 49 3

1. WSTĘP 1. WSTĘP Celem tej pracy jest znalezienie stabilnego algorytmu sortowania, który mógłby konkurować z powszechnie stosowanym algorytmem sortowania Quick Sort. W PHP systemowa funkcja sort realizuje właśnie algorytm Quick Sort [2][1], który jest algorytmem niestabilnym. W pracy tej nie będą omawiane proste algorytmy sortowania jak na przykład sortowanie bąbelkowe. Zostaną pokazane różne zaawansowane rozwiązania tego problemu, gdzie najważniejszą sprawą jest wydajność algorytmu. Wydajność poszczególnych rozwiązań zostanie porównana z wydajnością algorytmu Quick Sort. Te rozwiązania zostaną zoptymalizowane pod kątem języka PHP5, w którym to języku będzie wykonana cała praca. W przypadku uzyskania wyników nieznacznie gorszych niż czas działania Quick Sort, autor tej pracy będzie uważał osiągnięte wyniki za sukces. Wynikiem tej pracy będzie program, do testowania różnych algorytmów sortowania. Program zostanie napisany obiektowo, a jego istotą będzie klasa sort w której będą zawarte najważniejsze algorytmy sortowania. Program będzie dostępny w Internecie pod adresem http://wojtek77.ovh.org/praca_dyplomowa. Na tej stronie zostaną też upublicznione źródła algorytmów użytych w tej pracy jak i cała praca. Praca składa się z ośmiu rozdziałów. Pierwszy rozdział jest wprowadzeniem do pracy. Drugi opisuje ogólne cechy algorytmów sortujących. Trzeci rozdział pokazuje drogę od poszukiwań rozwiązań w Internecie po opis implementacji dwóch algorytmów stabilnych: Merge Sort i Radix Sort. W czwartym rozdziale są wymienione osiągnięcie uzyskane podczas tworzenia tej pracy. W piątym są przedstawione wyniki algorytmów sortujących a szósty rozdział kończy całą pracę podsumowaniem. 4

2. PRZEGLĄD ALGORYTMÓW SORTUJĄCYCH 2. PRZEGLĄD ALGORYTMÓW SORTUJĄCYCH Zasadniczo algorytmy sortowania można podzielić na proste i złożone. Przykładem algorytmów prostych jest sortowanie bąbelkowe czy sortowania przez wstawianie. Obydwa te algorytmy mają złożoność obliczeniową O(n 2 ), co oznacza, że nie nadają się do sortowania dużych ilości danych. Na przykład jeśli czas do posortowania 1 elementu wynosi 1 sekundę to do posortowania 10 elementów już ten czas będzie wynosił 100 sekund, a nie 10 sekund. Do algorytmów złożonych zalicza się takie algorytmy jak Quick Sort, Merge Sort. Te algorytmy mają często złożoność obliczeniową O(nlogn). Taka złożoność obliczeniowa nadaje się do sortowania dużych ilości danych. Jest prawie tak dobra jak złożoność liniowa czyli O(n), w której proporcjonalnie do ilości danych rośnie czas potrzebny na wykonanie zadania [1]. Rysunek 2.1. pokazuje trzy wykresy dla różnych złożoności obliczeniowych: czerwonym kolorem pokazano złożoność obliczeniową O(n 2 ) zielonym kolorem pokazano złożoność obliczeniową O(nlogn) niebieskim kolorem pokazano złożoność obliczeniową O(n) Rys. 2.1. Poglądowe przedstawienie rożnych złożoności obliczeniowych [3][3] Inną cechą algorytmów sortujących jest ich stabilność. Jest to cecha która mówi, czy dane o tych samych wartościach nie zostaną zamienione między sobą. Na przykład jeśli zbiór: 5

2. PRZEGLĄD ALGORYTMÓW SORTUJĄCYCH 1,1 poddany zostanie sortowaniu, to algorytm stabilny nie zamieni jedynki z pierwszego miejsca z jedynką z drugiego. Natomiast algorytm niestabilny może taką zamianę zrobić. Na dodatek nie można przewidzieć kiedy taka zamiana nastąpi, co jeszcze bardziej pogarsza sytuację. 6

3. POSZUKIWANIE ROZWIĄZANIA STABILNEGO ALGORYTMU SORTUJĄCEGO 3.1. Poszukiwanie rozwiązań w Internecie Na stronie Eioba można znaleźć ranking dla różnych algorytmów sortujących [4][4]. Z pośród algorytmów stabilnych pierwsze miejsce zajął algorytm Counting Sort, drugie Merge Sort a trzecie Radix Sort. Algorytm Counting Sort ma ograniczenie, polegające na tym, że musi mieć z góry określony zbiór danych jakie będą występować w zbiorze sortowanym. Na przykład liczby od 1 do 100 (więcej na temat tego algorytmu sortowanie będzie napisane w dalszej części pracy przy omawianiu algorytmu Radix Sort). Dlatego algorytm Counting Sort nie nadaje się jako uniwersalny algorytm sam w sobie. Drugie miejsce w tym teście algorytmu Merge Sort daje mu w zasadzie pierwsze miejsce wśród algorytmów stabilnych i jednocześnie uniwersalnych. Z kolei na stronie prywatnej p. Kamila Korolkiewicza [5][5] można znaleźć test, w którym pierwsze miejsce zajmuje algorytm Radix Sort. W tym teście ten algorytm jest użyty w różnych rozwiązaniach jako Radix Sort 1..10. Więcej na temat tego algorytmu sortowania będzie napisane w dalszej części pracy. Wybrano dwa algorytmy stabilne: Merge Sort i Radix Sort, które zostaną opisane w kolejnych rozdziałach. 3.2. Merge Sort 3.2.1. Opis Jest to algorytm sortowania wykorzystujący zasadę dziel i zwyciężaj. Istota działania polega na podzieleniu zbioru sortowanego na jednoelementowe zbiory, które są następnie scalane. Najpierw jest scalana para zbiorów jednoelementowych (taki zbiór jest sam w sobie już posortowany). Następnie są scalane zbiory dwuelementowe, potem czteroelementowe, ośmioelementowe i tak dalej, aż do scalenia wszystkich elementów zbioru. Podczas scalania następuje sortowanie danych. Na przykład podczas scalania zbioru 2 i 1 otrzymujemy zbiór 1,2. Ideę algorytmu pokazuje rysunek 3.1. 7

Rys. 3.1. Przykład sortowania przez scalanie dla 7-io elementowej tablicy[6] To czy algorytm Merge Sort będzie stabilny czy nie zależy od funkcji scalającej zbiory. Generalnie powinno się tak budować tą funkcję, aby uzyskać stabilność algorytmu, ponieważ nie ma to wpływu na szybkość działania, a najczęściej sprowadza się do ustawienia ostrej lub nieostrej nierówności ( < lub <= ) podczas porównywania elementów w funkcji scalającej, co zostało pokazano na listingu 3.1. pogrubioną czcionką. Listing 3.1. if($tab[$lt] <= $tab[$rt]) $bufor[++$i] = $tab[$lt]; else if(++$lt > $x) while($rt <= $right) $bufor[++$i] = $tab[$rt]; ++$rt; break; /*...*/ Początkowy podział zbioru sortowanego na jednoelementowe zbiory odbywa się w sposób rekurencyjny, jak pokazano na listingu 3.2. 8

Listing 3.2. private static function merge_sort_recursive(& $tab, & $bufor, $left, $right) if($left < $right) $x = (int) (($left + $right) / 2); self::merge_sort_recursive($tab, $bufor, $left, $x); self::merge_sort_recursive($tab, $bufor, $x+1, $right); /*...*/ 3.2.2. Funkcja scalająca zbiory W Internecie są dostępne dwa rozwiązania funkcji scalającej zbiory. Pierwsze rozwiązanie zostało zaimplementowane w klasie sort jako funkcja merge_sort_ze_strony_www_1, natomiast drugie rozwiązanie jako funkcja merge_sort_ze_strony_www_2. Drugie rozwiązanie jest o około 5% szybsze od pierwszego. Funkcja scalająca zbiory jest dosyć prostą funkcją, która ma za zadanie scalić zawsze tylko dwa zbiory elementów już wcześniej posortowanych w jeden zbiór również posortowany. Przy budowaniu takiej funkcji ważne jest zachowanie jak najmniejszej ilości porównań, co przełoży się na większą wydajność algorytmu. Dla lepszego omawiania tematu dwa zbiory przed scaleniem zostaną nazwane jako zbiór lewy i prawy a zbiór będący wynikiem scalenia jako zbiór końcowy. Dodatkowo rozważane są dwa stany: 1. Kiedy są dostępne elementy do porównania ze zbioru lewego i prawego (ten stan trwa najdłużej) 2. Kiedy już wszystkie elementy zostały porównane z lewego lub prawego zbioru a zostały jeszcze do dodania elementy z pozostałego zbioru Listing 3.3. przedstawia dwa dostępne w Internecie rozwiązania tego problemu. Listing 3.3. /* merge_sort_ze_strony_www_1 */ $j = $left; $k = $x; for($i = $left ; $i <= $right ; ++$i) $bufor[$i] = ($j == $x ($k <= $right && $tab[$j] > $tab[$k]))? $tab[$k++] : $tab[$j++]; for($i = $left ; $i <= $right ; ++$i) $tab[$i] = $bufor[$i]; $j oznacza licznik dla zbioru lewego a $k licznik dla zbioru prawego. $x oznacza pierwszy element dla zbioru prawego. Zbiór końcowy będzie utworzony w $bufor. Pierwsza pętla for 9

realizuje proces scalania. Jej długość wynosi dokładnie tyle ile ma wynosić wielkość zbioru końcowego. Pogrubionym drukiem zostało zaznaczone miejsce, gdzie następuje istota działania funkcji scalającej. Najpierw jest sprawdzany $j == $x co oznacza istnienie stanu 2 dotyczącego zbioru lewego. Później jest sprawdzane czy istnieje stan 1 i jeśli tak jest, następuję sprawdzanie dla wartości dla zbioru lewego i prawego. Podsumowując dla samego stanu 1 dla jednej iteracji są aż 4 porównania: 1. w pierwszej pętli for $i <= $right 2. $j == $x 3. $k <= $right 4. $tab[$j] > $tab[$k] Ostatecznie to rozwiązanie pokazane na listingu 3.3. okazało się najwolniejsze. Drugie rozwiązanie z Internetu zostało pokazane na listingu 3.4. Listing 3.4. /* merge_sort_ze_strony_www_2 */ $left_end = $x - 1; $i = $left; $count = $right - $left + 1; while($left <= $left_end && $x <= $right) if($tab[$left] <= $tab[$x]) $bufor[$i] = $tab[$left]; ++$i; ++$left; else $bufor[$i] = $tab[$x]; ++$i; ++$x; while($left <= $left_end) $bufor[$i] = $tab[$left]; ++$i; ++$left; while($x <= $right) $bufor[$i] = $tab[$x]; ++$i; ++$x; for($i = 0 ; $i < $count ; ++$i, --$right) $tab[$right] = $bufor[$right]; 10

$left oznacza licznik dla zbioru lewego a $x licznik dla zbioru prawego. $left_end oznacza ostatni element dla zbioru lewego a $right ostatni element dla zbioru prawego. Zbiór końcowy będzie utworzony w $bufor. Pierwsza pętla while realizuje stan 1, druga i trzecia pętla while realizuje stan 2 dla zbioru lewego lub prawego. Podsumowując dla samego stanu 1 dla jednej iteracji istnieją 3 porównania (zostały oznaczone pogrubionym drukiem): 1. w pierwszej pętli while $left <= $left_end 2. w pierwszej pętli while $x <= $right 3. $tab[$left] <= $tab[$x] To rozwiązanie ma o jedno porównanie mniej niż poprzednio omawiane i dlatego jest szybsze o około 3,5%. Autor tej pracy wymyślił trzecie rozwiązanie dla funkcji scalającej, które okazało się najszybsze i zostało użyte podczas testów jako domyślne rozwiązanie dla algorytmu Merge Sort. Rozwiązanie to zostało przedstawione na listingu 3.5. Listing 3.5. /* własne rozwiązanie dyplomanta */ $i = $left - 1; $lt = $left; $rt = $x + 1; while(true) if($tab[$lt] <= $tab[$rt]) $bufor[++$i] = $tab[$lt]; else if(++$lt > $x) while($rt <= $right) $bufor[++$i] = $tab[$rt]; ++$rt; break; $bufor[++$i] = $tab[$rt]; if(++$rt > $right) while($lt <= $x) $bufor[++$i] = $tab[$lt]; ++$lt; break; 11

$i oznacza licznik dla zbioru końcowego. $lt oznacza licznik dla zbioru lewego a $rt licznik dla zbioru prawego. $x oznacza ostatni element zbioru lewego a $right ostatni element dla zbioru prawego. Iteracje odbywają się w głównej pętli while z parametrem true. Na początku jest sprawdzane, który element zbioru lewego czy prawego ma zostać dodany do zbioru końcowego. Następnie w zależności, który zbiór został wybrany, następuje zwiększenie licznika dla danego zbioru i sprawdzenie czy licznik nie wyszedł poza zakres. Jeśli wyszedł to następuje dodanie elementów z pozostałej tablicy (lewej lub prawej) i następuje wyjście z pętli while poprzez break. Podsumowując dla samego stanu 1 dla jednej iteracji istnieją tylko 2 porównania (zostały oznaczone pogrubionym drukiem): 1. $tab[$lt] <= $tab[$rt] 2. ++$lt > $x lub ++$rt > $right w zależności na którym zbiorze wykonywane są operacje (lewy lub prawy) To rozwiązanie jest o około 6,5% szybsze od rozwiązania poprzedniego gdzie były 3 porównania i o około 10% szybsze od rozwiązania, gdzie były aż 4 porównania. 3.3. Radix Sort 3.3.1. Opis Radix sort (sortowanie pozycyjne) działa na zasadzie sortowania według pozycji cyfr, znaków, poczynając od najmniej znaczącej pozycji (od prawej strony liczby, łańcucha) do najbardziej znaczącej pozycji (do lewej strony liczby, łańcucha). Tabela 3.1. przedstawia przykład działania algorytmu Radix Sort. Tab. 3.1. Przykład działania algorytmu Radix Sort Przed Sortowanie wg Sortowanie wg sortowaniem jedności dziesiątek Po sortowaniu 31 31 21 21 23 21 23 23 21 23 31 31 38 38 38 38 Dodatkowy algorytm potrzebny do sortowania pozycji musi być algorytmem stabilnym, w przeciwnym wypadku dane nie zostaną posortowane. W wyniku tego algorytm Radix Sort jest również algorytmem stabilnym. Do sortowania pozycji idealnie nadaje się algorytm Counting Sort, który zostanie omówiony w następnym podrozdziale. Właśnie ten algorytm został wykorzystany w pracy do budowy algorytmu Radix Sort. Counting Sort ma liniową złożoność obliczeniową [7][7], 12

dzięki temu taką samą złożoność obliczeniową ma też Radix Sort, co sprawia, że doskonale nadaje się do sortowania dużych ilości danych. 3.3.2. Counting Sort Counting Sort z założenia musi znać możliwą maksymalną różnorodność zbioru jaki ma sortować. Czyli jeśli mają być sortowane dane ze zbioru 1..1000 to ten algorytm wymagana specjalnej tablicy o kluczach od 1 do 1000, gdzie każda wartość przypisana do klucza zlicza ilość wystąpień dla tego klucza. Ta tablica zliczająca w dalszej części tej pracy będzie nazywana tablicą C. Potrzeba znajomości kluczy dla tablicy C jest bardzo dużym ograniczeniem i sprawia, że ten algorytm nie może być sam w sobie algorytmem uniwersalnym, tak jak na przykład Quick Sort. Natomiast nie stanowi to problemu przy wykorzystaniu tego algorytmu przez Radix Sort, ponieważ przy sortowaniu pozycji wiadomo jaka różnorodność zbioru będzie potrzebna, na przykład przy liczbach będą to cyfry od 0 do 9 i wymagana tablica C będzie miała wtedy wielkość 10 gdzie kluczami będą cyfry od 0 do 9. Kolejność kluczy w tablicy C ma znaczenie i musi być dokładnie taka, aby tworzyła ciąg posortowany. Czyli po kolei klucze potrzebne do sortowania na przykład cyfr od 0 do 9 muszą być takie: 0, 1, 2...9. Tą właściwość można wykorzystać na przykład do sortowania malejącego i wtedy tablica C musiałaby mieć klucze w takiej kolejności: 9, 8, 7..0. W PHP kluczami w tablicy mogą być znaki. Przykładowa tablica C zawierająca na przykład znaki w takiej kolejności: c, b, a będzie w stanie zliczać 3 znaki a, b, c gdzie znak c będzie mniejszy od znaku b a znak b będzie mniejszy od znaku a. Podczas sortowania danych na tablicy C są wykonywane trzy operacje: 1. Wyzerowanie tablicy czyli wyzerowanie licznika dla każdego klucza 2. Zliczanie znaków wcześniej wyzerowany licznik dla każdego znaku jest zwiększany o jeden jeśli dany znak wystąpi 3. Dodanie do każdego licznika wszystkich jego wcześniejszych liczników ta operacja spowoduje, że teraz licznik będzie wskazywał na numer klucza dla tablicy posortowanej dla danego znaku, na przykład mając klucz 3 i wartość 4 oznacza to, że cyfra 3 ma zająć w tablicy już posortowanej pozycję 4, jeśli cyfr 3 było wiele pozycja 4 odnosi się do ostatniej cyfry 3 w tablicy posortowanej Przykład 3.1. 13

Dla zbioru 3,2,1,2, różnorodność zbioru testowanego wynosi 3, ponieważ występują tylko cyfry 1, 2, 3 i one będą kluczami tablicy C. W pierwszej operacji następuje wyzerowanie wartości dla wszystkich kluczy, w wyniku czego tablica C będzie miała taką postać: klucz 1 2 3 wartość 0 0 0 Następnie w drugiej operacji nastąpi zliczanie znaków: klucz 1 2 3 wartość 1 2 1 co oznacza, że cyfra 1 wystąpiła jeden raz, cyfra 2 dwa razy a cyfra 3 też tyko jeden raz. Następnie wykonywana jest trzecia operacja czyli dodanie do każdego licznika (wartości) wszystkich jego wcześniejszych liczników (wartości). Wygląda to następująco: dla klucza 1 nie ma liczników poprzednich dlatego nic jest dodawane dla klucza 2 istnieje poprzedni licznik i ma wartość 1 czyli teraz licznik dla klucza 2 będzie wynosił 3 (1+2) dla klucza 3 istnieją dwa poprzednie liczniki i mają wartość 1 i 2 czyli teraz licznik dla klucza 3 będzie wynosił 4 (1+2+1) klucz 1 2 3 wartość 1 3 4 Teraz tablica C pokazuje, że: cyfra 1 (klucz 1) ma być zapisana w tablicy posortowanej jako pierwszy element, cyfra 2 jako trzeci element i cyfra 3 jako czwarty element. Nie ma informacji o elemencie drugim. Przyczyną tego jest to, że cyfra 2 wystąpiła dwa razy i to właśnie ona będzie umieszczona na drugiej pozycji. I choć ostatecznie cyfra 2 będzie umieszczona po sortowaniu na pozycjach dwa i trzy to tablica C pokazuje trzecią pozycją czyli ostatnią dla danej cyfry. Aby ostatecznie posortować zbiór 3,2,1,2, trzeba przejść od tyłu przez ten zbiór i wykonać operacje: dla każdego elementu odczytać z tablicy C jego pozycję w posortowanym zbiorze i zapisać ten element w nowym zbiorze, który będzie już zbiorem posortowanym następnie należy tą pozycję pomniejszyć o jeden (będzie to pozycja dla następnego tego samego elementu, jeśli takie elementy występują wielokrotnie) Na przykładzie zbioru 3,2,1,2 wygląda to następująco: ostatnim elementem tego zbioru jest cyfra 2, w tablicy C dla cyfry 2 jest określona pozycja trzecia w posortowanym zbiorze, należy zatem skopiować cyfrę 2 na pozycję 14

trzecią do nowego zbioru z posortowanymi elementami i następnie w tablicy C dla cyfry 2 zmniejszyć pozycję o jeden uzyskując w ten sposób pozycję drugą, po tej operacji tablica C będzie miała postać: a nowo powstały zbiór: klucz 1 2 3 wartość 1 2 4 2 (ostatnia) kolejnym elementem tego zbioru jest cyfra 1, w tablicy C dla cyfry 1 jest określona pozycja pierwsza w posortowanym zbiorze, należy zatem skopiować cyfrę 1 na pozycję pierwszą do nowego zbioru z posortowanymi elementami i następnie w tablicy C dla cyfry 1 zmniejszyć pozycję o jeden uzyskując w ten sposób pozycję zerową, po tej operacji tablica C będzie tak wyglądać: a nowo powstały zbiór tak: 1 klucz 1 2 3 wartość 0 2 4 2 (ostatnia) kolejnym elementem tego zbioru jest cyfra 2, w tablicy C dla cyfry 2 jest określona pozycja druga w posortowanym zbiorze, należy zatem skopiować cyfrę 2 na pozycję drugą do nowego zbioru z posortowanymi elementami i następnie w tablicy C dla cyfry 2 zmniejszyć pozycję o jeden uzyskując w ten sposób pozycję pierwszą, po tej operacji tablica C będzie tak wyglądać: a nowo powstały zbiór tak: 1 klucz 1 2 3 wartość 0 1 4 2 (pierwsza) 2 (ostatnia) kolejnym elementem tego zbioru jest cyfra 3, w tablicy C dla cyfry 3 jest określona pozycja czwarta w posortowanym zbiorze, należy zatem skopiować cyfrę 2 na pozycję czwartą do nowego zbioru z posortowanymi elementami i następnie w tablicy C dla cyfry 3 zmniejszyć pozycję o jeden uzyskując w ten sposób pozycję trzecią, po tej operacji tablica C będzie tak wyglądać: a nowo powstały zbiór tak: klucz 1 2 3 wartość 0 1 3 15

1 2 (pierwsza) 2 (ostatnia) 3 Podsumowując, ten przykład pokazuje też, że algorytm Counting Sort jest algorytmem stabilnym, gdzie dwukrotnie występujące cyfry 2 nie zostały ze sobą zamienione. Dodatkowo ten algorytm potrzebuje specjalnej tablicy służącej jako bufor dla danych posortowanych. Kiedy już w tablicy bufor znajdują się wszystkie dane (posortowane) to wówczas następuje przepisanie tych danych ponownie do tablicy oryginalnej. 3.3.3. Optymalizacja związana z pętlą foreach Algorytm Radix Sort za każdym razem przy sortowaniu pozycji korzysta z algorytmu Counting Sort. Mając zbiór do posortowania składający się z 5 pozycji to tyle razy musi być uruchomiony algorytm Counting Sort. Każde użycie tego algorytmu oznacza koszty związane z operacjami na tablicy C i koszty związane z użyciem bufora (tablica bufor ). We wszystkich dostępnych w Internecie implementacjach tego algorytmu jest używana pętla for. Autor tej pracy porównywał różne metody przechodzenia przez tablice w PHP takie jak: 1. foreach (tylko możliwość przechodzenia do przodu) 2. for (można przechodzić w dwóch kierunkach) 3. reset-each lub end-prev (też można przechodzić w dwóch kierunkach) i zdecydowanie najbardziej efektywna jest metoda pierwsza czyli pętla foreach. Niestety ta pętla ma możliwość przechodzenia tylko do przodu, a w algorytmie Counting Sort istnieje potrzeba przechodzenia wstecz. Dlatego autor tej pracy zmodyfikował algorytm Counting Sort. Modyfikacji uległy dwa miejsca: 1. Opisywana wcześniej operacja w tablicy C: dodanie do każdego licznika wszystkich jego wcześniejszych liczników, została zmieniona tak, aby w wyniku tej operacji wskazywany numer klucza dla tablicy posortowanej odnosił się nie do aktualnie ostatniej tej samej cyfry (znaku) tylko do aktualnie pierwszej tej samej cyfry (znaku). 2. Końcowa pętla wykonywana do tyłu powodująca przepisanie posortowanych elementów do bufora została zamieniona na pętlę wykonywaną do przodu, a po przepisaniu konkretnego elementu zbioru, wskazywana pozycja dla danego znaku w tablicy C jest zwiększana o jeden (poprzednio była zmniejszana o jeden) Fragmentu kodu przy użyciu pętli for pokazuje listing 3.6. Listing 3.6. for($i = 0 ; $i < $csize ; ++$i) $c[$i] = 0; 16

for($i = 0 ; $i < $n ; ++$i) ++$c[$tab[$i] >> $pos & $maska]; for($i=1 ; $i < $csize ; ++$i) $c[$i] += $c[$i-1]; for($i = $n-1 ; $i >= 0 ; --$i) $bufor[--$c[$tab[$i] >> $pos & $maska]] = $tab[$i]; if($tab!== $bufor) $tab = $bufor; Fragmentu kodu przy użyciu pętli foreach pokazuje listing 3.7. Listing 3.7. foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[$w >> $pos & $maska]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; $suma += $w; foreach($tab as $w) $bufor[ ++$c[$w >> $pos & $maska] ] = $w; if($tab!== $bufor) $tab = $bufor; W wyniku tych zmian wydajność wzrosła o około 20%. 3.3.4. Optymalizacja związana z tablicą C Przy omawianiu algorytmu Counting Sort, była mowa o tym, że wielkość tablicy C jest uzależniona od różnorodności znaków na danej pozycji. W przypadku liczb taka różnorodność wynosi 10, ponieważ możliwe liczby występujące na dowolnej pozycji to cyfry od 0 do 9. W przypadku sortowania liczb, gdzie wszystkie liczby mają po 8 cyfr, algorytm Counting Sort musi być uruchomiony 8 razy. Można to zoptymalizować poprzez jednoczesne sortowanie wielu pozycji naraz. Na przykład zamiast sortować jedną pozycją, dla różnorodności 10 można sortować jednocześnie dwie pozycje i wtedy różnorodność równa jest 100 a Counting Sort musi być uruchamiany tylko 4 razy. Tabela 3.2. przedstawia przykład pokazujący różne użycie tablicy C dla liczb ośmiocyfrowych. 17

Tab. 3.2. Przykład pokazujący różne użycie tablicy C dla liczb 8-cyfrowych ilość jednocześnie sortowanych pozycji wielkość tablicy C ilość uruchomień algorytmu Counting Sort 1 10 8 2 10 2 4 3 10 3 3 4 10 4 2 5 10 5 2 6 10 6 2 7 10 7 2 8 10 8 1 Z tabeli 3.2. wynika, że wielkość tablicy C rośnie kwadratowo do liniowego przyrostu ilości jednocześnie sortowanych pozycji. To oznacza, że istnieje pewien punkt, poza którym nie opłaca się ze względu na wydajność zwiększać wielkości tablicy C, bo koszt obsługi tej tablicy gwałtownie wzrasta. W czasie testów, ten punkt zawierał się mniej więcej w przedziale od 10 tys. do 50 tys. wielkości tablicy C. Ten przykład pokazuje też, że sortowanie 4, 5, 6 i 7 pozycji jednocześnie powoduje taką samą ilość uruchomień algorytmu Counting Sort, co w praktyce oznacza, że sortowanie dla 4-ech pozycji jednocześnie będzie znacznie szybsze niż sortowanie dla 5-iu, 6-iu lub 7-iu pozycji. Autor tej pracy wykonał część algorytmów Radix Sort z automatycznym liczeniem wielkości tablicy C. Nazwy funkcji mają wtedy przydomek auto. Wyjątkowo funkcja radix_sort (bez tego przydomku), również korzysta z automatycznego liczenia tablicy C. Generalnie przy automatycznym liczeniu tablicy C największa rolę odgrywa przedział od 10 tys. do 50 tys. dla tablicy C. Ale istnieje też zależność pomiędzy ilością danych potrzebnych do sortowania a tablicą C. Przy bardzo małej ilości danych do sortowania (na przykład 100 liczb), koszt obsługi tablicy C rzędu 10 tys. jest na tyle duży, że wydajniej jest wtedy użyć tablicy C mniejszych rozmiarów kosztem większej ilości uruchomień algorytmu Counting Sort. Automatyczne wyliczanie wielkości tablicy C zostało indywidualnie wykonane dla: liczb całkowitych dodatnich liczb całkowitych dodatnich i ujemnych liczb zmiennoprzecinkowych łańcuchów 18

3.3.5. Implementacja dla liczb całkowitych dodatnich Algorytm Radix Sort dla liczb całkowitych dodatnich został zaimplementowany w klasie sort w trzech funkcjach: radix_sort_int radix_sort_int_auto radix_sort_number_plus_auto z czego dwie pierwsze funkcje wykorzystują do swojego działania operacje na bitach, natomiast trzecia funkcja ma podobną budowę jak omawiana w następnym rozdziale implementacja dla liczb całkowitych dodatnich i ujemnych, w związku z czym jej opis zostanie tu pominięty. Operacje na bitach umożliwiają wydajniejsze wykorzystanie tablicy C niż operacje na cyfrach w systemie dziesiętnym. Wynika to z tego, że dla operacji na bitach wielkość tablicy C będzie liczbą 2 n. W przypadku operacji na cyfrach w systemie dziesiętnym jest to liczba 10 n. Na przykład zakładając, że największa możliwa wielkość tablicy C będzie mogła wynieść 50 tys., możliwe są takie wielkości tablicy C: operacje na bitach 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 operacje na cyfrach 10, 100, 1000, 10000 Niestety, operacje na bitach są możliwe w PHP 5 tylko na danych typu int oraz string. W algorytmie Radix Sort nie mają zastosowania na danych typu string, ponieważ uniemożliwiają posortowanie specjalnych znaków narodowych takich jak na przykład ą, ć, ę itd. Operacje na bitach dla danych typu int również nie mają zastosowania, jeśli w zbiorze przeznaczonym do sortowania występują liczby dodatnie i ujemne. Wynika to z tego, że bit określający znak liczby jest bitem najbardziej znaczącym, co skutkuje tym, że przy operacjach na bitach liczby ujemne są zawsze większe od liczb dodatnich. Dlatego w tej pracy operacje na bitach zostały zastosowane tylko do sortowania liczb całkowitych dodatnich. Opis funkcji radix_sort_int. Ta funkcja została napisana na podstawie funkcji radix_sort dostępnej w Internecie [5][5]. Oryginalnie funkcja była napisana w C++ i autor tej pracy odpowiednio ją zmienił, dostosowując do PHP 5. Funkcja posiada jednak pewną wadę, niepotrzebnie sortuje wszystkie możliwe bity dostępne dla liczb int w systemie. Na przykład jeśli mają być sortowane liczby nie większe niż 255 (czyli zajmujące nie więcej niż 8 bitów) to mimo to 19

funkcja ta będzie przeszukiwać 32 bity a nie 8 bitów. Wyżej opisana wada zostanie zniwelowana w kolejnej funkcji radix_sort_int_auto opisywanej dalej. Funkcja radix_sort_int przyjmuje dwa argumenty: 1. $tab tablicę z danymi do sortowania (przez referencję) 2. $nbits zmienna określająca ile jednocześnie bitów ma być sortowanych (domyślna wartość 12 została ustalona doświadczalnie jako najlepsza) Następnie są inicjalizowane różne zmienne potrzebne do pracy tej funkcji takie jak między innymi tablica C czy tablica bufor, jak pokazano na listingu 3.8. Listing 3.8. $Tbits = PHP_INT_SIZE * 8; $csize = 1 << $nbits; $c = range(0, $csize-1); $bufor = $tab; $maska = ~(~0 << $nbits); Zmienna $Tbits określa ile maksymalnie bitów ma liczba, możliwe wartości to 32 lub 64 w zależności od systemu operacyjnego. Zmienna $csize określa wielkość tablicy C, gdzie sposób wyliczenia tej wartości polega na przesuwaniu bitów w lewo dla liczby 1. Przesuwanie bitów w lewo oznacza mnożenie przez 2 tyle razy ile jest przesunięć. Na przykład przesunięcie w lewo o 1 oznacza pomnożenie przez 2, przesunięcie w lewo o 2 oznacza pomnożenie przez 4 itd. Zmienna $c to tablica C. Zmienna $bufor to tablica służąca za bufor (o tej tablicy była mowa wcześniej przy opisie algorytmu Counting Sort). Zmienna $maska określa maskę (czyli ciąg jedynek np. 111 dla $nbits = 3) potrzebną do wyciągania zadanej liczby z liczby sortowanej, na przykład jeśli liczbą sortowaną jest liczba 101011 a ilość bitów jaka ma być jednocześnie sortowana wynosi 3 ($nbits = 3) to za pomocą maski można uzyskać 3 pierwsze pozycje czyli liczbę 011 oraz kolejne trzy pozycję czyli liczbę 101. Sposób liczenia tej maski jest następujący: ~0 uzyskanie największej możliwej maski w systemie (dla systemu 32-bitowego to będą 32 jedynki) ~0 << $nbits przesunięcie w lewo bitów o ilość $nbits, co w efekcie da liczbę z taką ilością zer z prawej strony ile wynosi wartość $nbits ~(~0 << $nbits) negacja bitowa dla poprzednio otrzymanej liczby co w efekcie da pożądaną maskę 20

Następnie jest wykonywana pętla for, w której przy każdej iteracji jest realizowany algorytm Counting Sort, jak pokazano na listingu 3.9. Listing 3.9. for($pos = 0 ; $pos < $Tbits ; $pos += $nbits) foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[$w >> $pos & $maska]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; $suma += $w; foreach($tab as $w) $bufor[ ++$c[$w >> $pos & $maska] ] = $w; if($tab!== $bufor) $tab = $bufor; Wyjaśnienia wymaga fragment kodu $w >> $pos & $maska występujący w dwóch miejscach, najpierw w pętli foreach gdzie następuje zliczanie liczb w tablicy C, później gdy następuje przepisywanie już posortowanych liczb do tablicy bufor. Zmienna $w oznacza liczbę która podlega sortowaniu na przykład liczba 101011. Zmienna $pos oznacza liczbę dla konkretnej iteracji, na przykład dla $nbits = 3 podczas pierwszej iteracji $pos = 0, podczas drugiej $pos = 3. Operator bitowy >> ma większy priorytet niż operator & dlatego najpierw zostanie wykonany kod: $w >> $pos który w efekcie da przycięcie liczby z prawej strony o tyle miejsc ile wynosi wartość $pos. Następnie kod: $w >> $pos & $maska spowoduje wyciągnięcie odpowiednich bitów z liczby sortowanej, na przykład dla liczby sortowanej 101011 i dla $nbits = 3 w pierwszej iteracji to będą takie 3 bity: 011 a w drugiej iteracji takie: 101. Opis funkcji radix_sort_int_auto. Ta funkcja jest rozszerzeniem poprzednio opisywanej funkcji radix_sort_int, ale pozbawiona jest jej wady, czyli zamiast przeszukiwania wszystkich możliwych bitów 21

w systemie dla liczb int, przeszukiwana jest tylko taka ilość bitów jaka jest niezbędna. Dzięki temu funkcja ta jest wydajniejsza od poprzednio opisywanej. Aby to osiągnąć najpierw musi być wyznaczona największa liczba w zbiorze sortowanym. Ta największa liczba może zostać podana jako argument funkcji $max lub - jeśli nie jest podana - zostanie wyliczona, co zostało pokazane na listingu 3.10. 22

Listing 3.10. if(!isset($max)) $max = reset($tab); foreach($tab as $w) if($max < $w) $max = $w; Następnie zostaje wyliczona w zmiennej $Tbits ilość bitów jaką zajmuje największa liczba, jak pokazano na listingu 3.11. Listing 3.11. $Tbits = 1; while(($w = (1 << $Tbits)) <= $max) if($w < 0) break; ++$Tbits; Najpierw zmienna $Tbits jest ustawiana na 1. Później w pętli while jest sprawdzane, czy liczba $max należy do następnego przedziału dla potęgi liczby 2. Możliwe przedziały to: 2-3 (dla $Tbits = 2) 4-7 (dla $Tbits = 3) 8-15 (dla $Tbits = 4)... 536 870 912-1 073 741 823 (dla $Tbits = 30) 1 073 741 824-2 147 483 647 (dla $Tbits = 31) Następnie jest sprawdzane, czy $w będąca najmniejszą liczbą z następnego przedziału na skutek przesunięć bitów (1 << $Tbits) nie stała są liczbą ujemną, co stałoby się gdyby wartość $Tbits wynosiła 31 dla 32-bitowych systemów lub 63 dla 64-bitowych systemów. W przypadku gdyby zmienna $w byłaby mniejsza od zera, nastąpi wyjście z pętli bez zwiększania wartości $Tbits, co oznacza, że maksymalna wartość $Tbits dla systemu 32- bitowego może wynieść 31 a dla systemu 64-bitowego 63. W przeciwnym wypadku, jeśli $w jest większe lub równe 0 następuje zwiększenie $Tbits o 1 i kolejne sprawdzanie warunku w pętli while. Należy tu jeszcze wspomnieć, że największa wartość $Tbits zawsze mniejsza o 1 od możliwej maksymalnej liczby bitów w systemie (31 a nie 32 i 63 a nie 64), bierze się stąd, że najbardziej znaczący bit określający znak liczby i nie ma znaczenia w tym algorytmie przeznaczonym tylko dla liczb dodatnich. 23

Funkcja radix_sort_int_auto dodatkowo automatycznie wylicza wielkość tablicy C, kiedy zostaje wywołana z domyślnym parametrem $nbits o wartości NULL. Istota działania polega na tym, że maksymalna możliwa w tym algorytmie tablica C może wynosić 2 12 (4096) co jest równoznaczne z tym, że zmienna $nbits równa jest 12. Jednakże w przypadku gdyby $Tbits wynosiło ponad 12 na przykład 13, nieefektywne byłoby użycie dwa razy tablicy o wielkości 2 12. W przypadku kiedy $Tbits równe było 13, efektywniej byłoby użyć dwa razy tablicę o wielkości 2 7. Zostało to zrealizowane w kodzie programu, jak pokazano na listingu 3.12. Listing 3.12. if(!isset($nbits)) $nbits = ceil($tbits/ceil($tbits/12)); ceil($tbits/12) wylicza ile razy będzie użyta tablica C (funkcja ceil zaokrągla ułamki w górę) ceil($tbits/ceil($tbits/12)) na podstawie wcześniej obliczonej ilości użycia tablicy C, teraz jest określana optymalna wielkość tej tablicy W tablicy 3.3. przedstawiono użycie tablicy C w zależności od wielkości zmiennej $Tbits w systemie 32-bitowym. Tab. 3.3. Funkcja radix_sort_int_auto - automatyczne użycie tablicy C w systemie 32-bitowym $Tbits 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 wielkość tablicy C ilość użycia tablicy C 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 2 9 2 10 2 11 2 12 2 7 2 7 2 8 2 8 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 $Tbits 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 wielkość tablicy C ilość użycia tablicy C 2 9 2 9 2 10 2 10 2 11 2 11 2 12 2 12 2 9 2 9 2 9 2 10 2 10 2 10 2 11 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 Pozostała funkcjonalność funkcji radix_sort_int_auto jest taka sama jak wcześniej opisywanej funkcji radix_sort_int. 24

3.3.6. Implementacja dla liczby całkowitych dodatnich i ujemnych Algorytm Radix Sort dla liczb całkowitych dodatnich i ujemnych został zaimplementowany w klasie sort w dwóch funkcjach: radix_sort_number radix_sort_number_auto Te funkcje w odróżnieniu od poprzednio omawianych funkcji radix_sort_int i radix_sort_int_auto potrafią dodatkowo sortować liczby większe niż maksymalna liczba typu int. W PHP w systemie 32-bitowym taka maksymalna liczba wynosi 2 147 483 647. Jeśli do takiej liczby zostałaby dodana wartość 1 to taka liczba automatycznie zmieniłaby typ na float. Operacje na bitach nie są możliwe na liczba typu float, dlatego funkcje omawiane w tym podrozdziale wykorzystują operacje na cyfrach w systemie dziesiętnym. Opis funkcji radix_sort_number Ta funkcja przyjmuje dwa argumenty: 1. $tab tablicę z danymi do sortowania (przez referencję) 2. $max zmienna określająca maksymalną wartość bezwzględną w zbiorze sortowanym, ta zmienna jeśli ma wartość domyślną NULL jest wyliczana przez funkcję Aby zoptymalizować liczenie zmiennej $max funkcja abs obliczająca wartość bezwzględną nie jest wywoływana dla każdej wartości ze zbioru sortowanego, tylko jest wywoływana 3 razy. Zostało to osiągnięte przez osobne liczenie największej i najmniejszej wartości zbioru, jak pokazano na listingu 3.13. Listing 3.13. if(!isset($max)) $min = $max = reset($tab); foreach($tab as $w) if($max < $w) $max = $w; elseif($min > $w) $min = $w; $max = (abs($min) < abs($max))? abs($max) : abs($min); Następnie są inicjalizowane dwie zmienne potrzebne do pracy tej funkcji: 1. $c oznacza tablicę C, która zawiera 19 kluczy dla cyfr od -9 do 9, ta tablica jest w stanie sortować tylko jedną pozycję cyfr w tym samym czasie 2. $bufor tablica bufor 25

Następnie jest wykonywana pętla for, w której przy każdej iteracji jest realizowany algorytm Counting Sort. Ta funkcja różni się od poprzednio opisywanych funkcji (radix_sort_int i radix_sort_int_auto) sposobem wyciągania cyfr z odpowiednich pozycji. Tu nie można było zastosować do tego celu przesunięć bitowych, dlatego zastosowano inną metodę, polegającą na dzieleniu liczby przez kolejne potęgi liczby 10 (1, 10, 100 itd. zmienna $d) i następnie wykonanie reszty z dzielenia z 10 przy pomocy funkcji fmod. Funkcja fmod zwraca wartość jako typ float, ale podczas użycia tej wartości jako klucza w tablicy ta wartość typu float zostaje w PHP zamieniona automatycznie na typ int. W ten sposób ostatecznie są wyciągane cyfry z żądanych pozycji, jak pokazano na listingu 3.14. Listing 3.14. for($d = 1 ; $d <= $max ; $d *= 10) foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[ fmod(($w/$d),10) ]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; $suma += $w; foreach($tab as $w) $bufor[ ++$c[fmod(($w/$d),10)] ] = $w; if($tab!== $bufor) $tab = $bufor; Opis funkcji radix_sort_number_auto Funkcja ta jest rozszerzeniem poprzednio opisywanej funkcji radix_sort_number, o możliwość sortowania wielu pozycji cyfr jednocześnie. Posiada ona dodatkowy parametr $ndigids, który to umożliwia. Jeśli ten parametr jest wywołany z domyślną wartością NULL oznacza to, że funkcja sama wyliczy najbardziej optymalną wartość zmiennej $ndigids. Sposób liczenia zmiennej $ndigids określający wielkość tablicy C jest bardzo podobny do sposobu liczenia wielkości tablicy C w funkcji radix_sort_int_auto (listing 3.12.). Różnica jest taka, że tu maksymalna możliwa wielkość tablicy C może wynieść 2*10 4-1 (czyli jest znacznie większa niż maksymalna tablica C w radix_sort_int_auto, gdzie ona wynosiła 4096) i dodatkowo maksymalna wielkość tej tablicy jest uzależniona od ilości danych do sortowania. Im mniejsza ilość danych tym mniejsza możliwa maksymalna tablica C. 26

Tabela 3.4. prezentuje maksymalną możliwą wielkość tablicy C w zależności od ilości sortowanych danych. Tab. 3.4. Funkcja radix_sort_number_auto wielkość tablicy C w zależność od ilości danych Ilość danych do sortowania Maksymalna możliwa wartość $ndigids Maksymalna możliwa wielkość tablicy C 1 3 999 2 2*10 2-1 = 199 3 999 39 999 3 2*10 3-1 = 1 999 40 000 wzwyż 4 2*10 4-1 = 19 999 Na listingu 3.15. pokazano sposób liczenia zmiennej $ndigids. Listing 3.15. if(!isset($ndigids)) $w = strlen($max); $ndigids = ceil($w/ceil($w/(isset($tab[39999])? 4 : (isset($tab[3999])? 3 : 2)))); Następnie są inicjalizowane trzy zmienne potrzebne do pracy tej funkcji: 1. $dzielnik oznacza dzielnik używany w operacji reszty z dzielenia, mającej na celu uzyskanie liczb z danych pozycji, wartość tej zmiennej jest wynikiem podniesienia liczby 10 do potęgi $ndigids (10, 100, 1000, 10 000) 2. $c oznacza tablicę C, której wielkość jest zależna od zmiennej $ndigids, na przykład dla $ndigids równego 2 tablica C ma klucze dla liczb w zakresie od -99 do 99 3. $bufor tablica bufor Następnie jest wykonywana pętla for, która jest prawie identyczna jak ta opisywana w funkcji radix_sort_number. Jedyna różnica polega na tym, że przy wyciąganiu liczb z danych pozycji, w poprzedniej funkcji dzielnikiem była liczba 10 a tu jest zmienna $dzielnik, co zostało pokazane na listingu 3.16. Listing 3.16. for($d = 1 ; $d <= $max ; $d *= $dzielnik) foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[ fmod(($w/$d),$dzielnik) ]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; 27

$suma += $w; foreach($tab as $w) $bufor[ ++$c[fmod(($w/$d),$dzielnik)] ] = $w; if($tab!== $bufor) $tab = $bufor; 3.3.7. Implementacja dla liczby zmiennoprzecinkowych Algorytm Radix Sort dla liczb zmiennoprzecinkowych został zaimplementowany w klasie sort w jednej funkcji radix_sort_float_auto. Ta funkcja jest prawie identyczna w swojej budowie, co poprzednio opisywana funkcja radix_sort_number_auto, poza dwiema różnicami. Pierwsza różnica to sposób liczenia zmiennej $ndigids (listing 3.17.), który jest uproszczony i jest zależny tylko od wielkości zbioru sortowanego, a nie jest zależny od długości liczby sortowanej, jak to miało miejsce w funkcji radix_sort_number_auto. Wynika to z faktu, że tu i tak najmniejsza długość liczby sortowanej będzie wynosić 14, bo taką dokładność mają liczby zmiennoprzecinkowe w PHP. Na listingu 3.17. pokazano sposób liczenia zmiennej $ndigids. Listing 3.17. if(!isset($ndigids)) $ndigids = isset($tab[11111])? 4 : (isset($tab[111])? 3 : 2); Druga różnica to zmienna $d w pętli for jest inicjalizowana wartością najmniejszą dla liczby zmiennoprzecinkowych czyli jedynka umieszczona 14 miejsc po przecinku (1.0E-14), jak pokazano na listingu 3.18. Listing 3.18. for($d = 1.0E-14 ; $d <= $max ; $d *= $dzielnik) foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[ fmod(($w/$d),$dzielnik) ]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; $suma += $w; foreach($tab as $w) 28

$bufor[ ++$c[fmod(($w/$d),$dzielnik)] ] = $w; if($tab!== $bufor) $tab = $bufor; 3.3.8. Implementacja dla łańcuchów Algorytm Radix Sort dla łańcuchów został zaimplementowany w klasie sort w dwóch funkcjach: radix_sort_łańcuch radix_sort_łańcuch_auto W tych dwóch funkcjach w przeciwieństwie do wcześniej opisywanych tablica C jest tablicą asocjacyjną, gdzie kluczami w tej tablicy są znaki lub łańcuchy. W poprzednio omawianych funkcjach, różnorodność zbioru dla jednej pozycji przy operacjach na bitach wynosiła 2 (cyfry 0, 1) a przy operacjach na cyfrach 10 (cyfry 0..9). Przy sortowaniu łańcuchów różnorodność zbioru dla jednej pozycji jest różna bo zależna od zastosowania ale jeśli przyjąć standardowe zastosowanie, to na przykład w polskim alfabecie są 32-ie litery, razy 2 (bo są duże i małe litery), plus dodatkowe znaki niealfanumeryczne takie jak spacja, myślnik, podkreślnik itd. co daje w rezultacie różnorodność zbioru około 70 znaków. Jest to zdecydowanie więcej niż w przypadku sortowania liczb. A im większa jest różnorodność zbioru tym większa jest tablica C i tym samym koszt obsługi takiej tablicy wzrasta, co przekłada się na gorszą wydajność. Niemniej jednak czasami może istnieć potrzeba sortowania niestandardowych łańcuchów, gdzie na przykład różnorodność zbioru to znaki od a-z. Aby umożliwić wzrost wydajności dla specyficznych rozwiązań, w obu tych funkcjach została wprowadzona zmienna $charlist, która zawiera listę znaków, które tworzą klucze w tablicy C. Dodatkowo przy definiowaniu tej zmiennej, kolejność znaków będzie odzwierciedlać kolejność sortowania, co daje dodatkowe możliwości dla specyficznych rozwiązań. Opis funkcji radix_sort_łańcuch Ta funkcja przyjmuje trzy argumenty: 1. $tab tablicę z danymi do sortowania (przez referencję) 2. $max zmienna określająca maksymalną długość łańcucha w zbiorze sortowanym, ta zmienna jeśli ma wartość domyślną NULL jest automatycznie wyliczana przez funkcję 3. $charlist lista znaków w łańcuchu tworzących tablicę C, znaki mogące występować w danych do sortowania muszą się znajdować w zmiennej $charlist, 29

ta zmienna jeśli ma wartość domyślną NULL, jest automatycznie wyliczana przez funkcję i tworzony jest wtedy łańcuch składający się ze 189-iu znaków Sposób liczenia zmiennych $max i $charlist został pokazany na listingu 3.19. Listing 3.19. if(!isset($max)) $max = strlen($tab[0]); foreach($tab as $w) if($max < strlen($w)) $max = strlen($w); /* zapis znakow w iso-8859-2 */ if(!isset($charlist)) $charlist = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`.'abcdefghijklmnopqrstuvwxyz ~ˇ Ł Ą Ş«Ż ± ł µ ąş»ľ ľżŕáâăäĺć'.'çčéęëěíîďđńňóôőö ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďñńňóôőö řůúűüýţ '; Następnie jest tworzona ze zmiennej $charlist tablica C, w taki sposób, że ma dodatkowy pierwszy klucz o wartości pustego łańcucha, który będzie służył do zliczania pozycji pustych lub nieistniejących. Tablica C w tej funkcji jest przystosowana tylko do zliczania jednej pozycji jednocześnie. Algorytm tworzenia tablicy C ma postać: str_split($charlist) dzieli $charlist na tablicę, gdzie każdy znak to osobna wartość w tablicy array_merge(array(''), str_split($charlist)) dodaje do wcześniej powstałej tablicy pusty łańcuch na początku array_flip( array_merge(array(''), str_split($charlist)) ) funkcja systemowa array_flip powoduje, że klucze stają się wartościami a wartości kluczami, co w efekcie daje pożądaną tablicę C Algorytm tworzenia tablicy C został pokazany na listingu 3.20. Listing 3.20. $c = array_flip( array_merge(array(''), str_split($charlist)) ); Następnie jest wykonywana pętla for, w której przy każdej iteracji jest realizowany algorytm Counting Sort. Różnica w stosunku to wcześniej opisywanych funkcji jest taka, że tu dodatkowo musi być sprawdzane, czy w danej pozycji dany znak istnieje. Wynika to z faktu, że sortowane mogą być łańcuchy o różnej długości. Jeśli dany znak nie istnieje, to wówczas jest zliczany przez klucz o pustym łańcuchu w tablicy C, jak pokazano na listingu 3.21. 30

Listing 3.21. for($pos = $max-1 ; $pos >= 0 ; --$pos) foreach($c as & $ref) $ref = 0; foreach($tab as $w) ++$c[ isset($w$pos)? $w$pos : '' ]; $suma = -1; foreach($c as & $ref) $w = $ref; $ref = $suma; $suma += $w; foreach($tab as $w) $bufor[ ++$c[ isset($w$pos)? $w$pos : '' ] ] = $w; if($tab!== $bufor) $tab = $bufor; Opis funkcji radix_sort_łańcuch_auto Funkcja radix_sort_łańcuch_auto jest rozszerzeniem poprzednio omawianej funkcji radix_sort_łańcuch o możliwość sortowania wielu pozycji jednocześnie. Dodatkowo ta funkcja sama liczy różnorodność zbioru sortowanego, dzięki czemu wykorzystanie tablicy C jest optymalne, a to z kolei przekłada się na większą wydajność. Ta funkcja przyjmuje cztery argumenty: 1. $tab tablicę z danymi do sortowania (przez referencję) 2. $max zmienna określająca maksymalną długość łańcucha w zbiorze sortowanym, ta zmienna jeśli ma wartość domyślną NULL jest automatycznie wyliczana przez funkcję 3. $nchars zmienna określająca ile znaków ma być jednocześnie sortowanych, ta zmienna jeśli ma wartość domyślną NULL jest automatycznie wyliczana przez funkcję 4. $charlist ta zmienna ma tu bardziej ograniczone zastosowanie niż w poprzednio omawianej funkcji radix_sort_łańcuch, ponieważ nie decyduje o kluczach powstających w tablicy C, a jedynie wskazuje na kolejność tych kluczy, innymi słowy tą zmienną można definiować jedynie kolejność znaków w posortowanym zbiorze Do stworzenia tablicy C, a także do obliczenia zmiennej $nchars (jeśli ma ona wartość NULL), potrzebna jest zmienna $tab_chars, w której wartości określają różnorodność zbioru sortowanego. Ponadto znaki w tej zmiennej muszą być ułożone w kolejności posortowanej. 31

Tworzenie zmiennej $tab_chars przebiega w taki sposób: $tab_chars = array(''); tworzona jest tablica z pierwszym elementem jako pustym łańcuchem, co ma takie samo zastosowanie jak w poprzednio omawianej funkcji, gdzie klucz w tablicy C z pustym łańcuchem służy do liczenia pozycji pustych lub nieistniejących count_chars( implode($tab) ) funkcja implode powoduje powstanie jednego wielkiego łańcucha ze wszystkich łańcuchów, które mają być sortowane, dzięki temu funkcja count_chars jest wywoływana tyko raz, funkcja count_chars ma za zadanie zwrócić tablicę z ilością wystąpień dla danego znaku następnie w pętli foreach jeśli ilość wystąpień dla danego znaku jest różna od zera następuje dodanie tego znaku do zmiennej $tab_chars Po tych trzech etapach wszystkie wymagane znaki znajdują się w tablicy $tab_chars, ale niekoniecznie są właściwie posortowane, co zostało pokazane na listingu 3.22. Listing 3.22. $tab_chars = array(''); foreach(count_chars( implode($tab) ) as $k => $w) if($w!== 0) $tab_chars[] = chr($k); Następnie, aby uzyskać właściwie posortowane znaki w zmiennej $tab_chars, są wykonywane takie kroki: array_merge(array(''), str_split($charlist)) tworzona jest tablica na podstawie zmiennej $charlist, gdzie dodatkowo pierwszym elementem jest pusty łańcuch, ta tablica ma przeważnie znacznie więcej znaków niż wymagane znaki do powstania tablicy C, ale za to są one odpowiednio posortowane za pomocą funkcji array_intersect jest wyciągana część wspólna dla uzyskanej w poprzednim kroku tablicy a zmienną $tab_chars, co doprowadza do sytuacji, że w zmiennej $tab_chars znaki są już właściwie posortowane Sposób uzyskania właściwie posortowanych znaków został pokazany na listingu 3.23. Listing 3.23. $tab_chars = array_intersect( ); array_merge(array(''), str_split($charlist)), $tab_chars 32

Po obliczeniu zmiennej $tab_chars, jest możliwe obliczanie zmiennej $nchars. Jednakże, aby zrozumieć, jak ta zmienna jest liczona, trzeba wiedzieć jaką wielkość będzie mieć tablica C w zależności od wielkości zmiennej $tab_chars oraz od ilości znaków, które będą jednocześnie sortowane. Tabela 3.5. przedstawia przykład dla zmiennej $tab_chars zdefiniowanej zbiorem, a, b. Ilość znaków jednocześnie sortowanych Tab. 3.5. Funkcja radix_sort_łańcuch_auto - przykład pokazujący wielkość tablicy C Klucze w tablicy C Wielkość tablicy C 1 a b 3 aa ab ba 2 a b bb 3 a b aa ab ba bb aaa aab aba abb baa bab bba bbb 3+4 = 7 3+4+8 = 15 Analizując przykład z tabeli 3.5, dla ilości znaków jednocześnie sortowanych równych jeden, tablica C ma dokładnie takie klucze jak zbiór znaków w zmiennej $tab_chars. Natomiast przy sortowaniu jednocześnie dwóch znaków, tablica C jest większa o iloczyn kartezjański ze zbioru składającego się z dwóch znaków a i b (bez znaku ), inaczej a, b x a, b, co jest równe wielkości 2 2. Dla sortowania jednocześnie trzech znaków jest rozważany iloczyn kartezjański a, b x a, b x a, b co daje w sumie dodatkową wielkość o 2 3. Podsumowując można napisać wzór iteracyjny na wielkość tablicy C, gdzie: n oznacza ilość dla różnorodności zbioru (ilość znaków w zmiennej $tab_chars) k ilość jednocześnie sortowanych znaków n + (n-1) 2 + (n-1) 3 + + (n-1) k wzór 3.1. Wzór zostanie użyty do obliczenia zmiennej $nchars. Algorytm liczenia tej zmiennej: najpierw jest liczona zmienna $max_count_c, która oznacza maksymalną wielkość do zaakceptowania dla tablicy C, ta wielkość jest ustalana względem ilości danych do sortowania i została ustalona doświadczalnie następnie jest wyznaczana zmienna $max_nchars, na podstawie wyżej pokazanego wzoru na wielkość tablicy C, gdzie $suma oznacza wielkość tablicy C dla danego $max_nchars a $w oznacza ilość dla tablicy $tab_chars bez znaku pustego łańcucha następnie jest wyznaczany zmienna $nchars w sposób analogiczny jak to było robione w poprzednich funkcjach Sposób liczenia zmiennej $nchars został pokazany na listingu 3.24. 33