Przykłady algorytmów Cz. 2 Sortowaniektm.umg.edu.pl/~pik/prokom/pliki/pp-w05.pdf•O(n!)...

43
Podstawy programowania Przykłady algorytmów Cz. 2 Sortowanie

Transcript of Przykłady algorytmów Cz. 2 Sortowaniektm.umg.edu.pl/~pik/prokom/pliki/pp-w05.pdf•O(n!)...

  • Podstawy programowania

    Przykłady algorytmówCz. 2 Sortowanie

  • Złożoność obliczeniowa

    Złożoność obliczeniowa to ilość zasobów niezbędna do wykonania algorytmu. Wyróżnia się dwa rodzaje:

    • Złożoność czasową (obliczeniową)

    • Złożoność pamięciową

    Złożoność zależy od rozmiaru danych wejściowych(np. sortowanie, niezależnie od użytego algorytmu, będzie trwało tym dłużej, im większy jest zbiór sortowanych elementów)

  • Złożoność obliczeniowa

    Istnieje wiele miar złożoności obliczeniowej i wiele notacji. W większości miar najważniejsze jest asymptotyczne tempo wzrostu (tj. jak złożoność rośnie ze wzrostem rozmiaru danych).

    Do porównywania algorytmów najczęściej używa się notacji O (dużego O), która podaje asymptotyczne ograniczenie górne

    W notacji O uwzględnia się tylko funkcję dominującą i pomija sięwspółczynniki liczbowe, ponieważ dla odpowiednio dużych zbiorów danych współczynniki stałe mają mniej istotne znaczenie niż rodzaj funkcji dominującej, np.

    • f(n) = 100 n2 + 50 n => O(n2)

    • f(n) = 1000 n => O(n)

  • Złożoność obliczeniowa

    Przykładowe złożoności algorytmów, w porządku rosnącym:

    • O(1) – złożoność stała,

    • O(logn) – złożoność logarytmiczna,

    • O(n) – złożoność liniowa,

    • O(nlogn) – złożoność liniowo-logarytmiczna,

    • O(n2) – złożoność kwadratowa,

    • O(nk) – złożoność wielomianowa (k jest stałą),

    • O(kn) – złożoność wykładnicza (k jest stałą),

    • O(n!) – złożoność rzędu silnia

  • Złożoność obliczeniona

    1,0E-06

    1,0E-05

    1,0E-04

    1,0E-03

    1,0E-02

    1,0E-01

    1,0E+00

    1,0E+01

    1,0E+02

    1,0E+03

    1,0E+04

    1,0E+05

    1,0E+06

    1,0E+07

    1,0E+08

    1,0E+09

    1,0E+10

    1,0E+11

    1,0E+12

    1 10 100 1000 10000 100000 1000000 10000000 100000000 1000000000 1E+10

    N N*logN N^2 2^N N!

    1000 lat

    100 lat

    1 rok

    1 doba

    1 godz.

    1 min.

    1 s

    1 ms

  • Sortowanie

    Sortowanie• Istnieje wiele algorytmów sortowania; niektóre mają bardzo

    specyficzne lub ograniczone zastosowanie (np. stosowane głównie w systemach wbudowanych albo kiedy możliwość ich użycia zależy od statystycznych cech sortowanego zbioru), inne są uniwersalne.

    • Pierwsze algorytmy sortowania pojawiły się w latach 50 ub. wieku (m.in. bublesort 1956, quicksort 1959), ale ciągle są opracowywane nowe użyteczne algorytmy, które znajdują zastosowania (np. timsort, opracowany w 2002 r., jako hybrydowy algorytm merge sort oraz insertion sort, zastosowany m.in. jako wbudowany algorytm sortowania w językach Python i GNU Octave, oraz na platformach Java SE i Android)

    • Duże zainteresowanie algorytmami sortowania wynika z powszechności zastosowań, dużej złożoności obliczeniowej (oraz potrzeby jej zmniejszenia, ze względu na powszechne i częste zastosowania)

    • Algorytmy sortowania są też wdzięcznym tematem nauki programowania (algorytmy jako takie, ale też np. złożoność obl.)

  • Sortowanie

    Właściwości i kryteria oceny algorytmów sortowania:

    • Złożoność obliczeniowaNie zawsze można ją określić jednoznacznie, ponieważ w wielu algorytmach zależy od rozkładu elementów – dlatego wyznacza sięzłożoność optymistyczną (best), średnią i pesymistyczną (worst);

    • Złożoność pamięciowaAlgorytmy sortowania "w miejscu" ("in-place") zamieniają elementy miejscami i nie potrzebują dodatkowej pamięci na same elementy, jednak czasami zużywają inne zasoby;

    • RekursjaUżycie rekursji zużywa zasoby (stos systemowy), co może ograniczaćzastosowania algorytmów – np. quicksort i syst. wbudowane;Istnieją algorytmy rekusywne, nierekursywne oraz mieszane

  • Sortowanie

    Właściwości i kryteria oceny algorytmów sortowania:

    • StabilnośćAlgorytm sortowania jest stabilny, jeżeli "równe" elementy po sortowaniu zachowują swój porządek sprzed sortowania;

    • Mechanizm sortowania (sposób działania)Wyróżnia się algorytmy sortowania przez porównanie ("comparisonsort") oraz takie, które porównania nie używają ("integer sort");

    • Metoda sortowaniaWyróżnia się kilka ogólnych metod: sortowanie przez wstawianie, zamiany, wybór, łączenie – konkretne algorytmy należą do jednej z metod ogólnych, np. bublesort jest sortowaniem przez zamiany

  • Sortowanie

    Właściwości i kryteria oceny algorytmów sortowania:

    • AdaptacyjnośćZdolność do skrócenia czasu sortowania, jeżeli zbiór jest częściowo posortowany (ma część elementów we właściwej kolejności) – np. bublesort ma śrenią złożoność O(n2) oraz optymistyczną O(n), natomiast insertion sort zawsze O(n2)

    • Złożoność implementacjiNiektóre hybrydowe algorytmy sortowania mają bardzo złożone algorytmy, a ich implementacja może być kłopotliwa dla początkującego programisty; inne, jak bublesort, są dziecinnie proste

  • Sortowanie

    Algorytmy sortowania stabilne:

    • sortowanie bąbelkowe (ang. bubblesort) O(n2)

    • sortowanie przez wstawianie (ang. insertion sort) O(n2)

    • sortowanie przez scalanie (ang. merge sort) O(nlogn)– wymaga dodatkowej pamięci O(n)

    • sortowanie przez zliczanie (ang. counting sort) O(n+k)– wymaga dodatkowej pamięci

    • sortowanie kubełkowe (ang. bucket sort) O(n) – wymaga dodatkowej pamięci

    • sortowanie biblioteczne (ang. library sort) O(nlogn)

  • Sortowanie

    Algorytmy sortowania niestabilne :

    • sortowanie przez wybieranie (ang. selection sort) O(n2)

    • sortowanie Shella – (ang. shellsort) O(nlogn)

    • sortowanie szybkie – (ang. quicksort) O(nlogn)

    • sortowanie przez kopcowanie – (ang. heapsort) O(nlogn)

  • Sortowanie

    Złożoność obliczeniowa

    • Sortowanie przez porównanieNa sortowanie przez porównanie składają się dwie operacje: porównywania elementów i (ewentualnie) ich zamiany – która z tych operacji jest bardziej krytyczna?

    Przy sortowaniu liczb więcej czasu zajmie zamiana (wykonywana w trzech krokach: temp � ti, ti � tj, tj � temp)

    Przy sortowaniu złożonych danych – wielokrotnie więcej czasu zajmie porównanie (np. "Kowalski, Bartłomiej" i "Kowalski, Bartosz"); zamiana zwykle nie dotyczy samych danych – to strata czasu – tylko referencji do nich

    Uwaga! W dalszej części zamiana liczb, dla skrócenia zapisu, będzie oznaczana tak: ti ↔ ti+1 – odpowiada to trzykrokowej operacji powyżej

  • Sortowanie

    Złożoność obliczeniowa

    • Sortowanie przez porównanie…Najprostsze ("naturalne", intuicyjnie zrozumiałe) algorytmy sortowania mają złożoność obliczeniową O(n2) – średnią lub pesymistycznąDolne ograniczenie złożoności wynosi O(nlogn) – istnieją zarówno stabilne, jak i niestabilne algorytmy o tej złożoności

    • Sortowanie bez porównań ("integer sort")Najprostsze algorytmy mają złożoność O(n⋅k) lub O(n+r)(k oznacza rozmiar klucza sortowania, r – zakres wartości kluczy)Dolne ograniczenie złożoności wynosi O(n), jednak jest osiągalne tylko dla algorytmów niestabilnychSortowanie bez porównań ma istotne praktyczne ograniczenia (co do złożoności klucza sortowania oraz rozkładu wartości kluczy) oraz zwykle większą złożoność pamięciową, co najmniej O(n)

  • Sortowanie bąbelkowe

    • Sortowanie bąbelkowe jest chyba najprostszym pojęciowo oraz najłatwiejszym w implementacji algorytmem sortowania

    • Polega na porównywaniu sąsiednich elementów i w razie potrzeby ich zamianie; Przy N elementach trzeba N-1 porównań, a cały proces trzeba powtórzyć co najwyżej N-1 razy

    FOR n=0, 1, …, N-2

    FOR i=0, 1, …, N-2

    IF ti > ti+1ti ↔ ti+1

  • Sortowanie bąbelkowe

    • Udoskonalenia (1)Algorytm można zakończyć, jeżeli wewnętrzna pętla nie wykona ani jednej zamiany (wtedy zewnętrzna pętla może być nieskończona)

    FOR ∞

    Z = false

    FOR i=0, 1, …, N-2

    IF ti > ti+1

    ti ↔ ti+1Z = true

    IF NOT(Z) BREAK

  • Sortowanie bąbelkowe

    • Udoskonalenia (2)Po pierwszym wykonaniu pętli wewnętrznej element największy znajdzie się na samym końcu ("wygra" wszystkie porównania)� liczba porównań pętli wewnętrznej może być zmniejszana

    • Można połączyć z (1)

    FOR n=0, 1, …, N-2FOR i=0, 1, …, NNNN----nnnn----2222IF ti > ti+1ti ↔ ti+1

    FOR n=Nn=Nn=Nn=N----2, N2, N2, N2, N----1, 1, 1, 1, …………, 2, 2, 2, 2FOR i=0, 1, …, nnnnIF ti > ti+1ti ↔ ti+1

  • Sortowanie bąbelkowe

    • Udoskonalenia (3)Jeżeli zbiór będzie prawie posortowany, z wyjątkiem ostatniego elementu (np. { 2, 3, 4, 5, 6, 1 }), to algorytm będzie miał złożonośćO(n2), chociaż mógłby mieć O(n) gdyby odwrócić kierunek sortowania – aby takiemu efektowi zapobiec, wewnętrzna pętla powinna na zmianę biec w górę i w dół zbioru, po coraz krótszym jego fragmencie

    FOR ∞l = 1r = N-2

    FOR i = l, l+1, …, r

    IF ti > ti+1 => ti ↔ ti+1r � r-1

    FOR i = r, r-1, …, l

    IF ti > ti+1 => ti ↔ ti+1l � l+1

  • Sortowanie gnoma

    • Ciekawy wariant sortowania bąbelkowego, przedstawiany w formie dykteryjki: gnom (niezbyt inteligentny) ma w ogrodzie poukładaćdoniczki z kwiatami wg wysokości kwiatów; zaczyna od jednej strony (np. od lewej) i porównuje kwiat, przy którym stoi z następnym. Jeżeli są we właściwej kolejności, to przechodzi dalej, jeżeli nie, to zamienia je miejscami i cofa się (chyba że już jest na samym początku)

    3 4 5 1 6 2

  • Sortowanie gnoma

    • UlepszenieWadą algorytmu jest jałowe powtarzanie porównań podczas poruszania się w prawo, zaraz po cofnięciu się w lewo:

    3 4 5 1 62

  • Sortowanie gnoma

    • UlepszenieWadą algorytmu jest jałowe powtarzanie porównań podczas poruszania się w prawo, po cofnięciu się w lewoWiadomo, że od bieżącego miejsca aż do miejsca, gdzie dotarłpoprzednio, kwiaty są posortowane => powinien to miejsce zaznaczyć(powiesić tam swoją czapkę?) i od razu tam wrócić

    3 4 51 62

  • Sortowanie gnoma

    • Wersja bez ulepszenia

    p = 0WHILE p tp+1tp ↔ tp+1IF p>0 p � p-1

    ELSEp � p+1

    3 4 5 1 6 2

  • Sortowanie przez wstawianie

    • Przypomina układanie kart w ręku gracza: dzielimy elementy zbioru na część posortowaną (z lewej) i nieposortowaną (z prawej); należy pobrać pierwszy element z części nieposortowanej i – przesuwając sięw lewo – znaleźć miejsce, gdzie powinien się znaleźć. Elementy posortowane, od tego miejsca aż do części nieposortowanej, należy przesunąć w prawo, żeby zrobić miejsce dla elementu wstawianegoFOR n = 1, 2, …, N-1temp = tni = n – 1WHILE i>=0 AND ti>tempti+1 � tii � i-1

    ti+1 = temp

  • Sortowanie przez wybieranie

    • Koncepcyjnie zbliżony do sortowania przez wstawianie, ale niestabilny. Najprostsza wersja polega na szukaniu minimum dla nieposortowanej przez kolejne porównania i (ewentualnie) zamiany elementu z początku tej części ze wszystkimi kolejnymi elementami:FOR n = 0, 1, …, N-2FOR i = n+1, n+2, … N-1IF ti

  • Sortowanie przez wybieranie

    • Najlepszy wariant sortowania przez wybieranie polega na szukaniuminimum w nieposortowanej (prawej) części zbioru i jednorazowej zamianie miejscami minimum oraz pierwszego elementu tej części:FOR n = 0, 1, …, N-2min = tnpoz = nFOR i = n+1, n+2, … N-1IF ti < minmin � tipoz = i

    tn ↔ tpoz

  • Sortowanie – złożoność obliczeniowa

    • Wszystkie proste algorytmy mają złożoność średnią O(n2).Np. dla sortowania przez wybieranie, dla N elementów jest (N/2)(N-1) porównań:

    E E E E E

    P P P P

    P P P

    P P

    P

    N-1

    N-1

  • Sortowanie medianowe

    • Koncepcja z pozoru słuszna, w praktyce zupełnie nieprzydatna,leżąca u podstaw innych wydajnych algorytmów, a w tym bardzo popularnych (np. quicksort), o złożoności O(nlogn)

  • Sortowanie medianowe

    • Skoro proste sortowanie ma złożoność O(n2) to może zróbmy tak: podzielmy zbiór na dwie połowy i posortujmy je oddzielnie – czas powinien być 2 x mniejszy: 2 x (n/2)2 = ½ n2

    Skoro tak, to czemu tego nie powtórzyć – dzieląc połowy tablicy znów na połowy, znowu zyskamy na czasie: 4 x (n/4)2 = ¼ n2

    Gdyby powtórzyć to parę razy (aż do fragmentu tablicy o rozmiarze 1), to takich kroków będzie log2n, a złożoność obliczeniowa n⋅logn

  • Sortowanie medianowe

    • Tak może wyglądać pierwszy etap sortowania:

    Niestety powstaje pewien drobny problem – posortowane oddzielnie tablice po połączeniu wcale nie tworzą tablicy posortowanej…

    2 14 7 15 3 19 5 11

    2 14 7 15 3 19 5 11

    2 7 14 15 3 5 11 19

  • Sortowanie medianowe

    • Tak może wyglądać pierwszy etap sortowania:

    Niestety powstaje pewien drobny problem – posortowane oddzielnie tablice po połączeniu wcale nie tworzą tablicy posortowanej…

    Rozwiązanie: załóżmy, że M jest medianą tablicy (tj. dokładnie połowa elementów jest od M mniejsza, a połowa większa) – trzeba przerzucićte mniejsze na lewo, a większe na prawo, a dopiero potem sortować

    2 14 7 15 3 19 5 11

    2 14 7 15 3 19 5 11

    2 7 14 15 3 5 11 19

  • Sortowanie medianowe

    • Sortowanie medianowe (pierwszy etap):

    Wygląda na to, że błąd został poprawiony.

    Skoro tak, to czynności można powtórzyć, dzieląc tablice znowu na połowy…

    2 14 7 15 3 19 5 11

    2 14 7 15 3 19 5 11

    2 5 7 3 15 19 14 11

  • Sortowanie medianowe

    • Sortowanie medianowe (całość):

    2 14 7 15 3 19 5 11

    2 14 7 15 3 19 5 11

    2 5 7 3 15 19 14 11

    2 5 7 3 15 19 14 11

    2 3 7 5 11 14 19 15

    2 3 7 5 11 14 19 15

    2 3 5 7 11 14 15 19

  • Sortowanie medianowe

    • Sortowanie medianowe (całość):

    W sortowaniu medianowym sortowanie polega wyłącznie na przerzucaniu elementów pomiędzy połówkami zbioru – nie porównuje się elementów z innymi elementami, a wyłącznie z medianą, złożoność obliczeniowa jest najlepsza możliwa, O(nlogn), jest jednak pewien problem…

    2 14 7 15 3 19 5 11

    2 5 7 3 15 19 14 11

    2 3 7 5 11 14 19 15

    2 3 5 7 11 14 15 19

  • Sortowanie medianowe

    • Sortowanie medianowe (całość):

    W sortowaniu medianowym sortowanie polega wyłącznie na przerzucaniu elementów pomiędzy połówkami zbioru – nie porównuje się elementów z innymi elementami, a wyłącznie z medianą, złożoność obliczeniowa jest najlepsza możliwa, O(nlogn), jest jednak pewien problem… złożoność szukania mediany jest O(n2)

    2 14 7 15 3 19 5 11

    2 5 7 3 15 19 14 11

    2 3 7 5 11 14 19 15

    2 3 5 7 11 14 15 19

  • Sortowanie medianowe

    • Sortowanie medianowe, przez złożoność szukania mediany O(n2), jest nieopłacalne, jednak sama koncepcja podziału zbioru na połowy leży u podstaw dwóch wydajnych algorytmów sortowania (i pewnej liczby ich odmian i wariacji):

    • Sortowanie przez scalanie (merge sort) "posortujmy te połówki oddzielnie, potem coś wykombinujemy"

    • Szybkie sortowanie (quicksort)"weźmy coś prostszego zamiast tej całej mediany, potem zobaczymy co wyszło"

  • Sortowanie – złożoność obliczeniowa

    • Algorytmy bazujące na medianowym mają złożoność średnią O(nlogn).Np. dla sortowania medianowego jest ≈ N⋅log2N porównań:

    Dla sortowania medianowego, sortowania przez scalanie i szybkiego jest log2N podziałów zbioru na połowy. Oznacza to log2N scaleń (każde ≈N porównań) w merge sort, podobnie log2N poziomów rekurencji i przerzucania (małe na lewo duże na prawo, ≈N porównań) w quicksort

    E E E E E

    P P P P

    P P P

    log2N

    N-1

    P

  • Sortowanie przez scalanie

    • Sortowanie przez scalanie (merge sort) polega na oddzielnym sortowaniu (hipotetycznie) połówek zbioru, a następnie ich scaleniu, prze wybieranie z lewej i prawej części kolejnych najmniejszych elementów; pomysł jest powielany na kolejnych poziomach podziału zbioru na połowy

    2 14 7 15 3 19 5 11

    2 14 7 15 3 19 5 11

    2 7 14 15 3 5 11 19

    2 3

  • Szybkie sortowanie

    • Szybkie sortowanie (quicksort)jest najbliższe sortowaniu medianowemu, tyle że zamiast mediany jest brana wartość ją zastępująca – zależnie od implementacji:– wartość z arytmetycznego środka zbioru (najczęściej)– wartość z losowo wybranej pozycji– średnia arytmetyczna z kilku pozycji

    • Optymistyczna złożoność obliczeniowa O(nlogn), pesymistyczna O(n2)(bardzo mało prawdopodobna)

    • Zaimplementowana starannie pod kątem wydajności jest 2-3 x szybsza niż merge sort i heap sort;

    • Jest wykonywany "w miejscu", jednak rekurencyjnie - zużywa stos, co w komputerach o małych zasobach jest kłopotliwe

    • Jest niestabilny (istnieją też warianty stabilne)

  • Szybkie sortowanie

    • Szybkie sortowanie (quicksort) – implementacja

    Implementacja pozornie jest prosta:QUICSORT (t, p, k)s = PodzielTablice(t, p, k)QUICKSORT(t, p, s)QUICKSORT(t, s+1, k)

    W praktyce podział (z przerzuceniem elementów mniejszych od "pseudo-mediany" na prawo i mniejszych od niej na lewo) jest kłopotliwy, ponieważ części tablicy nie muszą być równe (zależą od wartości "pseudo-mediany")

    Istnieją implementacje wykorzystujące 3 pętle while, dość trudne:WHILE (pp kk)WHILE (ppquasiMed) kk—tpp ↔ tkk

  • Szybkie sortowanie

    • Szybkie sortowanie (quicksort) – implementacja

    Najprostsza implementacja używa jednej pętli for i sprytnej sztuczki: ustawia "pseudomedianę" pomiędzy lewą i prawą częścią tablicy, dzięki czemu dalsze sortowanie tego elementu już nie dotyczy:QUICSORT (t, p, k)

    IF (p >= k) return; // koniec rekurencji

    pivotPoint = p + (k-p)/2 // środek tablicy pivotValue = t[pivotPoint] // pseudomediana

    t[pivotPoint] ↔ t[k] // "środek" na koniec

    splitPoint = p // punkt podziału tabl.

    FOR i=p, p+1, …, k-1 // bez "środka"!!!IF t[i]

  • Sortowanie bez porównywania

    • Sortowanie bez porównywania ma ograniczone zastosowania dla zbiorów o równomiernym rozkładzie kluczy, należących do skończonego zbioru, najlepiej o kluczach całkowitych, przy niewielkiej liczbie kluczy "pustych" – wówczas osiąga złożoność O(n)

    • Wadą tych algorytmów jest złożoność pamięciowa przynajmniej O(n)

  • Sortowanie przez zliczanie

    • Sortowanie przez zliczanie (counting sort)

    Może być stosowana dla zbiorów o równomiernym rozkładzie kluczy należących do skończonego zbioru – wówczas osiąga złożoność O(n+k) oraz złożoność pamięciową O(n+k), gdzie n – oznacza liczebnośćzbioru, k – rozpiętość wartości kluczy

    Polega na utworzeniu histogramu, tj. policzeniu liczby wystąpień kluczy o tej samej wartości. Później wartości są wkładane wprost na pozycjęwynikającą z histogramu

    • Jest stabilny

    • W skrajnym przypadku (kiedy klucze nie powtarzają się) ma złożonośćobliczeniową O(n) i pamięciową O(k)

    • Nie nadaje się do sortowania złożonych danych (np. dane osobowe, alfabetycznie) – co prawda klucze da się wyznaczyć, ale rozpiętość jest zbyt duża i złożoność pamięciowa jest nie do przyjęcia

  • Sortowanie kubełkowe

    • Sortowanie kubełkowe (bucket sort)

    Wariant koncepcji poprzedniej – należy podzielić zakres rozpiętości kluczy na k przedziałów (kubełków), następnie przeglądając elementy zbioru przypisywać je do odpowiedniego kubełka na podstawie wartości klucza (złożoność O(n)). Niepuste kubełki trzeba posortować(stosując mniejsze kubełki) itd.

    • Przy równomiernym rozkładzie kluczy całkowitych osiąga złożonośćO(n+k) oraz złożoność pamięciową O(n+k) i jest stabilny

  • Bogosort

    • Pseudosortowanie (bogus sort)

    Jeden z algorytmów zaprojektowanych tak, aby były jak najwolniejsze. Polega na losowym ustawianiu elementów zbioru przy każdej iteracji i sprawdzaniu, czy przypadkiem kolejność nie jest właściwa (niemalejąca). Możliwych permutacji zbioru n elementów jest n!, stąd złożoność obliczeniowa O(n!)

    • Już przy kilkudziesięciu elementach czas sortowania trzeba liczyć w miliardach lat (np. 25! ≈ 1,5⋅1025, 50! ≈ 3⋅1064)FOR ∞

    gotowe = true

    FOR i=0, 1, …, N-1IF ti>ti+1gotowe = false;BREAK

    IF gotowe BREEAK

    FOR i=N-1, N-2, …, 1ti ↔ trandom(i)