Privacy Policy Cookie Policy Terms and Conditions Sortowanie szybkie - Wikipedia, wolna encyklopedia

Sortowanie szybkie

Z Wikipedii

Zobrazowanie algorytmu szybkiego sortowania w akcji
Powiększ
Zobrazowanie algorytmu szybkiego sortowania w akcji

Algorytm quicksort (ang. szybkie sortowanie) – jeden z popularnych algorytmów sortowania działających na zasadzie "dziel i zwyciężaj".

Spis treści

[edytuj] Zasada

Algorytm działa rekursywnie - wybieramy pewien element tablicy, tzw. element dzielący, po czym na początek tablicy przenosimy wszystkie elementy mniejsze od niego, na koniec wszystkie większe, a w powstałe między tymi obszarami puste miejsce trafia wybrany element. Potem sortujemy 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 podtablica jest oczywiście posortowana.

Jeśli przez l oznaczymy indeks pierwszego, a przez r – ostatniego elementu sortowanego fragmentu tablicy, zaś przez i – indeks elementu, na którym tablica została podzielona, to procedurę sortowania można (w dużym uproszczeniu) przedstawić następującym, pascalo-podobnym zapisem:

  PROCEDURE Quicksort( l, r )
  BEGIN
    IF l < r THEN                  { jeśli fragment dłuższy niż 1 element }
    BEGIN
      i = PodzielTablice( l, r );  { podziel i zapamiętaj punkt podziału }
      Quicksort( l, i-1 );         { posortuj lewą część }
      Quicksort( i, r );           { posortuj prawą część }
    END
  END

Algorytm sortowania szybkiego jest bardzo wydajny: jego średnia złożoność obliczeniowa jest rzędu O(n·log n) (zob. hasło Notacja dużego O). Jego szybkość i prostota implementacji powodują, że jest powszechnie używany; jego implementacje znajdują się również w standardowych bibliotekach języków programowania - na przykład w bibliotece standardowej języka C, w implementacji klasy TList w Delphi, jako procedura standardowa w PHP itp.

[edytuj] Złożoność

Algorytm ten dość dobrze działa w praktyce, ale ma bardzo złą pesymistyczną złożoność.

[edytuj] Przypadek optymistyczny

W przypadku optymistycznym, jeśli mamy szczęście za każdym razem wybrać medianę z sortowanego fragmentu tablicy, to liczba porównań niezbędnych do uporządkowania n-elementowej tablicy opisana jest rekurencyjnym wzorem

T(n) = (n-1) + 2T\left(\frac{n-1}2\right)

Dla dużych n:

T(n) \approx n + 2 T\left(\frac n 2\right)

co daje w rozwiązaniu liczbę porównań (a więc wskaźnik złożoności czasowej):

T(n) \approx n \log n

Równocześnie otrzymuje się minimalne zagnieżdżenie rekursji (czyli głębokość stosu, a co za tym idzie, złożoność pamięciową):

M(n) \approx \log n

(Symbol logarytmu bez oznaczonej podstawy oznacza tu logarytm dwójkowy.)

[edytuj] Przypadek przeciętny

W przypadku przeciętnym, to jest dla równomiernego rozkładu prawdopodobieństwa wyboru elementu z tablicy:

T(n) \approx 2 n \ln n \approx 1.39 n\log n

złożoność jest zaledwie o 39% wyższa, niż w przypadku optymistycznym.

[edytuj] Przypadek pesymistyczny

W przypadku pesymistycznym, jeśli zawsze wybierzemy element najmniejszy (albo największy) w sortowanym fragmencie tablicy, to:

T(n) = n-1 + T(n-1)\;\!

skąd wynika kwadratowa złożoność czasowa:

T(n) = \frac{n^2-n}2 \approx \frac {n^2} 2

W tym przypadku otrzymuje się też olbrzymią, liniową złożoność pamięciową:

M(n) = n-1\;\!

[edytuj] Usprawnienia algorytmu

[edytuj] Wybór elementu

