ALGORYTMY I STRUKTURY DANYCH
WYKŁAD 04 Problem Sortowania Grażyna Mirkowska
PJWSTK, 2003/2004
Plan wykładu
Sformułowanie problemu
Sortowanie przez porównywanie elementów
– Sortowanie przez wstawianie – Sortowanie przez selekcję
– Operacja scalania ciągów uporządkowanych – Sortowanie przez scalanie
– Szybkie sortowanie
Sformułowanie problemu
Dany jest ciąg e elementów e1,e2,... , en należących do pewnej liniowo uporządkowanej przestrzeni <E, >.
Znaleźć taką permutację i1, i2,... ,in liczb 1,..., n aby ei1ei2 ... ein
Znaleźć taką funkcję różnowartościową i „na”
f : {1,2...,n} {e1,e2,... , en }, że dla każdego i<n, f(i) f(i+1).
5 3 1 6 7 2 8 3 6 2 1 4 5 7 1 2 3 5 6 7 8
1 2 3 4 5 6 7
Sortowanie przez selekcję
Metoda Sortowanie odbywa się w n-1 przebiegach. W i-tym przebiegu szukamy i-tego najmniejszego elementu.
Algorytm
{ for i := 1 to n-1 do min := i; j := i+1;
while j < n+1 do
if e[j] < e[min] then min := j fi od;
swap(e[i],e[min]);
od }
Odcinek uporządkowany
Diagram przepływu
i := 1 i < n
Znajdź x takie, że
x = minimum( e[i],..., e[n])
Zamień
miejscami x i e[i]
i := i+1
tak
nie
e[1] ... e[i-1] {e[i],...,e[n]}
x {e[i],...,e[n]}
e[1] ... e[i-1] e[i] {e[i+1],...,e[n]}
e[1] ... e[i-1] {e[i],...,e[n]}
Koszt algorytmu
Twierdzenie Algorytm Selection_sort jest poprawnym
rozwiązaniem problemu sortowania. W dowolnej strukturze danych, której elementy są liniowo uporządkowane przez relację , algorytm zatrzymuje się dla dowolnych danych i daje w wyniku ciąg uporządkowany niemalejąco.
A. Jeśli operacją dominującą jest porównywanie elementów:
T(n) = n-1 + n-2 + ... +2 + 1 = n(n-1)/2 = (n2)
B. Jeśli operacją dominującą jest zamiana elementów
T(n) = 1*(n-1) = n-1 = (n)
Sortowanie przez wstawianie
Sortowanie odbywa się w n -1 przebiegach. W i-tym przebiegu elementy na pozycjach 1...(i-1) są już uporządkowane, a
wstawiamy i-ty element przepychając go do przodu na właściwe miejsce, tak by stworzył wraz z innymi ciąg uporządkowany długości i.
Odcinek uporządkowany
i-ty element
X
4 8 5 3 9 6 4 5 8 3 9 6
5
4 5 3 8 9 6 4 3 5 8 9 6 3 4 5 8 9 6
3
9
3 4 5 8 9 6 6 itd
4 8
Schemat algorytmu
i := 2
i < n+1
Umieść e[i] wśród elementów e[1],e[2],...e[i-1],
przesuwając elementy większe o jedno miejsce w prawo, tak by ciąg i-pierwszych elementów był uporządkowany
start
Tak
Nie
i := i+1
stop
e[1]... e[i-1] , i < n+2
e[ 1]... e[i-1] , i<n +1
e[ 1]... e[i-1] e[i] , i<n +1 e[ 1]... e[i-2] e[i-1],
i<n +2
Algorytm Insertion_sort
{for i := 2 to n do
j := i; pom := e[i];
while ( j>1 andif e[j-1]> pom ) do
e[j] := e[j-1];
j := j-1 od;
e[j] := pom od}
e[ 1]... e[i-1]
e[ 1]... e[j-1] pom=e[i], j=i
pom< e[ j+1]... e[i], pom <e[j-1]
Pom < e[ j]e[j+1]... e[i]
pom< e[ j+1]... e[i]
e[ 1] ... e[j-1] pom< e[ j+1]... e[i]
e[ 1] ... e[j-1] e[j] < e[ j+1]... e[i]
Poprawność sortowania przez wstawianie
Algorytm sortowania przez wstawianie poprawnie rozwiązuje problem sortowania w każdej liniowo uporządkowanej strukturze danych.
Algorytm sortowania przez wstawianie jest, w każdej liniowo uporządkowanej strukturze
danych, całkowicie poprawny ze względu na warunek początkowy n>0 i warunek końcowy (1<i n) e[i-1] e[i] .
Koszt sortowania przez wstawianie
W(n) =
i=2...n (koszt maksymalny pętli wewnętrznej) =
i=2...n (i-1) = n(n-1)/2 = O(n2)A(n) =
i=2...n (koszt średni pętli wewnętrznej)=Element pom z
prawdopodobieństwem 1/i może zająć dowolną z pozycji
od 1 do i.
Operacja dominująca - porównywanie elementów.
=
i=2...n (
j=1...i j*(1/i)) =
i=2...n (1/i)(i (i+1))/2 = (n+1)(n+2)/4 - 1.5 = (1/4)n2 +O(n)Sortowanie przez scalanie
(1) Dzielimy zadanie posortowania całego ciągu na dwa podzadania: posortowania jego lewej i prawej połowy.
(2) Gdy obie części tworzą już ciągi
uporządkowane, wtedy scalamy je otrzymując rozwiązanie.
Przykład
16 5 12 4 10 6 1 13 15 7 1 14 9 3 8 11
16 5 12 4 10 6 1 13 15 7 1 14 9 3 8 11 16 5 12 4 10 6 1 13
16 5 12 4 5 16 4 12 4 5 12 16
10 6 6 10
1 6 10 13 1 13 1 13
1 4 5 6 10 12 13 16
15 7 1 14 9 3 8 11
1 3 7 8 9 11 14 15 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Operacja scalania
Dane są dwa ciągi X i Y, uporządkowane niemalejąco, x1,...xn i y1,...ym. Utworzyć ciąg e=e1,...e n+m złożony z elementów obu ciągów uporządkowany niemalejąco.
Wp = {n>0 m>0, x1... xn i y1... ym }
Wk = { e1... en+m , (in+m)( j)( ei=xj lub ei =yj)}
1
2 4 5 8 9 1 3 6 7
2 3 4 5 6 7 8 9
Algorytm scalania
{i:=1; j := 1;
k :=1,
while (i n and j m) do if x[i]< y[j] then
e[k] := x[i];
i := i +1 else
e[k] := y[j];
j := j +1 fi;
k := k+1;
od;
if ( j > m) then for i := i to n do
e[k] := x[i]; k := k+1 od
Else
for j := j to m do
e[k] := y[j]; k := k+1 od}
{k= i+j-1, e[1]... e[k-1] i wszystkie elementy x[1],...,x[i-1] oraz y[1],...,y[j-1]
zostały już umieszczone na pozycjach od 1 do k-1 w ciągu e .}
O(n+m)
Specyfikacja procedury scal(k,x,l)
Wersja procedury scal (lewy,x,prawy) użyta w algorytmie Sortowania przez scalanie ma następującą specyfikację
Wp = {lewy x prawy e[lewy] e[lewy+1] … e[x]
e[x+1] e[x+2] … e[prawy]}
Wk = {e[lewy] e[lewy+1] …e[x] e[x+1] … e[prawy] }
Twierdzenie (*)
Procedura scal(k, x,l) zastosowana do dowolnego ciągu
e[1],...,e[n] jest całkowicie poprawna ze względu na podaną wyżej specyfikację.
Sortowanie przez scalanie
procedure MS(lewy, prawy : integer);
begin
if prawy>lewy then
x := (lewy+ prawy) div 2;
MS(lewy,x);
MS(x+1, prawy);
scal (lewy, x, prawy) fi
end MS;
Jeśli lewy = prawy, to jest tylko jeden element w naszym ciągu.
W tym wywołaniu rozważamy lewą „połowę” danego ciągu
Z założenia indukcyjnego : e[lewy] ... e[x]
W tym wywołaniu rozważamy prawą „połowę” danego ciągu Z założenia indukcyjnego :e[x+1] ... e[prawy]
Na mocy Tw (*) : e[lewy] ... e[prawy]
Koszt algorytmu Merge_Sort
Załóżmy, że n = 2 p. wtedy T(n) = T(n/2) + T(n/2) + cn T(1) = 0
T(n) = (n lg (n))
Po podstawieniu mamy T(2 0) = 0
T(2 p) = 2 T(2 p-1) +c n T(2 p ) = 2 T(2 p-1) + c2 p = 2(2T(2 p-2) +c 2 p-1) + c2 p =
2 2 T(2p-2 ) + c2 p + c2 p = ...= 2 p T(2 0 ) + cp 2 p = ( n lg n)
Szybkie sortowanie
Krok 1. Rozdzielić elementy danego ciągu e1,e2,... ,en na dwie części względem pewnego ustalonego elementu, tzw.
mediany, tak by a lewo od niego znajdowały się elementy mniejsze, a na prawo elementy większe.
Krok 3. Posortować elementy znajdujące się na prawo od mediany.
Krok 2. Posortować elementy na lewo od mediany.
Metoda :
Przykład wykonania
10 5 7 8 14 12 3 4 1
Rozdzielanie ze
względu na wybraną
medianę 5 7 8 1 4 3 10 12 14
Stosujemy
rekurencyjnie tę samą zasadę do obu części
3 4 1 5 8 7
1 3 4 7 8
14 12
1 3 4 5 7 8 10 12 14
split
Sortowanie szybkie - algorytm
Dane: n>0, ciąg e[1],..., e[n].
procedure QS(lewy, prawy) {if (prawy > lewy) then Split (lewy, prawy,j);
QS(lewy,j-1);
QS(j+1,prawy);
fi }
{e[lewy],..., e[j-1]}< e[j] {e[j+1],...,e[prawy]}
e[lewy] ... e[j-1] e[j] {e[j+1],...,e[prawy]}
e[lewy] ... e[j-1] e[j] e[j+1] ... e[prawy]
lewy prawy
Najgorszy przypadek
Koszt Operacji rozdzielania SPLIT dla n elementowego ciągu wynosi n-1 porównań.
Koszt pesymistyczny algorytmu Quicksort mierzony liczbą porównań wynosi :
W(n) = (n 2)
1 2 3 4 5 6 7 8 9
Jeśli Split jako medianę wybiera zawsze pierwszy element, to w wyniku
rozdzielenia, jedna część „młodsza”
będzie pusta , a druga „starsza” będzie zawierała o jeden element mniej niż w poprzednim kroku.
W(n) = (n-1) +W(n-1)= (i-1) = (n )
Koszt średni
Koszt średni algorytmu QuickSort, mierzony liczbą porównań, wynosi
A(n) = (n lg n)
A(n) = (n-1) + j=1...n (1/n (A(j-1) + A(n-j))) A(0) = 0
1 j n
j-1 n-j
Zakładamy, że wszystkie ustawienia elementów w ciągu i każdy podział w wyniku Split są jednakowo
prawdopodobne.
A(0) = 0
A(n) = (n-1) + j=1...n-1 A(j) 2/n A(n)=cn lg n