background image

 

 

1

Algorytmy i 
struktury 
danych

Wykład 4

background image

 

 

2

1. Sortowanie Shella (ang. 
shellsort) 

Sortowanie polegające na podzieleniu 
tablicy na kilka podtablic. 

Dokonujemy tego wybierając liczby 
przeskakując po tablicy (pierwotnej) co 
h pozycji. Każdą z tych h podtablic 
sortujemy, zmniejszamy h i znów 
sortujemy kolejne (większe) podtablice. 
Operację tą wykonujemy aż będziemy 
mieli jedną tablicę do posortowania. 

background image

 

 

3

Sortowanie podtablic

Do posortowania podtablic wykorzystujemy 

algorytm sortowania przez wstawianie, lub 

sortowanie bąbelkowe. 

Wynika to z faktu, że co krok algorytmu 

nasza tablica będzie „coraz bardziej 

posortowana”, a algorytmy te są tym 

bardziej efektywne, im bardziej jest 

„posortowana” tablica wejściowa. 

Nie wolno wykorzystywać sortowania przez 

wybór, gdyż wtedy złożoność sortowania 

wzrośnie do O(n

2

).

background image

 

 

4

Przykład sortowania 
Shella

Ustalamy, że sortujemy co 3 (h=3). 
Podzielone podtablice zaznaczamy na 
rożne kolory.

Tablica do posortowania:

background image

 

 

5

background image

 

 

6

Złożoność sortowania 
Shella

Stwierdzono, że jeżeli pierwszy krok 
będzie sortował co (16n/π)

1/3

, a drugi co 1 

to złożoność takiego algorytmu będzie 
wynosić O(n

5/3

). 

Aby uzyskać złożoność O(n

5/4

) kroki 

należy dobierać wg tzw. zasady Knutha

Złożoność tego algorytmu jest lepsza od 
O(n

2

), ale ciągle brakuje jej do 

osiągnięcia złożoności O(ln n).

background image

 

 

7

Zasada Knutha

Ostatni krok jest zawsze równy 1, a każdy poprzedni 

powstaje z pomnożenia go przez 3 i dodania 1. 

Dodatkowo należy dobrać pierwszy (h

k

) krok tak, że 

krok h

k

+2 byłby większy lub równy liczbie n (ilość 

elementów w tablicy).

 h

1

=1

 h

i

=3h

i-1

+1

 h

k

 (początkowe) musi być tak dobrane, aby h

k

+2 ≥ 

n, gdzie n to ilość liczb do posortowania.

Wartości h

i

 będą więc wynosić: 1, 4, 13, 40, 121, 364, 

1093,…

Należy zauważyć, że minimalna ilość liczb musi być 

równa przynajmniej 13. Dla mniejszej tablicy stosujemy 

sortowanie przez wstawianie od razu dla całej tablicy.

background image

 

 

8

Przykład stosowania 
zasady Knutha

Mamy n=1000 liczb.

Wartości h

i

 będą wynosić: 

1,4,13,40,121,364,1093,…

Wartość pierwszego kroku 
dobieramy zgodnie z podanym 
warunkiem, a więc będzie to h

5

=121 

ponieważ h

7

 jest już większe od 1000 

(1093). 

background image

 

 

9

2. Sortowanie przez 
kopcowanie (ang. 
heapsort)

Do tego algorytmu 
wykorzystywany jest kopiec

Algorytm ten ma złożoność rzędu 
O(ln n).

background image

 

 

10

Algorytm
sortowani
a przez 
kopcowan
ie

background image

 

 

11

3. Sortowanie szybkie 
(ang. quicksort)

Dzielimy daną tablicę liczb na dwie 
podtablice, gdzie każdy element 
pierwszej podtablicy będzie nie 
większy od każdego elementu 
drugiej. 

To samo robimy z każdą podtablicą 
z osobna (rekurencyjnie) aż 
uzyskamy tablice jednoelementowe. 

background image

 

 

12

W celu podziału tablicy wybieramy element wg 

którego będziemy dokonywać podziału. 

Można wybrać pierwszy element tablicy (tak jak 

w poniższym przykładzie), ale można również 

wybrać losowo element z tablicy. Drugi wariant 

