« poprzedni punkt   następny punkt »


6. Sortowanie kubełkowe

Załóżmy teraz, że elementy, które chcemy posortować, są liczbami rzeczywistymi wybranymi losowo, z rozkładem jednostajnym, z przedziału  [0,1).

Metoda

Podzielmy przedział [0,1) na n podprzedziałów o takiej samej długości. Każdy przedział traktujemy jako "kubełek", do którego wkładamy te elementy danego ciągu, które należą do odpowiadającego "kubełkowi" przedziału.  Każdy z "kubełków" sortujemy osobno, a następnie zawartości "kubełków" łączymy w jeden ciąg.

Przykład 6.1

Niech 0.9, 0.32, 0.74,  0.15, 0.23, 0.21, 0.17, 0.45, 0.28 będzie ciągiem, który chcemy posortować.  W poniższej tablicy przedstawiamy stan "kubełków" po rozrzuceniu wszystkich elementów.

Dane: 0.32 0.74 0.15 0.23 0.21 0.17 0.45 0.28 0.43 0.90 S
O
R
T
O
W
A
N
I
E
K
U
B
E
Ł
K
I
L[0] ®   L[0] ®
L[1] ® .15 .17 L[1] ® .15 .17
L[2] ® .23 .21 .28 L[2] ® .21 .23 .28
L[3] ® .32 L[3] ® .32
L[4] ® .45 .43 L[4] ® .43 .45
L[5] ® L[5] ®
L[6] ® L[6] ®
L[7] ® .74 L[7] ® .74
L[8] ® L[8] ®
L[9] ® .90 L[9] ® .90
po rozrzuceniu po posortowaniu
Po połączeniu kubełków .15 .17 .21 .23 .28 .32 .43 .45 .74 .90

Wprawdzie spodziewamy się, że średnio w każdym "kubełku" jest tylko jeden element, ale nie musi tak być. W algorytmie BucketSort, realizującym opisaną wyżej metodę, zaproponowano użycie algorytmu InsertionSort do sortowania zawartości "kubełków".

Niech dany do posortowania ciąg e1,...,en będzie zapisany w tablicy e[1],...,e[n] i niech spełnione będą warunki 0 £ e[i] <1. Zakładamy ponadto , że mamy do dyspozycji pomocniczą tablicę L, n list, L[0],...,L[n-1], które będą pełniły rolę "kubełków". Wynikiem działania algorytmu będzie wypisanie elementów danego ciągu e1,...,en w porządku niemalejącym.

BucketSort{
 for i := 1 to n do
            dopisz e[i] do listy L[ ën´e[i]û] // rozrzuć wszystkie liczby do "kubełków"
od;         
 for i := 0  to n-1 do      
      posortuj listę L[i] algorytmem InsertionSort            //posortuj każdy z "kubełków" osobno
 od;         
   Wypisz kolejno elementy list L[0],L[1],..., L[n-1]  //połącz "kubełki"
     }       

Poprawność algorytmu

Dla dowodu poprawności przedstawionego algorytmu sprawdźmy przede wszystkim, czy elementy ciągu e trafiają do właściwej listy. Ponieważ  przedział [0, 1) został podzielony na równe części, zatem lista L[i] odpowiada przedziałowi [i/n, (i+1)/n). Liczba e[i] powinna się więc znaleźć w "kubełku" j-tym, jeżeli  j/n  £ e[i] < (j+1)/n. Mamy

ën´e[i]û £ n´e[i] < ën´e[i]û+1.

Przyjmując j = ën´e[i]û i umieszczając e[i] na liście  L[ën´e[i]û] otrzymamy częściowe posortowanie danego ciągu. Możemy więc stwierdzić, że  po wykonaniu pierwszej pętli "for" prawdziwa jest własność: każdy element danego ciągu znajduje się na dokładnie jednej z list L[0],..., L[n-1], oraz elementy listy L[i] są mniejsze od wszystkich elementów listy L[i+1], dla i = 0,1,...,n-1.

Przyjmując, że algorytm InsertionSort sortuje poprawnie elementy każdego "kubełka" w porządku niemalejącym, po wypisaniu kolejno elementów list L[0],...,L[n-1] otrzymamy ciąg uporządkowany niemalejąco.

Twierdzenie 6.1   Dla dowolnych danych spełniających warunek początkowy  { 0 £ e[i] < 1} w strukturze liczb rzeczywistych algorytm BucketSort zatrzymuje się, a wypisany w wyniku działania algorytmu ciąg, jest uporządkowaną niemalejąco permutacją elementów danego ciągu.

Koszt algorytmu

Zauważmy, że koszt pierwszej pętli i koszt wypisania zawartości wszystkich list jest liniowy Q(n), dla ciągu n elementowego. Pozostaje zbadać koszt związany z sortowaniem list. Algorytm InsertionSort jest wprawdzie algorytmem o złożoności kwadratowej, ale nie spodziewamy się dużej liczby elementów w jednym "kubełku". Jeżeli n[j] jest liczbą elementów w j-tej liście, to koszt algorytmu BucketSort  można oszacować następująco:

T(n) =  Q(n) + Sj=0,...,n-1 O( n[j]2).

Załóżmy, że każdy element danego ciągu wpada do dowolnego kubełka z takim samym prawdopodobieństwem. Można wtedy pokazać, że wartość oczekiwana kwadratu liczby elementów w j-tym "kubełku" wynosi E(n[j]2)= 2-1/n. Stąd, wartość oczekiwana liczby wykonanych operacji w algorytmie BucketSort  wynosi  Q(n) +  n ( 2-1/n)  i jest liniowa względem rozmiaru danych.

Pytanie 7 : Jaki jest pesymistyczny czas działania algorytmu BucketSort, zastosowanego do n elementowego ciągu?


« poprzedni punkt   następny punkt »