Serwis Edukacyjny w I-LO w Tarnowie ![]() Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2023 mgr Jerzy Wałaszek |
Algorytm sortowania szybkiego opiera się na strategii "dziel i zwyciężaj" (ang. divide and conquer), którą możemy krótko scharakteryzować w trzech punktach:
Idea sortowania szybkiego jest następująca:
DZIEL : | najpierw sortowany zbiór dzielimy na dwie części w taki sposób, aby wszystkie elementy leżące w pierwszej części (zwanej lewą partycją) były mniejsze lub równe od wszystkich elementów drugiej części zbioru (zwanej prawą partycją). |
ZWYCIĘŻAJ : | każdą z partycji sortujemy rekurencyjnie tym samym algorytmem. |
POŁĄCZ : | połączenie tych dwóch partycji w jeden zbiór daje w wyniku zbiór posortowany. |
![]() prof. Tony Hoare |
Sortowanie szybkie zostało wynalezione przez angielskiego informatyka,
profesora
Tony'ego Hoare'a w latach 60-tych ubiegłego wieku. W
przypadku typowym algorytm ten jest najszybszym algorytmem sortującym z klasy
złożoności obliczeniowej
Do utworzenia partycji musimy ze zbioru wybrać jeden z elementów, który nazwiemy piwotem. W lewej partycji znajdą się wszystkie elementy niewiększe od piwotu, a w prawej partycji umieścimy wszystkie elementy niemniejsze od piwotu. Położenie elementów równych nie wpływa na proces sortowania, zatem mogą one występować w obu partycjach. Również porządek elementów w każdej z partycji nie jest ustalony.
Jako piwot można wybierać element pierwszy, środkowy, ostatni, medianę lub losowy. Dla naszych potrzeb wybierzemy element środkowy:
piwot ← d[(lewy + prawy) div 2] |
|
Dzielenie na partycje polega na umieszczeniu dwóch wskaźników na początku zbioru - i oraz j. Wskaźnik i przebiega przez zbiór poszukując wartości mniejszych od piwotu. Po znalezieniu takiej wartości jest ona wymieniana z elementem na pozycji j. Po tej operacji wskaźnik j jest przesuwany na następną pozycję. Wskaźnik j zapamiętuje pozycję, na którą trafi następny element oraz na końcu wskazuje miejsce, gdzie znajdzie się piwot. W trakcie podziału piwot jest bezpiecznie przechowywany na ostatniej pozycji w zbiorze.
Przykład:
Dla przykładu podzielimy na partycje zbiór:
Lp. | Operacja | Opis |
1. |
7 2 4 7 3 1 4 6 5 8 3 9 2 6 7 6 3 |
Wyznaczamy na piwot element środkowy. |
2. |
7 2 4 7 3 1 4 6 3 8 3 9 2 6 7 6 5 |
Piwot wymieniamy z ostatnim elementem zbioru |
3. |
7 2 4 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Na początku zbioru ustawiamy dwa wskaźniki. Wskaźnik i będzie przeglądał zbiór do przedostatniej pozycji. Wskaźnik j zapamiętuje miejsce wstawiania elementów mniejszych od piwotu |
4. |
7 2 4 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Wskaźnikiem i szukamy elementu mniejszego od piwotu |
5. |
2 7 4 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Znaleziony element wymieniamy z elementem na
pozycji |
6. |
2 7 4 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Szukamy |
7. |
2 4 7 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
8. |
2 4 7 7 3 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Szukamy |
9. |
2 4 3 7 7 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
10. |
2 4 3 7 7 1 4 6 3 8 3 9 2 6 7 6 5 i j |
Szukamy |
11. |
2 4 3 1 7 7 4 6 3 8 3 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
12. |
2 4 3 1 7 7 4 6 3 8 3 9 2 6 7 6 5 i j |
Szukamy |
13. |
2 4 3 1 4 7 7 6 3 8 3 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
14. |
2 4 3 1 4 7 7 6 3 8 3 9 2 6 7 6 5 i j |
Szukamy |
15. |
2 4 3 1 4 3 7 6 7 8 3 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
16. |
2 4 3 1 4 3 7 6 7 8 3 9 2 6 7 6 5 i j |
Szukamy |
17. |
2 4 3 1 4 3 3 6 7 8 7 9 2 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
18. |
2 4 3 1 4 3 3 6 7 8 7 9 2 6 7 6 5 i j |
Szukamy |
19. |
2 4 3 1 4 3 3 2 7 8 7 9 6 6 7 6 5 i j |
Wymieniamy i przesuwamy j. |
20. |
2 4 3 1 4 3 3 2 5 8 7 9 6 6 7 6 7 ^ i Lewa partycja j Prawa partycja |
Brak dalszych elementów do wymiany. Piwot wymieniamy z elementem na pozycji j-tej. Podział na partycje zakończony. |
Po zakończeniu podziału na partycje wskaźnik j
wyznacza pozycję piwotu. Lewa partycja zawiera elementy mniejsze od
piwotu i rozciąga się od początku zbioru do pozycji
d[ ] | - Zbiór zawierający elementy do posortowania. Zakres indeksów elementów jest dowolny. |
lewy | - indeks pierwszego elementu w zbiorze, lewy ∈ C |
prawy | - indeks ostatniego elementu w zbiorze, prawy ∈ C |
d[ ] | - Zbiór zawierający elementy posortowane rosnąco |
piwot | - | element podziałowy |
i, j | - | indeksy, i, j ∈ C |
K01: |
![]() |
K02: | piwot ← d[i]; d[i] ← d[prawy]; j ← lewy |
K03: | Dla i =
lewy, lewy + 1, ..., prawy
- 1: wykonuj kroki K04...K05 |
K04: | Jeśli d[i] ≥ piwot, to wykonaj kolejny obieg pętli K03 |
K05: | d[i] ↔ d[j]; j ← j + 1 |
K06: | d[prawy] ← d[j]; d[j] ← piwot |
K07: | Jeśli lewy <
j - 1, to Sortuj_szybko(lewy, j - 1) |
K08: | Jeśli j + 1 <
prawy, to Sortuj_szybko(j + 1, prawy) |
K09: | Zakończ |
Algorytm sortowania szybkiego wywołujemy podając za lewy indeks pierwszego elementu zbioru, a za prawy indeks elementu ostatniego (czyli Sortuj_szybko(1,n)). Zakres indeksów jest dowolny - dzięki temu ten sam algorytm może również sortować fragment zbioru, co wykorzystujemy przy sortowaniu wyliczonych partycji.
Na element podziałowy wybieramy element leżący w środku dzielonej partycji. Wyliczamy jego pozycję i zapamiętujemy ją tymczasowo w zmiennej i. Robimy to po to, aby dwukrotnie nie wykonywać tych samych rachunków.
Element
Ustawiamy zmienną j na początek partycji. Zmienna ta zapamiętuje pozycję podziału partycji.
W pętli sterowanej zmienną i przeglądamy kolejne
elementy od pierwszego do przedostatniego (ostatni został
umieszczony na pozycji piwotu, a piwot zapamiętany). Jeśli
Po zakończeniu pętli element z pozycji
partycja lewa od pozycji
lewy do |
Sprawdzamy, czy partycje te obejmują więcej niż jeden element. Jeśli tak, to wywołujemy rekurencyjnie algorytm sortowania szybkiego przekazując mu granice wyznaczonych partycji. Po powrocie z wywołań rekurencyjnych partycja wyjściowa jest posortowana rosnąco. Kończymy algorytm.
C++// Sortowanie Szybkie //-------------------------------------------------------- // (C)2012 mgr Jerzy Wałaszek // I Liceum Ogólnokształcące // im. K. Brodzińskiego // w Tarnowie //-------------------------------------------------------- #include <iostream> #include <iomanip> #include <cstdlib> #include <time.h> using namespace std; const int N = 20; // Liczebność zbioru. int d[N]; // Procedura sortowania szybkiego //------------------------------- void Sortuj_szybko(int lewy, int prawy) { int i,j,piwot; i = (lewy + prawy) / 2; piwot = d[i]; d[i] = d[prawy]; for(j = i = lewy; i < prawy; i++) if(d[i] < piwot) { swap(d[i], d[j]); j++; } d[prawy] = d[j]; d[j] = piwot; if(lewy < j - 1) Sortuj_szybko(lewy, j - 1); if(j + 1 < prawy) Sortuj_szybko(j + 1, prawy); } // Program główny //--------------- int main() { int i; srand((unsigned)time(NULL)); cout << " Sortowanie szybkie\n" "------------------------\n" " (C)2005 Jerzy Walaszek \n\n" "Przed sortowaniem:\n\n"; // Najpierw wypełniamy tablicę d[] liczbami pseudolosowymi // a następnie wyświetlamy jej zawartość for(i = 0; i < N; i++) d[i] = rand() % 100; for(i = 0; i < N; i++) cout << setw(4) << d[i]; cout << endl; // Sortujemy Sortuj_szybko(0,N - 1); // Wyświetlamy wynik sortowania cout << "Po sortowaniu:\n\n"; for(i = 0; i < N; i++) cout << setw(4) << d[i]; cout << endl; return 0; } |
Pascal// Sortowanie Szybkie //-------------------------------------------------------- // (C)2012 mgr Jerzy Wałaszek // I Liceum Ogólnokształcące // im. K. Brodzińskiego // w Tarnowie //-------------------------------------------------------- program Quick_Sort; const N = 20; // Liczebność zbioru. var d : array[1..N] of integer; // Procedura sortowania szybkiego //------------------------------- procedure Sortuj_szybko(lewy, prawy : integer); var i,j,piwot,x : integer; begin i := (lewy + prawy) div 2; piwot := d[i]; d[i] := d[prawy]; j := lewy; for i := lewy to prawy - 1 do if d[i] < piwot then begin x := d[i]; d[i] := d[j]; d[j] := x; inc(j); end; d[prawy] := d[j]; d[j] := piwot; if lewy < j - 1 then Sortuj_szybko(lewy, j - 1); if j + 1 < prawy then Sortuj_szybko(j + 1, prawy); end; // Program główny //--------------- var i : integer; begin writeln(' Sortowanie szybkie'); writeln('------------------------'); writeln(' (C)2005 Jerzy Walaszek '); writeln; // Najpierw wypełniamy tablicę d[] liczbami pseudolosowymi // a następnie wyświetlamy jej zawartość randomize; for i := 1 to N do d[i] := random(100); writeln('Przed sortowaniem:'); writeln; for i := 1 to N do write(d[i] : 4); writeln; // Sortujemy Sortuj_szybko(1,N); // Wyświetlamy wynik sortowania writeln('Po sortowaniu:'); writeln; for i := 1 to N do write(d[i] : 4); writeln; writeln('Nacisnij Enter...'); readln; end. |
Basic' Sortowanie szybkie '-------------------------------------------------------- ' (C)2012 mgr Jerzy Wałaszek ' I Liceum Ogólnokształcące ' im. K. Brodzińskiego ' w Tarnowie '-------------------------------------------------------- DECLARE SUB Sortuj_szybko(lewy AS INTEGER, prawy AS INTEGER) CONST N = 20 ' liczebność zbioru DIM SHARED d(N) AS INTEGER DIM i AS INTEGER PRINT " Sortowanie szybkie" PRINT "-----------------------" PRINT "(C)2005 Jerzy Walaszek" PRINT PRINT "Przed sortowaniem:": PRINT ' Wypełniamy tablicę liczbami pseudolosowymi i wyświetlamy je RANDOMIZE FOR i = 1 TO N d(i) = INT(RND * 100): PRINT USING "####";d(i); NEXT PRINT ' Sortujemy Sortuj_szybko(1,N) ' Wyświetlamy wynik sortowania PRINT "Po sortowaniu:": PRINT FOR i = 1 TO N: PRINT USING "####";d(i);: NEXT PRINT PRINT "Nacisnij Enter..." SLEEP END ' Procedura sortowania szybkiego '------------------------------- SUB Sortuj_szybko(lewy AS INTEGER, prawy AS INTEGER) DIM AS INTEGER i, j, piwot i = (lewy + prawy) \ 2 piwot = d(i): d(i) = d(prawy) j = lewy FOR i = lewy TO prawy - 1 IF d(i) < piwot THEN SWAP d(i), d(j) j += 1 END IF NEXT d(prawy) = d(j): d(j) = piwot IF lewy < j - 1 THEN Sortuj_szybko(lewy, j - 1) IF j + 1 < prawy THEN Sortuj_szybko(j + 1, prawy) END SUB |
JavaScript<html> <head> </head> <body> <form style="BORDER-RIGHT: #ff9933 1px outset; PADDING-RIGHT: 4px; BORDER-TOP: #ff9933 1px outset; PADDING-LEFT: 4px; PADDING-BOTTOM: 1px; BORDER-LEFT: #ff9933 1px outset; PADDING-TOP: 1px; BORDER-BOTTOM: #ff9933 1px outset; BACKGROUND-COLOR: #ffcc66" name="frmquicksort"> <h3 style="text-align: center">Sortowanie Szybkie</h3> <p style="TEXT-ALIGN: center"> (C)2012 mgr Jerzy Wałaszek - I LO w Tarnowie </p> <hr> <p style="TEXT-ALIGN: center"> <input onclick="main()" type="button" value="Sortuj" name="B1"> </p> <p id="t_out" style="TEXT-ALIGN: center">...</p> </form> <script language=javascript> // Sortowanie Szybkie //-------------------------------------------------------- // (C)2012 mgr Jerzy Wałaszek // I Liceum Ogólnokształcące // im. K. Brodzińskiego // w Tarnowie //-------------------------------------------------------- var N = 20; // Liczebność zbioru. var d = new Array(N) // Procedura sortowania szybkiego //------------------------------- function Sortuj_szybko(lewy, prawy) { var i,j,piwot,x; i = Math.floor((lewy + prawy) / 2); piwot = d[i]; d[i] = d[prawy]; for(j = i = lewy; i < prawy; i++) if(d[i] < piwot) { x = d[i]; d[i] = d[j]; d[j] = x; j++; } d[prawy] = d[j]; d[j] = piwot; if(lewy < j - 1) Sortuj_szybko(lewy, j - 1); if(j + 1 < prawy) Sortuj_szybko(j + 1, prawy); } // Program główny //--------------- function main() { var i,t; // Najpierw wypełniamy tablicę d[] liczbami pseudolosowymi // a następnie wyświetlamy jej zawartość for(i = 0; i < N; i++) d[i] = Math.floor(Math.random() * 100); t = "Przed sortowaniem:<BR><BR>"; for(i = 0; i < N; i++) t += d[i] + " "; // Sortujemy Sortuj_szybko(0, N - 1); // Wyświetlamy wynik sortowania t += "<BR><BR>Po sortowaniu:<BR><BR>"; for(i = 0; i < N; i++) t += d[i] + " "; document.getElementById("t_out").innerHTML = t } </script> </body> </html> |
Wynik: |
Sortowanie
szybkie ------------------------ (C)2005 Jerzy Walaszek Przed sortowaniem: 46 5 56 35 75 95 33 93 4 71 8 5 69 50 35 34 65 32 72 61 Po sortowaniu: 4 5 5 8 32 33 34 35 35 46 50 56 61 65 69 71 72 75 93 95 |
W celach badawczych testujemy czas wykonania algorytmu sortowania szybkiego w środowisku opisanym we wstępie. Program testujący jest następujący:
Pascal// Program testujący czas sortowania dla // danego algorytmu sortującego //-------------------------------------- // (C)2012 mgr Jerzy Wałaszek // I Liceum Ogólnokształcące // w Tarnowie //-------------------------------------- program TestCzasuSortowania; uses Windows; const NAZWA = 'Sortowanie szybkie'; K1 = '-----------------------------------------------------------'; K2 = '(C)2011/2012 I Liceum Ogolnoksztalcace w Tarnowie'; K3 = '------n---------tpo---------tod---------tpp---------tpk---------tnp'; K4 = '-------------------------------------------------------------------'; MAX_LN = 8; // określa ostatnie LN LN : array[1..8] of integer = (1000,2000,4000,8000,16000,32000,64000,128000); var d : array[1..128000] of real; // sortowana tablica n : integer; // liczba elementów qpf,tqpc : int64; // dane dla pomiaru czasu qpc1,qpc2 : int64; // Tutaj umieszczamy procedurę sortującą tablicę d //------------------------------------------------------- procedure Sortuj_szybko(lewy, prawy : integer); var i,j : integer; piwot,x : real; begin i := (lewy + prawy) div 2; piwot := d[i]; d[i] := d[prawy]; j := lewy; for i := lewy to prawy - 1 do if d[i] < piwot then begin x := d[i]; d[i] := d[j]; d[j] := x; inc(j); end; d[prawy] := d[j]; d[j] := piwot; if lewy < j - 1 then Sortuj_szybko(lewy, j - 1); if j + 1 < prawy then Sortuj_szybko(j + 1, prawy); end; function Sort : extended; begin QueryPerformanceCounter(addr(qpc1)); Sortuj_szybko(1,n); QueryPerformanceCounter(addr(qpc2)); Sort := (qpc2 - qpc1 - tqpc) / qpf; end; // Program główny //--------------- var i,j,k : integer; tpo,tod,tpp,tpk,tnp : extended; f : Text; begin if QueryPerformanceFrequency(addr(qpf)) then begin QueryPerformanceCounter(addr(qpc1)); QueryPerformanceCounter(addr(qpc2)); tqpc := qpc2 - qpc1; assignfile(f,'wyniki.txt'); rewrite(f); // Wydruk na ekran writeln('Nazwa: ',NAZWA); writeln(K1); writeln(K2); writeln; writeln(K3); // Wydruk do pliku writeln(f,'Nazwa: ',NAZWA); writeln(f,K1); writeln(f,K2); writeln(f,''); writeln(f,K3); for i := 1 to MAX_LN do begin n := LN[i]; // Czas sortowania zbioru posortowanego for j := 1 to n do d[j] := j; tpo := Sort; // Czas sortowania zbioru posortowanego odwrotnie for j := 1 to n do d[j] := n - j; tod := Sort; // Czas sortowania zbioru posortowanego // z przypadkowym elementem na początku - średnia z 10 obiegów tpp := 0; for j := 1 to 10 do begin for k := 1 to n do d[k] := k; d[1] := random * n + 1; tpp += Sort; end; tpp /= 10; // Czas sortowania zbioru posortowanego // z przypadkowym elementem na końcu - średnia z 10 obiegów tpk := 0; for j := 1 to 10 do begin for k := 1 to n do d[k] := k; d[n] := random * n + 1; tpk += Sort; end; tpk /= 10; // Czas sortowania zbioru nieuporządkowanego - średnia z 10 obiegów tnp := 0; for j := 1 to 10 do begin for k := 1 to n do d[k] := random; tnp += Sort; end; tnp /= 10; writeln(n:7,tpo:12:6,tod:12:6,tpp:12:6,tpk:12:6,tnp:12:6); writeln(f,n:7,tpo:12:6,tod:12:6,tpp:12:6,tpk:12:6,tnp:12:6); end; writeln(K4); writeln(f,K4); writeln(f,'Koniec'); closefile(f); writeln; writeln('Koniec. Wyniki w pliku WYNIKI.TXT'); end else writeln('Na tym komputerze program testowy nie pracuje !'); writeln; write('Nacisnij klawisz ENTER...'); readln; end. |
Otrzymane wyniki są następujące (dla komputera o innych parametrach wyniki mogą się różnić co do wartości czasów wykonania, dlatego w celach porównawczych proponuję uruchomić podany program na komputerze czytelnika):
Zawartość pliku wygenerowanego przez program | ||||||||||||||||||
Nazwa: Sortowanie szybkie Objaśnienia oznaczeń (wszystkie czasy podano w sekundach):
|
(Arkusz kalkulacyjny Excel do wyznaczania klasy czasowej złożoności obliczeniowej)
(Arkusz kalkulacyjny Excel do wyznaczania wzrostu prędkości sortowania)
Analizując wyniki obliczeń w arkuszu kalkulacyjnym otrzymanych czasów sortowania dla algorytmu sortowania szybkiego wyciągamy następujące wnioski:
Cechy Algorytmu Sortowania Szybkiego | |
klasa złożoności obliczeniowej optymistyczna | ![]() |
klasa złożoności obliczeniowej typowa | |
klasa złożoności obliczeniowej pesymistyczna |
![]() |
Sortowanie w miejscu | TAK |
Stabilność | NIE |
Klasy złożoności obliczeniowej szacujemy następująco:
Własności algorytmu | |||||
Algorytm | tpo | tod | tpp | tpk | tnp |
Sortowanie szybkie | ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Wzrost prędkości sortowania | |||||
Algorytmy | tpo | tod | tpp | tpk | tnp |
Sortowanie
przez scalanie Sortowanie szybkie |
![]() |
![]() |
![]() |
![]() |
![]() |
dobrze | dobrze | dobrze | dobrze | brak |
Otrzymane wyniki potwierdzają, iż algorytm sortowania szybkiego jest najszybszym algorytmem sortującym. Jednakże w przypadku ogólnym notujemy jedynie bardzo nieznaczny wzrost prędkości sortowania w stosunku do algorytmu sortowania przez scalanie.
Ponieważ jak dotąd algorytm sortowania szybkiego jest najszybszym algorytmem sortującym, do dalszych porównań czasów sortowania zastosujemy czasy uzyskane w tym algorytmie.
Na początku partycji
Na końcu partycji
W miejscu losowym wewnątrz partycji
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2023 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.