Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych. Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2013 mgr
Jerzy Wałaszek
|
Sortowanie danych jest jednym z podstawowych problemów programowania komputerów, z którym prędzej czy później spotka się każdy programista. Poniżej przedstawiamy tylko nieliczne dziedziny, w których występuje potrzeba sortowania danych:
Wbrew obiegowym opiniom nie ma "idealnego" i "uniwersalnego" algorytmu sortującego - jeden algorytm jest lepszy w jednej sytuacji, drugi w innej. Dlatego dokładna znajomość własności algorytmów sortujących pozwala na tworzenie naprawdę efektywnych aplikacji.
D = { 1, 3, 3, 5, 7, 7, 8, 9 }
jest posortowany rosnąco, ponieważ pomiędzy każdymi dwoma kolejnymi elementami zachodzi relacja:
element poprzedni jest mniejszy lub równy od elementu następnego
Odwrotnie, zbiór:
D = { 9, 8, 6, 6, 4, 2, 1, 1, 0 }
jest posortowany malejąco, ponieważ pomiędzy każdymi dwoma kolejnymi elementami zachodzi relacja:
element poprzedni jest większy lub równy od elementu następnego.
Oczywiście zbiór wcale nie musi składać się z liczb (chociaż tak naprawdę każdy rodzaj danych komputerowych w końcu sprowadza się do liczb, gdyż wewnętrznie jest reprezentowany przez kody binarne). W takim przypadku należy określić dla każdego elementu tzw. klucz (ang. key), wg którego dokonywane jest sortowanie. Ponieważ klucz jest liczbą, zatem obowiązują dla niego podane wyżej zasady kolejności elementów. Na przykład dla tekstów kluczem mogą być kody poszczególnych znaków. Większość języków programowania posiada operatory porównywania ciągu znaków (problemem może być sortowanie wg zasad języka polskiego - nie wystarczy wtedy porównywać kodów znakowych, ponieważ kody polskich literek ą, ć, Ć itd. są zwykle większe od kodów liter alfabetu łacińskiego, ale i ten problem daje się z powodzeniem rozwiązać przez odpowiedni dobór kluczy).
Przez sortowanie będziemy rozumieć taką zmianę kolejności elementów zbioru nieuporządkowanego (permutację), aby w wyniku spełniały one założony porządek.
O(n) | - | Algorytm o liniowej zależności czasu wykonania od ilości danych. Dwukrotny wzrost liczby przetwarzanych danych powoduje dwukrotny wzrost czasu wykonania. Tego typu złożoność powstaje, gdy dla każdego elementu należy wykonać stałą liczbę operacji. |
O(n2) | - | Algorytm, w którym czas wykonania rośnie z kwadratem liczby przetwarzanych elementów. Dwukrotny wzrost liczby danych powoduje czterokrotny wzrost czasu wykonania. Tego typu złożoność powstaje, gdy dla każdego elementu należy wykonać ilość operacji proporcjonalną do liczby wszystkich elementów. |
O(n log n) | - | Dobre algorytmy sortujące mają taką właśnie złożoność obliczeniową. Czas wykonania przyrasta dużo wolniej od wzrostu kwadratowego. Tego typu złożoność powstaje, gdy zadanie dla n elementów można rozłożyć na dwa zadania zawierające po połowie elementów. |
O(n!) O(an) |
- | Bardzo pesymistyczne algorytmy, czas wykonania rośnie szybko ze wzrostem liczby elementów wejściowych, czyli znalezienie rozwiązania może zająć najszybszym komputerom całe wieki lub tysiąclecia. Takich algorytmów należy unikać jak ognia ! |
Zapis O( ) określamy mianem klasy złożoności obliczeniowej algorytmu. Klasa czasowej złożoności obliczeniowej umożliwia porównywanie wydajności różnych algorytmów sortujących. Z reguły proste algorytmy posiadają wysoką złożoność obliczeniową - długo dochodzą do wyniku końcowego. Algorytmy bardziej skomplikowane posiadają mniejszą złożoność obliczeniową - szybko dochodzą do rozwiązania. Złożoność obliczeniowa wszystkich algorytmów sortujących została dokładnie oszacowana - my również dokonujemy tego w niniejszym opracowaniu.
Przykład:
Załóżmy, iż mamy program sortujący dane zbudowany na bazie algorytmu
sortującego o klasie złożoności obliczeniowej
Lp. | n | Czas obliczeń |
---|---|---|
1. | 100 | = 1 sekunda |
2. | 1.000 | = 100 sekund = 1 minuta 40 sekund |
3. | 10.000 | = 10.000 sekund = 2 godziny 46 minut 40 sekund |
4. | 100.000 | = 1.000.000 sekund = 11 dni 13 godzin 46 minut 40 sekund |
5. | 1.000.000 | = 100.000.000 sekund = 3 lata 2 miesiące 9 godzin 46 minut 40 sekund |
6. | 10.000.000 | = 1 x 1010 sekund = 317 lat 1 miesiąc 4 dni 17 godzin 46 minut 40 sekund |
Widzimy więc, iż algorytm ten spisuje się dobrze tylko przy niewielkiej
liczbie elementów. Gdy liczba sortowanych elementów jest duża, czas oczekiwania
na rozwiązanie może być nie do zaakceptowania. Dlatego właśnie informatycy
poświęcili tak dużo swojego wysiłku na opracowanie odpowiednio szybkich
algorytmów sortujących (te najszybsze mają klasę złożoności
Oprócz złożoności czasowej rozważa się również złożoność pamięciową. Określa ona ilość zasobów komputera, których wymaga dany algorytm w zależności od liczby danych wejściowych. Tutaj także ma zastosowanie notacja omikron. Przy określaniu złożoności algorytmu należy wziąć pod uwagę oba typy złożoności obliczeniowej.
Ze względu na złożoność pamięciową algorytmy sortujące dzielimy na dwie podstawowe grupy:
Algorytmy sortujące dzieli się również na dwie grupy:
Algorytm sortowania bąbelkowego jest jednym z najstarszych algorytmów sortujących. Zasada działania opiera się na cyklicznym porównywaniu par sąsiadujących elementów i zamianie ich kolejności w przypadku niespełnienia kryterium porządkowego zbioru. Operację tę wykonujemy dotąd, aż cały zbiór zostanie posortowany.
Algorytm sortowania bąbelkowego przy porządkowaniu zbioru
nieposortowanego ma klasę czasowej złożoności obliczeniowej równą
Jako przykład działania algorytmu sortowania bąbelkowego
posortujemy przy jego pomocy
Obieg | Zbiór | Opis operacji |
---|---|---|
1 |
5 4 3 2 1
|
Rozpoczynamy od pierwszej pary, która wymaga wymiany elementów |
4 5 3 2 1 |
Druga para też wymaga zamiany elementów | |
4 3 5 2 1 |
Wymagana wymiana elementów | |
4 3 2 5 1
|
Ostatnia para również wymaga wymiany elementów | |
4 3 2 1 5
|
Stan po pierwszym obiegu. Zwróć uwagę, iż najstarszy element (5) znalazł się na końcu zbioru, a najmłodszy (1) przesunął się o jedną pozycję w lewo. | |
2 |
4 3 2 1 5
|
Para wymaga wymiany |
3 4 2 1 5
|
Para wymaga wymiany | |
3 2 4 1 5
|
Para wymaga wymiany | |
3 2 1 4 5
|
Elementy są w dobrej kolejności, zamiana nie jest konieczna. | |
3 2 1 4 5
|
Stan po drugim obiegu. Zwróć uwagę, iż najmniejszy element (1) znów przesunął się o jedną pozycję w lewo. Z obserwacji tych można wywnioskować, iż po każdym obiegu najmniejszy element wędruje o jedną pozycję ku początkowi zbioru. Najstarszy element zajmuje natomiast swe miejsce końcowe. | |
3 |
3 2 1 4 5
|
Para wymaga wymiany |
2 3 1 4 5
|
Para wymaga wymiany | |
2 1 3 4 5
|
Dobra kolejność | |
2 1 3 4 5
|
Dobra kolejność | |
2 1 3 4 5 |
Stan po trzecim obiegu. Wnioski te same. | |
4 |
2 1 3 4 5
|
Para wymaga wymiany |
1 2 3 4 5
|
Dobra kolejność | |
1 2 3 4 5
|
Dobra kolejność | |
1 2 3 4 5
|
Dobra kolejność | |
1 2 3 4 5
|
Zbiór jest posortowany. Koniec |
Posortowanie naszego zbioru wymaga 4 obiegów. Jest to oczywiste:
w przypadku najbardziej niekorzystnym najmniejszy element znajduje się na samym
końcu zbioru wejściowego. Każdy obieg przesuwa go o jedną pozycję w kierunku
początku zbioru. Takich przesunięć należy wykonać
Uwaga:Algorytm sortowania bąbelkowego jest uważany za bardzo
zły algorytm sortujący. Można go stosować tylko dla niewielkiej liczby
elementów w sortowanym zbiorze (do około 5000).
Przy większych zbiorach czas sortowania może być zbyt długi. |
n | - liczba elementów w sortowanym zbiorze, n N |
d[ ] | - zbiór n-elementowy, który będzie sortowany. Elementy zbioru mają indeksy od 0 do n - 1. |
d[ ] | - posortowany zbiór n-elementowy. |
i, j | - zmienne sterujące pętli, i, j Î N |
K01: | Dla j = 0,1,...,n - 2: wykonuj K02 |
K02: | Dla i = 0,1,...,n - 2: jeśli d[i] > d[i + 1], to d[i] ↔ d[i + 1] |
K03: | Zakończ |
Idea algorytmu sortowania przez wybór jest bardzo prosta. Załóżmy, iż chcemy posortować zbiór liczbowy rosnąco. Zatem element najmniejszy powinien znaleźć się na pierwszej pozycji. Szukamy w zbiorze elementu najmniejszego i wymieniamy go z elementem na pierwszej pozycji. W ten sposób element najmniejszy znajdzie się na swojej docelowej pozycji.
W identyczny sposób postępujemy z resztą elementów należących do zbioru. Znów wyszukujemy element najmniejszy i zamieniamy go z elementem na drugiej pozycji. Otrzymamy dwa posortowane elementy. Procedurę kontynuujemy dla pozostałych elementów dotąd, aż wszystkie będą posortowane.
Algorytm sortowania przez wybór posiada klasę czasowej złożoności obliczeniowej równą O(n2). Sortowanie odbywa się w miejscu.
Przykład:
Dla przykładu posortujmy tą metodą zbiór {4 7 2 9 3}. Kolorem zielonym oznaczyliśmy elementy zbioru, które są już posortowane.
Zbiór | Opis operacji |
---|---|
4 7 2 9 3 |
Wyszukujemy najmniejszy element w zbiorze. Jest nim liczba 2. |
2 7 4 9 3 |
Znaleziony element minimalny wymieniamy z pierwszym elementem zbioru - liczbą 4 |
2 7 4 9 3 |
Wśród pozostałych elementów wyszukujemy element najmniejszy. Jest nim liczba 3. |
2 3 4 9 7 |
Znaleziony element minimalny wymieniamy z drugim elementem zbioru - liczbą 7. |
2 3 4 9 7 |
Znajdujemy kolejny element minimalny - liczbę 4. |
2 3 4 9 7 |
Wymieniamy go z samym sobą - element ten nie zmienia zatem swojej pozycji w zbiorze. |
2 3 4 9 7 |
Znajdujemy kolejny element minimalny |
2 3 4 7 9 |
Wymieniamy go z liczbą 9 |
2 3 4 7 9 |
Ostatni element jest zawsze na właściwej pozycji. Sortowanie zakończone |
Podana metoda sortuje zbiór rosnąco. Jeśli chcemy posortować zbiór malejąco, to zamiast elementu minimalnego poszukujemy elementu maksymalnego. Pozostała część procedury sortującej nie ulega zmianie.
n | - liczba elementów w sortowanym zbiorze, n N |
d[ ] | - zbiór n-elementowy, który będzie sortowany. Elementy zbioru mają indeksy od 0 do n - 1. |
d[ ] | - posortowany zbiór n-elementowy. |
i, j | - zmienne sterujące pętli, i, j N |
pmin | - pozycja elementu minimalnego w zbiorze d[ ], pmin N |
K01: | Dla j = 0,1, ..., n - 2: wykonuj K02...K04 |
K02: | pmin ← j |
K03: | Dla i = j + 1,j + 2, ...,n - 1: jeśli d[i] < d[pmin], to pmin ← i |
K04: | d[j] ↔ d[pmin] |
K05: | Zakończ |
Algorytm sortowania przez wstawianie można porównać do sposobu układania kart pobieranych z talii. Najpierw bierzemy pierwszą kartę. Następnie pobieramy kolejne, aż do wyczerpania talii. Każdą pobraną kartę porównujemy z kartami, które już trzymamy w ręce i szukamy dla niej miejsca przed pierwszą kartą starszą (młodszą w przypadku porządku malejącego). Gdy znajdziemy takie miejsce, rozsuwamy karty i nową wstawiamy na przygotowane w ten sposób miejsce (stąd pochodzi nazwa algorytmu - sortowanie przez wstawianie, ang. Insertion Sort). Jeśli nasza karta jest najstarsza (najmłodsza), to umieszczamy ją na samym końcu. Tak porządkujemy karty. A jak przenieść tę ideę do świata komputerów i zbiorów liczbowych?
Algorytm sortowania przez wstawianie będzie składał się z dwóch
pętli. Pętla główna (zewnętrzna) symuluje pobieranie
kart, czyli w tym wypadku elementów zbioru. Odpowiednikiem kart na ręce jest
tzw. lista uporządkowana
Algorytm sortowania przez wstawianie posiada klasę czasowej
złożoności obliczeniowej równą
Najważniejszą operacją w opisywanym algorytmie sortowania jest wstawianie wybranego elementu na listę uporządkowaną. Zasady są następujące:
Przykład:
Dla przykładu wstawmy wg opisanej metody pierwszy element
zbioru listę uporządkowaną utworzoną z pozostałych elementów
Zbiór | Opis operacji |
---|---|
8 4 5 6 9
|
Element 8 znajduje się tuż przed listą uporządkowaną. |
8 4 5 6 9 |
Wybieramy ze zbioru element 8. Zajmowane przez niego miejsce staje się puste. |
8 4 5 6 9 |
Porównujemy 8 z pierwszym elementem listy uporządkowanej, z liczbą 4. |
8 4 <<< 5 6 9 |
Ponieważ element 4 jest mniejszy od elementu wybranego 8, przesuwamy go na puste miejsce. Zwróć uwagę, iż puste miejsce wędruje w kierunku końca listy uporządkowanej. |
8 4 5 6 9 |
Porównujemy 8 z kolejnym elementem listy uporządkowanej, z liczbą 5. |
8 4 5 <<< 6 9 |
5 jest5 mniejsze od 8, zatem wędruje na puste miejsce, które przesuwa się przed kolejny element listy uporządkowanej, liczbę 6. |
8 4 5 6 9 |
Porównujemy 8 z 6. |
8 4 5 6 <<< 9 |
6 jest mniejsze od 8, wędruje na puste miejsce. |
8 4 5 6 9 |
Porównujemy 8 z 9. |
4 5 6 8 9 |
Tym razem element wybrany wędruje na puste miejsce, ponieważ jest mniejszy od elementu 9 listy uporządkowanej. Operacja wstawiania jest zakończona. Lista rozrasta się o jeden element. |
Przykład:
Wykorzystajmy podane informacje do posortowania opisywaną metodą
zbioru
Zbiór | Opis operacji |
---|---|
7 3 8 5 2
|
Ostatni element jest zalążkiem listy uporządkowanej. |
5 7 3 8 2 |
Ze zbioru wybieramy element leżący tuż przed listą uporządkowaną. |
5 7 3 8 2 |
Wybrany element porównujemy z elementem listy. |
5 7 3 8 2 <<< |
Ponieważ element listy jest mniejszy od elementu wybranego, to przesuwamy go na puste miejsce. |
7 3 8 2 5 |
Na liście nie ma już więcej elementów do porównania, więc element wybrany wstawiamy na puste miejsce. Lista uporządkowana zawiera już dwa elementy. |
8 7 3 2 5 |
Ze zbioru wybieramy 8 |
8 7 3 2 5 |
8 porównujemy z 2 |
8 7 3 2 <<< 5 |
2 jest mniejsze, zatem przesuwamy je na puste miejsce. |
8 7 3 2 5 |
8 porównujemy z 5 |
8 7 3 2 5 <<< |
5 jest mniejsze, przesuwamy je na puste miejsce |
7 3 2 5 8 |
Lista nie zawiera więcej elementów, zatem 8 wstawiamy na puste miejsce. Na liście uporządkowanej mamy już trzy elementy. |
3 7 2 5 8 |
Ze zbioru wybieramy 3 |
3 7 2 5 8 |
3 porównujemy z 2 |
3 7 2 <<< 5 8 |
2 jest mniejsze, wędruje zatem na puste miejsce |
3 7 2 5 8 |
3 porównujemy z 5. |
7 2 3 5 8 |
Tym razem mniejszy jest element wybrany. Znaleźliśmy jego miejsce na liście, więc wstawiamy go na puste miejsce. Lista zawiera już 4 elementy. |
7 2 3 5 8 |
Ze zbioru wybieramy ostatni element - liczbę 7. |
7 2 3 5 8 |
7 porównujemy z 2 |
7 2 <<< 3 5 8 |
2 jest mniejsze, przesuwamy je na puste miejsce |
7 2 3 5 8 |
7 porównujemy z 3 |
7 2 3 <<< 5 8 |
3 jest mniejsze, przesuwamy je na puste miejsce |
7 2 3 5 8 |
7 porównujemy z 5 |
7 2 3 5 <<< 8 |
5 jest mniejsze, przesuwamy je na puste miejsce |
7 2 3 5 8 |
7 porównujemy z 8 |
2 3 5 7 8 |
Element wybrany jest mniejszy, wstawiamy go na puste miejsce. Lista uporządkowana objęła już cały zbiór. Sortowanie jest zakończone. |
n | - liczba elementów w sortowanym zbiorze, n N |
d[ ] | - zbiór n-elementowy, który będzie sortowany. Elementy zbioru mają indeksy od 0 do n - 1. |
d[ ] | - posortowany zbiór n-elementowy. |
i, j | - zmienne sterujące pętli, i, j N |
x | - zawiera wybrany ze zbioru element. |
K01: | Dla j = n - 2, n - 3, ...,0: wykonuj K02...K04 |
K02: | x ← d[j]; i ← j + 1 |
K03: | Dopóki ( i < n ) ∧ ( x > d[i] ): wykonuj d[i - 1] ← d[i]; i ← i + 1 |
K04: | d[i - 1] ← x |
K05: | Zakończ |
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe