« poprzedni punkt | następny punkt » |
Sortowanie pozycyjne jest pewnym algorytmem sortowania przez rozrzucanie stosowanym przy porządkowaniu np. kart bibliotecznych (np. ze względu na nazwisko autora), poczty (np. ze względu na kod pocztowy). Algorytm ten był używany do sortowania kart perforowanych służących do przechowywania danych, w dawnych komputerach. Karta składała się z 80 kolumn, z których każda miała 12 pozycji. Kolumna służyła do zapamiętania jednej cyfry przez wybicie dziurki na odpowiedniej pozycji, zatem na jednej karcie można było zapamiętać słowo 80 znakowe. Maszyna sortująca, automatycznie rozdzielała karty do 12 pojemników w zależności od położenia dziurki w ustalonej kolumnie. Następnie, należało wyjąć karty z kolejnych pojemników tworząc jeden plik kart i ponownie użyć maszyny sortującej do rozdzielenia kart ze względu na następną kolumnę.
Można sądzić, że zadania sortowania liczb w systemie dziesiętnym można wykonać stosując podobną zasadę: posortować najpierw dany zbiór liczb, ze względu na najbardziej znaczącą cyfrę, np. tworząc rodzinę zbiorów liczb o takiej samej pierwszej cyfrze. Połączyć wszystkie kubełki tworząc ponownie jeden ciąg i powtórzyć proces rozrzucania, ale biorąc tym razem pod uwagę następną cyfrę rozważanych liczb. Czy postępowanie takie można jednak zastosować do dowolnego ciągu liczb naturalnych?
Przykład 5.1
Chcemy posortować następujący ciąg liczb 94, 572, 321, 58. Postąpimy tak, jak w opisanej wyżej metodzie. W kolejnych krokach otrzymamy:
Wynik nie jest, niestety, ciągiem uporządkowanym niemalejąco. Jest oczywiste, że powodem naszego niepowodzenia jest niejednakowa liczba cyfr w rozważanych liczbach.
Niech zatem będzie dany ciąg liczb całkowitych d cyfrowych. Metoda zastosowana w algorytmie RadixSort jest bardzo podobna do wspomnianej powyżej metody, ale zaczynamy sortowanie od najmniej znaczącej cyfry. Aby posortować ciąg liczb całkowitych w systemie dziesiętnym tworzymy 10 pojemników, tzw. "kubełków" odpowiadających cyfrom 0,...,9. Każda liczba wpada najpierw do "kubełka" o numerze równym jej ostatniej cyfrze. Następnie liczby są wyjmowane kolejno z "kubełków" 0,1,2... aby utworzyć jeden ciąg. Teraz proces należy powtórzyć, z tym, że "rozrzucając" liczby, bierzemy pod uwagę drugą od końca cyfrę, itd.
Aby wynik algorytmu był zawsze poprawny trzeba zwrócić uwagę na sposób reprezentacji "kubełków". Gdybyśmy je zaimplementowali jako stosy, to mogłoby się zdarzyć tak jak w przykładzie 5.2
Przykład 5.2
Dany jest ciąg 253, 523, 235, 532, 325, 352, 252, 525, 718. Elementy wewnątrz "kubełków" będziemy układali na stosie. Pamiętamy, że każdy nowy element trafia na szczyt stosu i zdejmujemy elementy najpierw ze szczytu stosu.
Dany ciąg: | 253 | 523 | 235 | 532 | 325 | 352 | 252 | 525 | 718 | |||||||||||||
STOSY ® |
STOSY ® |
|||||||||||||||||||||
252 | 525 | 325 | 253 | |||||||||||||||||||
352 | 523 | 325 | 525 | 235 | 352 | |||||||||||||||||
532 | 253 | 235 | 718 | 718 | 523 | 532 | 252 | |||||||||||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |||
I etap | 252 | 352 | 532 | 523 | 253 | 525 | 325 | 235 | 718 | II etap | 718 | 325 | 525 | 523 | 235 | 532 | 253 | 352 | 252 | |||
STOSY ® |
Nieudana próba
sortowania pozycyjnego z użyciem stosów |
|||||||||||||||||||||
252 | 532 | |||||||||||||||||||||
253 | 352 | 523 | ||||||||||||||||||||
235 | 325 | 525 | 718 | |||||||||||||||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |||||||||||||
III etap | 252 | 253 | 235 | 352 | 325 | 532 | 523 | 525 | 718 | |||||||||||||
W pierwszym etapie rozrzucania bierzemy pod uwagę tylko ostatnią cyfrę. Zauważmy, że niektóre "kubełki" są puste, np. "kubełek" o numerze 1 jest pusty, bo nie było w naszym ciągu żadnej liczby kończącej się jedynką. Po pierwszym rozrzucaniu i po po połączeniu "kubełków" otrzymaliśmy następujący ciąg :252, 352, 532, 523, 253, 525, 325, 235, 718. Teraz powtarzamy rozrzucanie ze względu na drugą cyfrę. Tak jak poprzednio elementy w "kubełkach" kładziemy jeden nad drugim. Po zdjęciu elementów kolejno ze stosów o numerach 1, 2, 3, 5 ( bo tylko te stosy były niepuste) otrzymaliśmy ciąg 718, 325, 525, 523, 235, 532, 253, 352, 252. Teraz następuje trzeci etap, rozrzucanie ze względu na najbardziej znaczącą cyfrę. Po połączeniu "kubełków" otrzymujemy ciąg: 252, 253, 235, 352, 325, 532, 525, 718, który nie jest niestety uporządkowany niemalejąco.
Kłopot polega na tym, że elementy są zdejmowane ze stosu w kolejności odwrotnej do tej, w jakiej były wkładane na stos. Taki problem nie wystąpi, jeżeli zamiast stosów użyjemy kolejek. J
Przykład 5.3
Rozważmy ciąg z przykładu 5.2 : 253, 523, 235, 532, 325, 352, 252, 525, 718. W tabeli poniżej przedstawiliśmy kolejne etapy algorytmu.
Dany ciąg: | 253 | 523 | 235 | 532 | 325 | 352 | 252 | 525 | 718 | ||||||||||||
Kolejki | 2 | ® | 532 | 352 | 252 | Kolejki | 1 | ® | 718 | ||||||||||||
3 | ® | 253 | 523 | 2 | ® | 523 | 325 | 525 | |||||||||||||
5 | ® | 235 | 325 | 525 | 3 | ® | 532 | 235 | |||||||||||||
8 | ® | 718 | 5 | ® | 352 | 252 | 253 | ||||||||||||||
I etap | 532 | 352 | 252 | 253 | 523 | 235 | 325 | 525 | 718 | II etap | 718 | 523 | 325 | 525 | 532 | 235 | 352 | 252 | 253 | ||
Kolejki | 2 | ® | 235 | 252 | 253 | Sortowanie pozycyjne | |||||||||||||||
3 | ® | 325 | 352 | ||||||||||||||||||
5 | ® | 523 | 525 | 532 | |||||||||||||||||
7 | ® | 718 | |||||||||||||||||||
III etap | 235 | 252 | 253 | 325 | 352 | 523 | 525 | 532 | 718 | ||||||||||||
Tym razem "kubełki" są realizowane jako kolejki, do których elementy wpadają zawsze na koniec, a wyjmowane są zawsze najpierw początkowe elementy. W tabeli, dla uproszczenia, przedstawiliśmy tylko te kolejki, które w danym etapie algorytmu były niepuste. Tym razem wynikiem trzeciego etapu algorytmu jest ciąg uporządkowany niemalejąco. J
Szkic algorytmu
Niech e[1], ..., e[n] będzie tablicą elementów do posortowania. Załóżmy, że kolejki q[0], q[1],..., q[9] są początkowo puste.
RadixSort{ | |||
for k := 1 to d do | |||
for j := 1 to n do | |||
x := k-ta cyfra od końca liczby e[j]; | // rozrzuć wszystkie liczby do kubełków | ||
włóż e[j] do kolejki q[x]; | //o numerach 0,...9 ze względu na k-tą od końca cyfrę | ||
od; | |||
for j := 0 to 9 do | //połącz kolejki w jeden ciąg | ||
przepisz zawartość kolejki q[j] na kolejne pozycje tablicy e ; |
//z zachowaniem kolejności elementów w kolejkach | ||
od; | |||
od; | |||
} |
Poprawność algorytmu RadixSort
Załóżmy, że w wyniku k-1 pierwszych iteracji, elementy tablicy e tworzą ciąg uporządkowany niemalejąco, jeśli rozważać tylko k-1 ostatnich pozycji każdej z liczb. Prześledzimy zachowanie algorytmu w k-tym przebiegu. Niech x i y będą dowolnymi elementami tablicy e i niech:
x = xk+1 10 k+1 + xk 10k + x*
y = yk+1 10 k+1 + yk 10k + y*
gdzie x* i y* są liczbami mniejszymi od 10k oraz x poprzedza y w tablicy e (tzn. x* £ y*). Rozważymy trzy przypadki ze względu na relację zachodzącą między k-tymi cyframi rozwinięcia dziesiętnego liczb x i y:
1. Albo xk = yk i wtedy liczby x, y znajdą się w k-tym przebiegu w tej samej kolejce, ale x zostanie w niej umieszczone wcześniej niż y i dlatego, po połączeniu kolejek, x będzie poprzedzało y.
2. Albo xk < yk i wtedy x trafi do kolejki o mniejszym numerze niż y, zgodnie z zasadą rozrzucania. Ponieważ łączenie kolejek nastąpi też w kolejności rosnących numerów kolejek, zatem po k-tym przebiegu x będzie poprzedzało y po połączeniu kolejek.
3. Albo xk > yk. W takim przypadku y zostanie wpisane do kolejki o numerze wcześniejszym niż numer kolejki, do której wpadnie x. Gdy kolejki zostaną połączone y będzie poprzedzało x.
Zatem we wszystkich trzech przypadkach liczby x i y będą występowały we właściwej kolejności. Po k-tej iteracji otrzymany ciąg e jest uporządkowany niemalejąco ze względu na k ostatnich pozycji.
Biorąc pod uwagę udowodniony powyżej niezmiennik, po d iteracjach, tablica e zawiera uporządkowane niemalejąco elementy początkowego ciągu.
Twierdzenie 5.1
Algorytm RadixSort jest całkowicie poprawny w strukturze liczb naturalnych ze względu na następującą specyfikację:
wp = {($ k) 10 d £ ei <10 d+1 oraz e[i] = ei dla dowolnego i=1,2,...,n}
wk = {e[1] £ e[2] £... £e[n] oraz ciąg e[1],...,e[n] jest permutacją elementów e1, ... ,en}
Koszt algorytmu RadixSort
Operacjami dominującymi w tym algorytmie są operacje wkładania i usuwania elementu do kolejki. W każdym przebiegu pętli zewnętrznej wykonujemy Q(n) tego typu operacji. Koszt całkowity algorytmu zastosowanego do ciągu n liczb d-cyfrowych wynosi Q(n ´ d). Jeżeli d jest stałą, to koszt algorytmu jest liniowy.
Algorytm RadixSort można też przedstawić w uogólnionej postaci następująco:
RadixSort{ | |||
for k := 1 to d do | |||
posortuj dany ciąg ze względu na k-tą od końca cyfrę używając dowolnego stabilnego algorytmu sortowania | |||
od; | |||
} |
Zwykle, w treści pętli tego algorytmu występuje algorytm CountingSort, który jest algorytmem stabilnym i łatwym do zaimplementowania. I chociaż wymaga on w przypadku ogólnym dodatkowej tablicy, to w tym zastosowaniu, do sortowania pozycyjnego, jego tablica pomocnicza jest dziesięcioelementowa (pozycje tej tablicy odpowiadają cyfrom).
Uwaga 1. Algorytm RadixSort można stosować również do sortowania liczb o różnej długości, np. uzupełniając "brakujące" pozycje początkowe zerami. 2. Można też go stosować do sortowania dowolnych rekordów, których składowe należą do dowolnych liniowo uporządkowanych zbiorów skończonych.
Pytanie 6: Załóżmy, że alfabet składa się z 25 liter oraz, że mamy posortować 1000 słów 5 literowych. Ile kolejek musimy utworzyć w procesie sortowania tych słów i ile razy wykonamy operację wkładania elementu do kolejki?
« poprzedni punkt | następny punkt » |