Najprostsza, "naiwna" metoda podziału - wybieranie zawsze skrajnego elementu tablicy - dla danych już uporządkowanych daje nam katastrofalną złożoność O(n2). Trywialne na pozór zadanie posortowania posortowanej tablicy okazuje się dla tak zapisanego algorytmu zadaniem skrajnie trudnym. Aby uchronić się przed takim przypadkiem stosuje się najczęściej randomizację wyboru albo wybór "środkowy-z-trzech". Pierwszy sposób opiera się na losowaniu elementu dzielącego, co sprowadza prawdopodobieństwo zajścia najgorszego przypadku do wartości zaniedbywalnie małych. Drugi sposób polega na wstępnym wyborze trzech elementów z rozpatrywanego fragmentu tablicy, i użyciu jako elementu dzielącego tego z trzech, którego wartość leży pomiędzy wartościami pozostałych dwu. Można również uzupełnić algorytm o poszukiwanie przybliżonej mediany (patrz poniżej: #Gwarancja złożoności).

[edytuj] Ograniczenie rekursji

Wysoka wydajność alogrytmu 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.

W najgorszym przypadku, jeśli algorytm będzie dzielił tablicę zawsze na część jednoelementową i resztę, to rekursja osiągnie głębokość n-1. Aby temu zapobiec, należy sprawdzać, która część jest krótsza – i tę porządkować najpierw. Z dłuższą zaś nie wchodzić w rekursję, lecz ponownie dzielić na tym samym poziomie wywołania:

  PROCEDURE Quicksort( l, r )
  BEGIN
    WHILE l < r DO                 { dopóki fragment dłuższy niż 1 element }
    BEGIN
      i := PodzielTablice( l, r );
      IF (i-l) ≤ (r-i) THEN        { sprawdź, czy lewa część krótsza }
        BEGIN                      { TAK? }
          Quicksort( l, i-1 );     { posortuj lewą, krótszą część }
          l := i+1                 { i kontynuuj dzielenie dłuższej }
        END
      ELSE
        BEGIN                      { NIE }
          Quicksort( i, r );       { posortuj prawą, krótszą część }
          r := i-1                 { i kontynuuj dzielenie dłuższej }
        END
    END
  END

Przy takiej organizacji pracy na stosie zawsze pozostają zapamiętane (w zmiennych i, r albo l, i) indeksy ograniczające dłuższą, jeszcze nie posortowaną część tablicy, a wywołanie rekurencyjne zajmuje się częścią krótszą. To znaczy, że na każdym poziomie wywołań algorytm obsługuje fragment będący co najwyżej połową fragmentu z poprzedniego poziomu. Stąd wynika, że poziomów wywołań nie będzie więcej, niż log2n, gdzie n oznacza długość całej tablicy. Zatem usprawnienie to zmienia asymptotyczne wykorzystanie pamięci tego algorytmu z O(n) do O(logn).

[edytuj] Quicksort w miejscu

Istnieje usprawnienie czyniące algorytm quicksort działającym w miejscu. W oryginalnym sortowaniu szybkim używa się rekursji lub stosu (de facto oba sposoby niewiele się różnią – rekursja w uproszczeniu jest niejawnym stosem) do zapamiętywania miejsc podziału. Więc chociaż algorytm w obu wersjach nie korzysta z dodatkowych tablic o rozmiarze zależnym od rozmiaru danych wejściowych, to nie można nazywać go działającym w miejscu, gdyż wysokość, a więc i wymagania pamięciowe wywołań rekusji/stosu są ściśle zależne od rozmiaru danych początkowych.

Załóżmy, że procedura dla sortowanej tablicy A funkcja PodzielTablice(l,p) zwróciła wartość s. W oryginalnym algorytmie quicksort powinno zostać wykonane wywołania Quicksort(l,s-1) i Quicksort(s+1,p). Zamiast tego "zajmujemy się" tylko Quicksort(l,s-1), a pozycje s+1 i p zapamiętujemy w następujący sposób:

  • s+1: jednocześnie wyznaczona przez koniec ciągu (l,s-1) -> s = (s-1) + 2
  • p: znajdujemy maksimum ciągu (s+1,p) i zamieniamy tę pozycję z pozycją s. Aby odtworzyć pozycję p, wystarczy przeszukiwać ciąg w prawo do znalezienia elementu większego od wartości stojącej na pozycji s. Wtedy indeks o najmniejszej wartości większej od A[s]] to p+1.