zabezpiecza nas przed sytuacją, w której nasza 

tablica do posortowania będzie prawie 

uporządkowana (co spowoduje niekorzystne 

mało równomierne podziały). Wtedy wybranie 

losowego elementu spowoduje w miarę 

równomierny podział na podtablice.

Dzięki losowemu składnikowi nie stracimy na 

efektywności algorytmu.

background image

 

 

13

Do podziału na podtablice można wykorzystać dwa 

wskaźniki: np. niebieski i czerwony

Pierwszy (niebieski ) przesuwamy w lewo dopóty, 

dopóki wskazuje na element większy lub równy 

elementowi dzielącemu. 

Czerwony wskaźnik przesuwamy w prawo aż nie 

napotka na element większy lub równy elementowi 

dzielącemu. 

Dopóki wskaźniki się nie minęły to zamieniamy 

miejscami elementy na które wskazują wskaźniki i 

przesuwamy je dalej. Jeśli się miną to tablica zostanie 

podzielona na dwie części. Dla każdej podtablicy 

wykonujemy to samo.

background image

 

 

14

Przykład: pierwszy podział 
na 2 podtablice.

background image

 

 

15

Cd. 
przykładu:Rekurencyjny 
podział podtablic

background image

 

 

16

Złożoność algorytmu 

W sytuacji pesymistycznej, gdy w każdym 

przypadku jako element dzielący wybierzemy 

element najmniejszy (lub największy) z 

tablicy. Podział będzie wtedy zawsze na 

pojedynczy element oraz resztę tablicy. Wtedy 

złożoność algorytmu quicksort wyniesie O(n

2

). 

Wariant optymistyczny zakłada, że każda 

podtablica podzieli się zawsze dokładnie na 

pół. 

W sytuacji pośredniej oczekiwana złożoność 

algorytmu wynosi O(ln n).

background image

 

 

17

4. Sortowanie przez 
scalanie (ang. mergesort)

Sortowanie wykorzystujące rekurencję.

Złożoność tego algorytmu wynosi O(
ln n).

Dzielimy tablicę z liczbami na 2 
podtablice. Te podtablice znów 
dzielimy na dwie. Czynność tą 
wykonujemy do uzyskania tablic 
jednoelementowych. 

background image

 

 

18

Innymi słowy wykorzystujemy metodę 
dziel i rządź - dzielimy problem, 
którego nie potrafimy rozwiązać na 
mniejsze. 

Te małe problemy są już tak proste, że 
ich rozwiązanie nie sprawia nam 
problemów. Po podzieleniu tablicy 
scalamy te podtablice jednocześnie 
sortując. 

background image

 

 

19

Sortowanie odbywa się poprzez 
zastosowanie pewnego schematu 
scalania tablic. 

Mamy dwie podtablice: pierwsza ma 0-n 
elementów i jest indeksowana zmienną 
a, druga ma 0-m elementów i jest 
indeksowana zmienną b oraz tablice 
wynikową (scaloną) o n+m elementach 
– indeksowaną zmienną c.

background image

 

 

20

background image

 

 

21

Algorytmy 
rekurencyjne

background image

 

 

22

Metoda dziel i rządź

Czasami problem można rozłożyć na 

coraz to prostsze, których rozwiązanie 

nie wymaga wysiłku lub wymaga bardzo 

małego wysiłku. Rozwiązujemy te proste 

problemy wracając do coraz 

trudniejszych, aż okaże się, że główny 

(wyjściowy) problem został rozwiązany. 

Nazywa się to metodą dziel i rządź

Metoda ta często okazuje się bardzo 

skuteczna, a niekiedy nawet jedyna.

Rekurencję bardzo wygodnie się implementuje, 

stosujemy po prostu wywołania podprogramów, 

a maszyna cyfrowa liczy za nas.

background image

 

 

23

1 Silnia (liczona 
rekurencyjnie)

Przykład: mamy obliczyć 5!. Korzystamy 
ze wzoru, bo wiemy, że 5! to 4! 
mnożone przez 5. Podobnie dalej.

background image

 

 

24

Rozkładamy(zagłębiamy się w rekurencji) tak 

długo, aż dojdziemy do problemu, który 

potrafimy rozwiązać (

na niebiesko

): 1! - która 

