Wykład 5 Sortowanie w czasie liniowo

Transkrypt

Wykład 5 Sortowanie w czasie liniowo
Wykład 5
Sortowanie
w czasie liniowologarytmicznym
1
Sortowanie - zadanie
Definicja (dla liczb):
wejście: ciąg n liczb A = (a1, a2, …, an)
wyjście: permutacja (a1,…, a’n) taka, że a’1 ≤ … ≤ a’n
2
Zestawienie czasów działania
Ø  Przez wybór: O(N2) zawsze
Ø  Bąbelkowe: O(N2) najgorszy przypadek; O(N) najlepszy przyp.
Ø  Wstawianie: O(N2) średnio; O(N) najlepszy przypadek
Ø  Shellsort:
O(N3/2)
Ø  Heapsort:
O(NlogN) zawsze
Ø  Mergesort:
O(NlogN) zawsze
Ø  Quicksort:
O(NlogN) średnio; O(N2) najgorszy przypadek
Ø  Zliczanie:
O(N) zawsze
Ø  Radix sort:
O(N) zawsze
Ø  zewnętrzne: O(b logb)) dla pliku o b „stronach”.
3
Plan:
Ø  Trzy algorytmy sortowania:
Ø  Mergesort
Ø  Quicksort
–  Bardzo popularny algorytm, bardzo szybki w średnim przypadku
Ø  Heapsort
–  Wykorzystuje strukturę kopca (heap)
4
Mergesort – pomysł
Ø  Dzielimy ciąg na podciągi, sortujemy te podciągi, a następnie
łączymy zachowując porządek.
–  Przykład algorytmu typu „dziel i zwyciężaj”.
–  Potrzeba dodatkowego miejsca dla tych podciągów – nie jest to
sortowanie „w miejscu”.
•  Można realizować ten proces „w miejscu”, ale rośnie stopień
komplikacji.
–  Często realizowany jako metoda zewnętrzna
5
Mergesort – przykład
ciąg: EASYQUESTION (12 znaków)
podział
EASYQUESTION
ESTION
EASYQU
YQU
EAS
E
AS
A S
Y
QU
Q U
EST
E
ION
ST I ON
S T
O N
6
Mergesort – przykład
łaczenie
AEEINOQSSTUY
EINOST
AEQSUY
QUY
AES
A E S
EST
INO
C1
Q U Y
C2
E
AS
A S
Y
QU
Q U
E
ST I NO
S T
O N
C3
7
Mergesort - pseudokod
MERGE-SORT(A, p, r)
1 if p < r
2
then q ← ⌊(p + r)/2⌋
3
MERGE-SORT(A, p, q)
4
MERGE-SORT(A, q + 1, r)
5
MERGE(A, p, q, r)
8
Mergesort - pseudokod
MERGE(A, p, q, r)
1 n1 ← q - p + 1
2 n2 ← r - q
3 create arrays L[1 ..n1 + 1] and R[1 ..n2 + 1]
4 for i ← 1 to n1
5 do L[i] ← A[p + i - 1]
6 for j ← 1 to n2
7 do R[j] ← A[q + j]
8 L[n1 + 1] ← ∞
9 R[n2 + 1] ← ∞
10 i ← 1
11 j ← 1
12 for k ← p to r
13 do if L[i] ≤ R[j]
14
then A[k] ← L[i]
15
i←i+1
16
else A[k] ← R[j]
17
j←j+1
9
Sortowanie szybkie (Quick Sort) - pomysł
Ø  Jest to najszybszy w praktyce algorytm sortowania, pozwala na
efektywne implementacje.
–  średnio: O(NlogN)
–  najgorzej O(N2), przypadek bardzo mało prawdopodobny.
Ø  Procedura:
–  Wybieramy element osiowy (pivot ).
–  Dzielimy ciąg na dwa podciągi: elementów mniejszych lub równych
od osiowego oraz elementów większych od osiowego. Powtarzamy
takie postępowanie, aż osiągniemy ciąg o długości 1.
–  Algorytm typu – „dziel i zwyciężaj”.
–  Jest to metoda sortowania w miejscu (podobnie jak Insert-sort,
przeciwnie do np. Merge-sort), czyli nie wymaga dodatkowej
pamięci
10
Quicksort – algorytm
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r)
3 QUICKSORT(A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
Problemy:
1.  Wybór elementu osiowego;
2.  Podział (partition).
11
Quicksort – podział
Ø  Funkcja partition dzieli ciąg na dwa podciągi: elementów
mniejszych (bądź równych) od osiowego i większych od niego
Po podziale:
{a[j] | a[j] <= a[i]
dla j ∈[left, i-1]}
wynik
quicksort(a, left, i-1)
El. osiowy
a[i] {a[k] | a[k] > a[i]
dla k ∈[i+1,right]}
wynik
quicksort(a, i+1, right)
12
Quicksort – przykład podziału
ciąg: EASYQUESTION (12 znaków).
Przeglądaj aż: a[j] <= a[right]
Przeglądaj aż: a[i] > a[right]
EASYQUESTION
j
i
EAIYQUESTSON
j
i
EAIEQUYSTSON
j
El. osiowy: N
i
Swap(a[i], a[j])
Swap(a[i], a[j])
(indeksy i oraz j „minęły” się)
Swap(a[i], a[right])
EAIENUYSTSOQ
Lewy podciąg Prawy podciąg
13
Quicksort – wybór elementu osiowego
Ø  opcja 1: zawsze wybierać skrajny element (pierwszy lub ostatni).
–  Zalety: szybkość;
–  Wady: jeśli trafimy na najmniejszy (największy) element podział nie
redukuje istotnie problemu.
Ø  opcja 2: wybieramy losowo.
–  Zalety: średnio powinno działać dobrze (podział na podciągi o
zbliżonej długości);
–  Wady: czasochłonne i nie gwarantuje sukcesu.
Ø  opcja 3: wybieramy medianę z pierwszych/ostatnich/środkowych 3/5/7
elementów.
–  gwarantuje, że nie będzie zdegenerowanych podciągów (pustych).
–  kompromis pomiędzy opcją 1 i 2
14
Podział – pseudokod (opcja 1)
Partition(A, Left, Right)
1. 
Pivot ß A[Right]
2. 
i ß Left – 1
3. 
for j ß Left to Right–1
4. 
do if (A[j] ≤ Pivot)
5. 
then i ß i + 1
6. 
Exchange(A[i], A[j])
7. 
Exchange (A[i+1], A[Right])
8. 
return i +1
15
Randomizowany Quicksort (opcja 2)
Ø  Zakładamy że nie ma powtórzeń
Ø  Jako element osiowy wybieramy losowy element ciągu (opcja 2)
Ø  Powtarzamy procedurę, wszystkie podziały są równie prawdopodobne
(1:n-1, 2:n-2, ..., n-1:1), z prawdopodobieństwem 1/n
Ø  Randomizacja jest drogą do unikania najgorszego przypadku
16
Randomizowany Quicksort
Randomized-Partition(A,p,r)
01 i←Random(p,r)
02 exchange A[r] ↔A[i]
03 return Partition(A,p,r)
Randomized-Quicksort(A,p,r)
01 if p<r then
02
q←Randomized-Partition(A,p,r)
03
Randomized-Quicksort(A,p,q)
04
Randomized-Quicksort(A,q+1,r)
17
Quicksort – czas działania
Ø  Najgorszy przypadek: O(N2)
–  Podciągi zawsze mają długości 0 i N-1 (el. Osiowy jest zawsze
najmniejszy/największy). Np. dla posortowanego ciągu i pierwszej
opcji wyboru el. osiowego.
Ø  Najlepszy przypadek: O(NlogN)
–  Podział jest zawsze najlepszy (N/2). El. osiowy zawsze jest
medianą.
Ø  Średnio: O(NlogN)
18
Quicksort – najlepszy przypadek
Ø  Podciągi otrzymane w wyniku podziału są równe
T (n) = 2T (n / 2) + Θ(n)
19
Quicksort – najgorszy przypadek
20
Quicksort- czas działania
Ø  T(N) = T(i) + T(N-i-1) + N for N > 1
T(0) = T(1) = 1
–  T(i) i T(N-i-1) dla podziału i/N-i-1.
–  N dla podziału 1/N-1(liniowe – przeglądamy wszystkie elementy).
21
Quicksort – czas działania
Ø  najgorzej: T(N) = T(0) + T(N-1) + N = T(N-1) + N
= O(N2)
Ø  najlepiej: T(N) = 2T(N/2) + N = O(NlogN)
Ø  „średnio”:
T(N) = (1/N) ∑i=0 N-1T(i) + (1/N) ∑i=0 N-1T(N-i-1) + N
= (2/N) ∑j=0 N-1T(j) + N = O(NlogN)
22
Quicksort - uwagi
Ø  Małe ciągi
–  Quicksort zachowuje się źle dla krótkich ciągów.
–  Poprawa – jeśli podciąg jest mały zastosować sortowanie przez
wstawianie (zwykle dla ciągów o długości 5 ~ 20)
Ø  Porównanie z mergesort:
–  Oba zbudowane na zasadzie „dziel i zwyciężaj”.
–  Mergesort wykonuje sortowanie w fazie łączenia.
–  Quicksort wykonuje prace w fazie podziału.
23
Heap Sort – pojęcie kopca
Ø  Struktura kopca binarnego
–  Drzewo binarne (bliskie zrównoważenia)
•  Wszystkie poziomy, z wyjątkiem co najwyżej ostatniego, kompletnie
zapełnione
–  Wartość klucza w węźle jest większa lub równa od wartości kluczy
wszystkich dzieci; własność taka jest zachowana dla lewego i prawego
poddrzewa (zawsze)
24
Heap Sort – reprezentacja tablicowa kopca
Parent (i)
return ⎣i/2⎦
Left (i)
return 2i
Right (i)
return 2i+1
Własność kopca:
A[Parent(i)] ≥ A[i]
1
2
3
4
5
6
7
8
9
10
16
15
10
8
7
9
3
2
4
1
poziomy: 3
2
1
0
25
Heap Sort – reprezentacja kopca w tablicy
Ø  Zauważmy połączenia w drzewie – dzieci węzła i występują na pozycjach
2i oraz 2i+1
Ø  Czemu to jest wygodne?
–  Dla reprezentacji binarnej, dzieleniu/mnożeniu przez 2 odpowiada
przesuwanie (szybka operacja)
–  Dodawanie jedynki oznacza zmianę najmłodszego bitu (po przesunięciu)
26
Kopcowanie (Heapify)
Ø  Niech i będzie indeksem w tablicy A
Ø  Niech drzewa binarne Left(i) i Right(i) będą kopcami
Ø  Ale, A[i] może być mniejsze od swoich dzieci – co powoduje złamanie
własności kopca
Ø  Metoda Kopcowania (Heapify) przywraca własności kopca dla A poprzez
przesuwanie A[i] w dół kopca aż do momentu, kiedy własność kopca jest
już spełniona
27
Kopcowanie (Heapify)
28
Kopcowanie (Heapify) – przykład
29
Kopcowanie – czas działania
Ø  Czas działania procedury Heapify dla poddrzewa o n węzłach i korzeniu
w i:
–  Ustalenie relacji pomiędzy elementami: Θ(1)
–  dodajemy czas działania Heapify dla poddrzewa o korzeniu w jednym z
potomków i, gdzie rozmiar tego poddrzewa 2n/3 jest najgorszym
przypadkiem.
T (n) ≤ T (2n / 3) + Θ(1) ⇒ T (n) = O(log n)
–  Inaczej mówiąc
•  Czas działania dla drzewa o wysokości h: O(h)
30
Budowa kopca
Ø  Konwertujemy tablicę A[1...n], gdzie n = length[A], na kopiec
Ø  Zauważmy, że elementy w A[(⎣n/2⎦ + 1)...n] są już zbiorem kopców jednoelementowych!
31
Budowanie kopca – 1
32
Budowanie kopca – 2
33
Budowanie kopca – 3
34
Budowa kopca – analiza
Ø  Poprawność: indukcja po i, (wszystkie drzewa o korzeniach m > i są
kopcami)
Ø  Czas działania: n wywołań kopcowania (Heapify) = n O(lg n) = O(n lg n)
Ø  Wystarczająco dobre ograniczenie – O(n lg n) dla zadania sortowanie
(Heapsort), ale czasem kopiec budujemy dla innych celów
35
Sortowanie za pomocą kopca – Heap Sort
O(n)
Ø  Czas działania O(n lg n) + czas budowy kopca (O(n))
36
Heap Sort – 1
37
Heap Sort – 2
38
Heap Sort – podsumowanie
Ø  Heap sort wykorzystuje strukturę kopca przez co dostajemy
asymptotycznie optymalny czas sortowania
Ø  Czas działania O(n log n) – podobnie do merge sort, i lepiej niż wybór,
wstawianie czy bąbelkowe
Ø  Sortowanie w miejscu – podobnie do sortowania przez wybór, wstawianie
czy bąbelkowego
39