[edytuj] Drobne fragmenty

U podstaw kolejnego usprawnienia leży spostrzeżenie, że około połowa wszystkich rekurencyjnych wywołań procedury dotyczy jednoelementowych fragmentów tablicy – a więc fragmentów z definicji posortowanych. Co więcej, po wliczeniu dodatkowej pracy potrzebnej na wybór elementu dzielącego i zorganizowanie pętli dzielącej tablicę okazuje się, że sortowanie tablic nawet kilkuelementowych algorytmem quicksort jest bardziej pracochłonne, niż jakimś algorytmem prostym, na przykład przez wstawianie.

Warto więc zaniechać dalszych podziałów, gdy uzyskane fragmenty staną się dostatecznie krótkie – rzędu kilku lub kilkunastu. Otrzymuje się w wyniku tablicę "prawie posortowaną", w której większość elementów może nie znajduje się na właściwych miejscach, ale są blisko właściwych miejsc. Taką tablicę ostatecznie sortuje się algorytmem wstawiania, który bardzo efektywnie radzi sobie z tego rodzaju danymi. Jak pokazuje praktyka, wybór granicznej długości fragmentu nie wymaga szczególnych rygorów – algorytm działa niemal równie dobrze dla wartości od 5 do 25. W większości zastosowań pozwala to osiągnąć oszczędność łącznego czasu wykonania rzędu 20%.

[edytuj] Gwarancja złożoności

Pomimo wszelkich usprawnień, pozostaje jednak, zazwyczaj znikome, prawdopodobieństwo zajścia przypadku pesymistycznego, w którym złożoność czasowa wynosi O(n2). Jeśli chcemy mieć pewność wykonania sortowania w czasie nie dłuższym niż O(nlogn), należy uzupełnić algorytm o poszukiwanie przybliżonej mediany, czyli elementu dzielącego posortowaną tablicę na tyle dobrze, że pesymistyczne oszacowanie złożoności zrówna się z optymistycznym.

Jeżeli prawdopodobieństwo wystąpienia przypadku pesymistycznego w praktyce jest duże, to można skorzystać ze specjalnych algorytmów znajdowania dobrej mediany. Niestety algorytmy te mają dość dużą złożoność, dlatego w takiej sytuacji należy też rozważyć skorzystanie z innych algorytmów sortowania, takich jak np. sortowanie stogowe, sortowanie pozycyjne, czy sortowanie przez scalanie.

W większości praktycznych zastosowań algorytm sortowania szybkiego jest bezkonkurencyjny. W praktyce pozostaje on zdecydowanie najczęściej używanym algorytmem sortowania. Opracowano też wiele modyfikacji i usprawnień tego algorytmu, poprawiając niektóre właściwości, lub dostosowując go do konkretnych wymagań.

[edytuj] Przykładowe programy

Kod szybkiego sortowania tablicy w Pascalu (z randomizowanym wyborem elementu dzielącego, lecz bez pozostałych usprawnień).

   PROCEDURE Quicksort (VAR A : tab; l,r: INTEGER);
   VAR 
       pivot,b,i,j :  INTEGER;
   BEGIN 
       IF l < r THEN
       BEGIN
           pivot := A[random(r-l) + l+1];  { losowanie elementu dzielącego }
           i := l-1;
           j := r+1;
           REPEAT
               REPEAT i := i+1 UNTIL pivot <= A[i];
               REPEAT j := j-1 UNTIL pivot >= A[j];
               b:=A[i]; A[i]:=A[j]; A[j]:=b
           UNTIL i >= j;
           A[j]:=A[i]; A[i]:=b;
           Quicksort(A,l,i-1);
           Quicksort(A,i,r)
       END
   END; 