z definicji wynosi 1.

Następnie wracamy z powrotem. Wiedząc ile 

wynosi 1! umiemy obliczyć 2!.

Wiedząc ile wynosi 2! Umiemy obliczyć 3! itd. 

W ten sposób udało się obliczyć silnie z 5.

Czyli: wykorzystaliśmy rekurencję do 

rozłożenia problemu na szereg mniejszych, 

czyli zastosowaliśmy metodę dziel i rządź.

background image

 

 

25

2. NWD – największy 
wspólny dzielnik 
(algorytm Euklidesa)

Wzory:

NWD(x,y) = NWD(y,x % y) dla x>y
NWD(y,x) = NWD(x,y % x) dla y>x

gdzie % oznacza dzielenie modulo.

Rozkładamy problem na mniejsze według tego 

wzoru do mementu, aż gdy drugi z 

argumentów osiągnie wartość zero.

background image

 

 

26

Przykład algorytmu 
Euklidesa

Mamy obliczyć NWD dla 26 i 8.

Obliczamy NWD dla 8 i (26%8)=2
Obliczamy NWD dla 

i (8%2)=

0

Gdy druga z liczb będzie równa 
zero to pierwsza jest NWD. Zatem 
NWD dla 26 i 8 wynosi 2.

background image

 

 

27

3. Wypisywanie wyrazu 
od ko
ńca

Aby rozwiązać to zadanie 
rozbijamy je na dwa kroki: 

1. próbujemy wypisać w odwrotnej 

kolejności wszystko z wyjątkiem 
pierwszego znaku (wywołujemy 
rekurencyjnie nasz podprogram) 

2. wypisujemy pierwszy znak 

wyrażenia.

background image

 

 

28

Przykład: słowo „notatka”

background image

 

 

29

Przykładowe zastosowanie: chcemy 

wypisać od końca zawartość naszej 
listy jednokierunkowej (np. 
posortowanej rosnąco, gdy my 
potrzebujemy posortowanej 
malejąco).

background image

 

 

30

Algorytmy zachłanne 
(ang. greek algorithm 
(sic!))

Algorytmy te nie przewidują tego, 
co będzie „potem”. Interesuje ich 
tylko „teraz”.

Innymi słowy postępują zachłannie. 
Takie działanie nie zawsze okazuje 
się najlepsze, ale bywa że jest 
idealne.

background image

 

 

31

Problem kasjera

Mając następujący system monetarny: 1, 2, 
5, 10, 20, 50 wydajemy resztę w taki 
sposób, aby stracić jak najmniej monet. 
Postępując zgodnie z zasadami algorytmu 
zachłannego za każdym razem będziemy 
wydawać monety o największym nominale.

Doprowadzi nas to w tym wypadku do 
optymalnego rozwiązania – wydamy 
najmniejszą ilość monet.

background image

 

 

32

Przykład: Wydać 79 
złotych.

wydajemy 50, zostaje 29zł

wydajemy 20, zostaje 9zł 

wydajemy 10, zostaje 9zł

wydajemy 5, zostaje 4zł

wydajemy 2, zostaje 2zł

wydajemy 2, zostaje 0zł

Stosując powyższą metodę wydaliśmy 6 

monet i lepiej się nie da.

background image

 

 

33

Ale - rzecz jasna - nie dla każdej 
sytuacji algorytmy zachłanne dają 
optymalne rozwiązanie.

Przykład: system monetarny: 1,10, 
20, 25.

background image

 

 

34

Kontrprzykład przeciw 
zachłanności

Przykład: Mając system: 1, 10, 20, 25. Wydać 31 

złotych.

– wydajemy 25, zostaje 6zł
– wydajemy 1, zostaje 5zł
– wydajemy 1, zostaje 4zł
– wydajemy 1, zostaje 3zł
– wydajemy 1, zostaje 2zł
– wydajemy 1, zostaje 1zł
– wydajemy 1, zostaje 0zł

Wydaliśmy 7 monet, co nie jest minimalną liczbą 

monet. Można było wydać 20 + 10 + 1 co daje 

tylko trzy monety. W tym przypadku algorytm 

zachłanny nie zadziałał optymalnie.


Document Outline