Sortowania: Sortowanie – jeden z podstawowych problemów

Transkrypt

Sortowania: Sortowanie – jeden z podstawowych problemów
Sortowania:
Sortowanie – jeden z podstawowych problemów informatyki, polegający na uporządkowaniu
zbioru danych względem pewnych cech charakterystycznych każdego elementu tego zbioru.
Szczególnym przypadkiem jest sortowanie względem wartości każdego elementu, np. sortowanie
liczb, słów itp. Algorytmy sortowania są stosowane w celu uporządkowania danych, umożliwienia
stosowania wydajniejszych algorytmów (np. wyszukiwania) i prezentacji danych w sposób
czytelniejszy dla człowieka.
Sortowanie bąbelkowe prosta metoda sortowania o złożoności czasowej O(n^2) i pamięciowej
O(1). Polega na porównywaniu dwóch kolejnych elementów i zamianie ich kolejności, jeżeli
zaburza ona porządek, w jakim się sortuje tablicę. Sortowanie kończy się, gdy podczas kolejnego
przejścia nie dokonano żadnej zmiany. Algorytm można rozbudować tak, by czas optymistyczny
był lepszy. Najłatwiejsze jest dodanie flagi informującej, czy w danej iteracji doszło do zmiany.
Flaga jest zerowana na wejściu w przebiegu pętli, w przypadku natrafienia na zmianę jest
podnoszona, a po wykonaniu przejścia sprawdzana. Jeśli nie było zmian, to sortowanie jest
zakończone. Modyfikacja ta wprawdzie wydłuża czas wykonania jednego przejścia przez pętlę
(gdyż trzeba wyzerować flagę, podnieść ją i sprawdzić), jednakże w wariancie optymistycznym
(ciąg częściowo posortowany) może zaoszczędzić iteracji, przez co algorytm będzie działać
szybciej.
Sortowanie przez wstawianie (ang. Insert Sort, Insertion Sort) - jeden z najprostszych algorytmów
sortowania, którego zasada działania odzwierciedla sposób w jaki ludzie ustawiają karty - kolejne
elementy wejściowe są ustawiane na odpowiednie miejsca docelowe. Jest efektywny dla niewielkiej
liczby elementów, jego złożoność wynosi O(n2). Pomimo tego, że jest znacznie mniej wydajny od
algorytmów takich jak quicksort czy heapsort, posiada pewne zalety:
• jest wydajny dla danych wstępnie posortowanych
• jest wydajny dla zbiorów o niewielkiej liczebności
• jest stabilny
sortowanie przez scalanie (ang. merge sort), to rekurencyjny algorytm sortowania danych,
stosujący metodę dziel i zwyciężaj. Odkrycie algorytmu przypisuje się Johnowi von Neumannowi.
Wyróżnić można trzy podstawowe kroki[1]:
• Podziel zestaw danych na dwie równe części[2]
• Zastosuj sortowanie przez scalanie dla każdej z nich oddzielnie, chyba że pozostał już tylko
jeden element;
• Połącz posortowane podciągi w jeden. (podczas tej operacji należy sprawdzać z którego
ciągu trzeba wziąć element tutaj następuje właściwe sortowanie)
Sortowanie przez zliczanie – metoda sortowania danych, która polega na sprawdzeniu ile
wystąpień kluczy mniejszych od danego występuje w sortowanej tablicy. Algorytm zakłada, że
klucze elementów należą do skończonego zbioru (np. są to liczby całkowite z przedziału 0..100), co
ogranicza możliwości jego zastosowania. Główną zaletą tej metody jest liniowa złożoność
obliczeniowa algorytmu – O(n+k)[2] (n – oznacza liczebność zbioru, k – rozpiętość danych, czyli w
przypadku liczb całkowitych: powiększoną o 1 różnicę między maksymalną a minimalną wartością,
np. rozpiętość liczb w Dużym Lotku wynosi (49-1) + 1 = 49). Największymi ograniczeniami
algorytmu są konieczność uprzedniej znajomości zakresu danych i złożoność pamięciowa.
Sortowanie kubełkowe (ang. bucket sort) – jeden z algorytmów sortowania, najczęściej stosowany,
gdy liczby w zadanym przedziale są rozłożone jednostajnie, ma on wówczas złożoność Θ(n). W
przypadku ogólnym pesymistyczna złożoność obliczeniowa tego algorytmu wynosi O(n²). Pomysł
takiego sortowania podali po raz pierwszy w roku 1956 E. J. Issac i R. C. Singleton.
Idea działania algorytmu sortowania kubełkowego:
1. Podziel zadany przedział liczb na k podprzedziałów (kubełków) o równej długości.
2. Przypisz liczby z sortowanej tablicy do odpowiednich kubełków.
3. Sortuj liczby w niepustych kubełkach.
4. Wypisz po kolei zawartość niepustych kubełków.
Sortowanie Shella (ang. Shellsort) – algorytm sortowania działający w miejscu i korzystający z
porównań elementów. Stanowi uogólnienie sortowania przez wstawianie, dopuszczające
porównania i zamiany elementów położonych daleko od siebie. Jego pierwszą wersję opublikował
w 1959 roku Donald Shell. Złożoność czasowa sortowania Shella w dużej mierze zależy od użytego
w nim ciągu odstępów. Wyznaczenie jej dla wielu stosowanych w praktyce wariantów tego
algorytmu pozostaje problemem otwartym.
Sortowanie Shella to algorytm wieloprzebiegowy. Kolejne przebiegi polegają na sortowaniu przez
proste wstawianie elementów oddalonych o ustaloną liczbę miejsc h, czyli tak zwanym hsortowaniu. Sortowanie Shella nie jest stabilne, czyli może nie zachowywać wejściowej kolejności
elementów o równych kluczach. Wykazuje ono zachowanie naturalne, czyli krótszy czas sortowania
dla częściowo uporządkowanych danych wejściowych. Z sortowania Shella rzadko się obecnie
korzysta w poważnych zastosowaniach. Wykonuje ono więcej działań niż sortowanie szybkie,
ponadto częściej od niego nie trafia w pamięć podręczną procesora przy odczytach z pamięci. Ze
względu na stosunkowo krótki kod i nieużywanie stosu bywa używane w implementacjach
systemów wbudowanych.
Sortowanie szybkie (ang. quicksort) – jeden z popularnych algorytmów sortowania działających
na zasadzie "dziel i zwyciężaj". Sortowanie QuickSort zostało wynalezione w 1962 przez C.A.R.
Hoare'a. Algorytm sortowania szybkiego jest wydajny: jego średnia złożoność obliczeniowa jest
rzędu O(n \log n). Ze względu na szybkość i prostotę implementacji jest powszechnie używany.
Jego implementacje znajdują się w bibliotekach standardowych wielu środowisk programowania.
Opis algorytmu: Z tablicy wybiera się element rozdzielający, po czym tablica jest dzielona na dwa
fragmenty: do początkowego przenoszone są wszystkie elementy nie większe od rozdzielającego,
do końcowego wszystkie większe. Potem sortuje się osobno początkową i końcową część tablicy.
Rekursja kończy się, gdy kolejny fragment uzyskany z podziału zawiera pojedynczy element, jako
że jednoelementowa tablica nie wymaga sortowania. Złożoność algorytmu zależy od wyboru
elementu rozdzielającego; jeśli podziały są zrównoważone algorytm jest tak szybki jak sortowanie
przez scalanie czyli O(n·logn); w przeciwnym przypadku może działać tak wolno jak sortowanie
przez wstawianie (O(n2)). Średni czas działania przy losowym wyborze elementu rozdzielającego,
dorównuje przypadkowi optymistycznemu. Wysoka wydajność algorytmu sortowania szybkiego
predestynuje go do przetwarzania dużych tablic. Takie zastosowanie wymaga jednak zwrócenia
szczególnej uwagi na głębokość rekursji. Głębokość rekursji wiąże się bowiem z wykorzystaniem
stosu maszynowego. Pomimo wszelkich zalet, pozostaje jednak, zazwyczaj znikome,
prawdopodobieństwo zajścia przypadku pesymistycznego, w którym złożoność czasowa wynosi
O(n^2). Jeśli chcemy mieć pewność wykonania sortowania w czasie nie dłuższym niż O(n \log_2
n), należy uzupełnić algorytm o poszukiwanie przybliżonej mediany, czyli elementu dzielącego
posortowaną tablicę.
Sortowanie przez kopcowanie (ang. heapsort) – jeden z algorytmów sortowania, choć
niestabilny, to jednak szybki i niepochłaniający wiele pamięci (złożoność czasowa wynosi O(n \log
n), a pamięciowa –O(n), przy czym jest to rozmiar sortowanych danych, złożoność pamięciowa
dodatkowych struktur wynosi O(1); jest to zatem algorytm sortowania w miejscu). Jest on w
praktyce z reguły nieco wolniejszy od sortowania szybkiego, lecz ma lepszą pesymistyczną
złożoność czasową (przez co jest odporny np. na atak za pomocą celowo spreparowanych danych,
które spowodowałyby jego znacznie wolniejsze działanie). Podstawą algorytmu jest użycie kolejki
priorytetowej zaimplementowanej w postaci binarnego kopca zupełnego. Zasadniczą zaletą kopców
jest stały czas dostępu do elementu maksymalnego (lub minimalnego) oraz logarytmiczny czas
wstawiania i usuwania elementów; ponadto łatwo można go implementować w postaci tablicy.
Algorytm sortowania przez kopcowanie składa się z dwóch faz. W pierwszej sortowane elementy
reorganizowane są w celu utworzenia kopca. W drugiej zaś dokonywane jest właściwe sortowanie.
Podstawową zaletą algorytmu jest to, że do stworzenia kopca wykorzystać można tę samą tablicę, w
której początkowo znajdują się nieposortowane elementy. Dzięki temu uzyskuje się stałą złożoność
pamięciową. Początkowo do kopca należy tylko pierwszy element w tablicy. Następnie kopiec
rozszerzany jest o drugą, trzecią i kolejne pozycje tablicy, przy czym przy każdym rozszerzeniu,
nowy element jest przemieszczany w górę kopca, tak aby spełnione były relacje pomiędzy węzłami.