![]() |
Autor artykułu: mgr Jerzy Wałaszek |
©2014 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. |
|||||||||||||||||||||||||||||||||
Porządek w zbiorzeZbiór posortowany to taki zbiór, w
którym kolejne elementy są poukładane w pewnym porządku
(kolejności). Porządek ten możemy określić za
pomocą relacji ≥ lub ≤
(albo dowolnej innej relacji porządkowej, która jednoznacznie
wyznacza kolejność elementów w zbiorze). Równość jest konieczna w
przypadku, gdy elementy zbioru mogą przyjmować takie same wartości. Na
przykład zbiór liczb:
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. |
|||||||||||||||||||||||||||||||||
Czasowa złożoność obliczeniowaKolejnym zagadnieniem, które powinniśmy omówić we wstępie, jest
tzw.
czasowa złożoność obliczeniowa
(ang. computational complexity) algorytmu sortującego
(istnieje również złożoność pamięciowa). Określa ona statystycznie
czas wykonywania algorytmu w zależności od liczby danych wejściowych.
Czasowa złożoność obliczeniowa wyrażana jest liczbą tzw.
operacji dominujących, czyli takich, które mają bezpośredni wpływ na
czas wykonywania algorytmu. Dzięki takiemu podejściu uniezależniamy czasową
złożoność obliczeniową od szybkości komputera, na którym dany algorytm jest
realizowany. Złożoność obliczeniową charakteryzujemy przy pomocy tzw.
notacji
O (omikron). Oto
odpowiednie przykłady:
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.
Załóżmy, iż mamy program sortujący dane zbudowany na bazie
algorytmu sortującego o klasie złożoności obliczeniowej
O(n2).
Sto elementów jest sortowane w czasie 1 sekundy. Ile czasu zajmie
posortowanie za pomocą tego programu zbioru o tysiącu elementach?
Odpowiedź brzmi -
100
sekund. Ponieważ ilość danych wzrosła
10
razy, to czas obliczeń wzrósł
102,
czyli
100
razy. Poniżej przedstawiamy odpowiednią tabelkę.
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 O(n log n) ).
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 przy porządkowaniu zbioru nieposortowanego ma klasę czasowej złożoności obliczeniowej równą O(n2). Sortowanie odbywa się w miejscu.
Jako przykład działania algorytmu sortowania bąbelkowego posortujemy przy
jego pomocy 5-cio elementowy zbiór liczb
{5 4 3 2 1},
który wstępnie jest posortowany w kierunku odwrotnym, co możemy uznać za
przypadek najbardziej niekorzystny, ponieważ wymaga przestawienia wszystkich
elementów.
| 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ć n - 1 (n - ilość elementów w zbiorze).
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
|
| 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
|
| 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.
4 7 2 9 3
2 7 4 9 3
2 7 4 9 3
2 3 4 9 7
2 3 4 9 7
2 3 4 9 7
2 3 4 9 7
2 3 4 7 9
2 3 4 7 9
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
|
| 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
|
| pmin | - pozycja elementu minimalnego w zbiorze d[ ], pmin
|
| 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 (ang. sorted list), którą sukcesywnie będziemy tworzyli na końcu zbioru (istnieje też odmiana algorytmu umieszczająca listę uporządkowaną na początku zbioru). Pętla sortująca (wewnętrzna) szuka dla pobranego elementu miejsca na liście uporządkowanej. Jeśli takie miejsce zostanie znalezione, to elementy listy są odpowiednio rozsuwane, aby tworzyć miejsce na nowy element i element wybrany przez pętlę główną trafia tam. W ten sposób lista uporządkowana rozrasta się. Jeśli na liście uporządkowanej nie ma elementu większego od wybranego, to element ten trafia na koniec listy. Sortowanie zakończymy, gdy pętla główna wybierze wszystkie elementy zbioru.
Algorytm sortowania przez wstawianie posiada klasę czasowej złożoności obliczeniowej równą O(n2). Sortowanie odbywa się w miejscu.
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 {8 4 5 6 9}. Elementy listy uporządkowanej zaznaczyliśmy kolorem zielonym. Puste miejsce zaznaczyliśmy kolorem białym:
8 4 5 6 9
8 4 5 6 9
8 4 5 6 9
8 4 <<< 5 6 9
8 4 5 6 9
8 4 5 <<< 6 9
8 4 5 6 9
8 4 5 6 <<< 9
8 4 5 6 9
4 5 6 8 9
Przykład:
Wykorzystajmy podane informacje do posortowania opisywaną metodą zbioru { 7 3 8 5 2 }.
7 3 8 5 2
5 7 3 8 2
5 7 3 8 2
5 7 3 8 2 <<<
7 3 8 2 5
8 7 3 2 5
8 7 3 2 5
8 7 3 2 <<< 5
8 7 3 2 5
8 7 3 2 5 <<<
7 3 2 5 8
3 7 2 5 8
3 7 2 5 8
3 7 2 <<< 5 8
3 7 2 5 8
7 2 3 5 8
7 2 3 5 8
7 2 3 5 8
7 2 <<< 3 5 8
7 2 3 5 8
7 2 3 <<< 5 8
7 2 3 5 8
7 2 3 5 <<< 8
7 2 3 5 8
2 3 5 7 8
| n | - liczba elementów w sortowanym zbiorze, 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
|
| 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.