• Nie Znaleziono Wyników

ALGORYTMY I STRUKTURY DANYCH

N/A
N/A
Protected

Academic year: 2021

Share "ALGORYTMY I STRUKTURY DANYCH"

Copied!
23
0
0

Pełen tekst

(1)

ALGORYTMY I STRUKTURY DANYCH

WYKŁAD 04 Problem Sortowania Grażyna Mirkowska

PJWSTK, 2003/2004

(2)

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

(3)

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 ei1ei2 ... 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

(4)

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

(5)

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]}

(6)

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)

(7)

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

(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

(9)

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]

(10)

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] .

(11)

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)

(12)

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.

(13)

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

(14)

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 , (in+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

(15)

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)

(16)

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ę.

(17)

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]

(18)

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)

(19)

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 :

(20)

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

(21)

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

(22)

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 )

(23)

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

Cytaty

Powiązane dokumenty

Czas dostępu do danych w pamięci zewnętrznej jest zależny od położenia- zaleca się sekwencyjne czytanie i zapis danych, gdyż koszt dostępu niesekwencyjnego

(2.2.4) Wyznacz kolejne serie, po jednej o tym samym numerze z każdego niewyczerpanego pliku wejściowego;. (2.3) Zamień pliki wyjściowe

Zaleca się, aby każdy podprogram posiadał własną planszę (czyli plik o dowolnej nazwie nazwa.lis np.. Plik/Plansza/Nowa i następnie na planszy wywołanej

Zastosuj kod programu genTest.cpp do wygenerowania serii liczb wejsciowych. Za pomoca kodu sortTest.cpp utw´orz wzorcowy output posortowanych serii, kod u˙zywa funkcji

Zastosuj kod programu generatorTest.cpp do wygenerowania serii liczb wejsciowych. Za pomoca kodu wzorcowka.cpp wyprodukuj

Sortowanie w miejscu nie wymaga wykorzystania dodatkowej pamięci zależnej od ilości sortowanych danych, lecz tylko pewną stałą jej ilość.. Sortowanie stabilne zapewnia, iż

Sortowanie takiego pliku kart omawianą metodą polega na tym, że gracz stopniowo dokłada karty do uporządkowanej części kart (początkowo zawierającej jedną kartę)

• Ostatnim krokiem jest zamiana miejscami elementów tablicy: pierwszego i poprzedzającego wskazywany przez zmienną granica – chcemy, aby element osiowy był