Wywoływanie dla tablicy o długości n: quicksort(tablica,1,n);.

Kod szybkiego sortowania w SML

infix 5 <<;
infix 5 >>;

fun x << (y::ys) = if (y < x) then y::(x << ys) else x << ys
  | x << nil = nil;

fun x >> (y::ys) = if (y >= x) then y::(x >> ys) else x >> ys
  | x >> nil = nil;

fun quicksort nil = nil
  | quicksort [x] = [x]
  | quicksort (x::xs) = quicksort(x << xs) @ (x::quicksort(x >> xs))

(* np. sortuj int list *)
quicksort [108,14,13,15];

(* albo krócej *)
fun qs [] = [] 
  | qs (x::xs) = (qs (filter (fn p => p < x) xs)) @ (x::(qs (filter (fn p => p>= x) xs)))

Funkcja w języku ANSI C (dla tablicy o elementach typu float i długości typu int) :

typedef float TYP;

void QuickSort(TYP *T, int Lo, int Hi)
{
   int i,j;
   TYP x;
   x = T[(Lo+Hi)/2];
   i = Lo;
   j = Hi;
   do
   {
      while (T[i] < x) i++;
      while (T[j] > x) j--;
      if (i<=j)
      {
         TYP tmp = T[i];
         T[i] = T[j];
         T[j] = tmp;
         i++; j--;
      }
   } while(i < j);
   if (Lo < j) QuickSort(T, Lo, j);
   if (Hi > i) QuickSort(T, i, Hi);
}
 

[edytuj] Zobacz też

THIS WEB:

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - be - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - closed_zh_tw - co - cr - cs - csb - cu - cv - cy - da - de - diq - dv - dz - ee - el - eml - en - eo - es - et - eu - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gd - gl - glk - gn - got - gu - gv - ha - haw - he - hi - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mg - mh - mi - mk - ml - mn - mo - mr - ms - mt - mus - my - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - rm - rmy - rn - ro - roa_rup - roa_tara - ru - ru_sib - rw - sa - sc - scn - sco - sd - se - searchcom - sg - sh - si - simple - sk - sl - sm - sn - so - sq - sr - ss - st - su - sv - sw - ta - te - test - tet - tg - th - ti - tk - tl - tlh - tn - to - tokipona - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu

Static Wikipedia 2008 (no images)

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - en - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu -

Static Wikipedia 2007:

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - be - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - closed_zh_tw - co - cr - cs - csb - cu - cv - cy - da - de - diq - dv - dz - ee - el - eml - en - eo - es - et - eu - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gd - gl - glk - gn - got - gu - gv - ha - haw - he - hi - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mg - mh - mi - mk - ml - mn - mo - mr - ms - mt - mus - my - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - rm - rmy - rn - ro - roa_rup - roa_tara - ru - ru_sib - rw - sa - sc - scn - sco - sd - se - searchcom - sg - sh - si - simple - sk - sl - sm - sn - so - sq - sr - ss - st - su - sv - sw - ta - te - test - tet - tg - th - ti - tk - tl - tlh - tn - to - tokipona - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu

Static Wikipedia 2006:

aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - be - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - closed_zh_tw - co - cr - cs - csb - cu - cv - cy - da - de - diq - dv - dz - ee - el - eml - en - eo - es - et - eu - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gd - gl - glk - gn - got - gu - gv - ha - haw - he - hi - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mg - mh - mi - mk - ml - mn - mo - mr - ms - mt - mus - my - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - rm - rmy - rn - ro - roa_rup - roa_tara - ru - ru_sib - rw - sa - sc - scn - sco - sd - se - searchcom - sg - sh - si - simple - sk - sl - sm - sn - so - sq - sr - ss - st - su - sv - sw - ta - te - test - tet - tg - th - ti - tk - tl - tlh - tn - to - tokipona - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu