Złożoność obliczeniowa - Letnie Warsztaty Matematyczno

Transkrypt

Złożoność obliczeniowa - Letnie Warsztaty Matematyczno
Złożoność obliczeniowa
Rozwiązywanie zależności rekurencyjnych
Złożoność obliczeniową możemy szacować intuicyjnie.
Możemy też wyprowadzić ją analitycznie, rozwiązując zależności
rekurencyjne.
Na przykład, na koszt zsumowania n liczb składa się stały koszt
dodania jednej liczby do sumy plus koszt zsumowania n − 1 liczb.
Możemy to zapisać jako:
TSum (n) ∈ O(1) + TSum (n − 1)
Złożoność obliczeniowa
Rozwiązywanie zależności rekurencyjnych
Teraz chcemy pozbyć się T z prawej strony. Rozwijamy więc
według tego samego wzoru:
TSum (n) ∈ O(1) + TSum (n − 1)
= O(1) + O(1) + TSum (n − 1)
= 2O(1) + TSum (n − 1)
···
= (k + 1)O(1) + TSum (n − k)
= (n + 1)O(1) + TSum (n − n)
= (n + 1)O(1) + TSum (0)
Złożoność obliczeniowa
Rozwiązywanie zależności rekurencyjnych
Przyjmujemy, że T (k) ∈ O(1), jeżeli k jest stałą.
Dodatkowo O(f ) · O(g ) = O(fg ).
Stąd:
TSum (n) ∈ (n + 1)O(1) + TSum (0)
= (n + 1)O(1) + O(1)
= (n + 2)O(1)
= O(n) · O(1) = O(n)
Otrzymaliśmy złożoność liniową, tak jak wcześniej.
Złożoność obliczeniowa
Zadania
Trochę matematyki.
5. Udowodnij, że jeżeli f należy do O(k · g (n)) wtedy i tylko
wtedy, gdy f należy do O(g (n)).
6. Udowodnij, że dwa logarytmy o różnych podstawach, ale tej
samej liczbie logarytmowanej, różnią się zawsze tylko o stałą.
7. Udowodnij, że dwie funkcje wykładnicze o różnych
podstawach, ale tym samym wykładniku, różnią się zawsze
tylko o stałą.
8. Rozwiąż zależność rekurencyjną T (n) ∈ O(n) + T (n − 1).
Letnie Warsztaty Matematyczno-Informatyczne
Algorytmy i struktury danych
Algorytmy sortowania
Algorytmy sortowania
Problem sortowania
Zanim przejdziemy do omawiania algorytmów rozwiązujących
problem sortowania, zdefiniujmy najpierw problem sortowania.
Sortowanie polega zamianie kolejności elementów ciągu w taki
sposób, aby otrzymać ciąg niemalejący.
Innymi słowy: chcemy uporządkować dany zestaw liczb od
najmniejszej do największej.
Algorytmy sortowania
Problem sortowania
Proste, prawda?
Znów, problem polega na tym, że komputer nie potrafi spojrzeć na
wszystkie liczby na raz. Ty też nie potrafisz, jeżeli jest ich kilka
milionów.
Algorytmy sortowania
Sortowanie przez wybieranie
Pomysł: wybierz minimum, przenieś je na początek, powtarzaj aż
otrzymasz uporządkowany ciąg.
Algorytm ten nazywamy sortowaniem przez wybieranie (ang.
selection sort).
3 9 5 1 2 7 6 8
Algorytmy sortowania
Sortowanie przez wybieranie
W pseudokodzie:
procedure Selection-Sort(T )
for all i ∈ 0..Length(T ) do
m ← Minimum-Index(T [i..Length(T )])
Swap(T [i], T [m + i])
end for
return T
end procedure
Teraz pozostaje jedynie zdefiniować procedurę Minimum-Index,
która zwracać będzie indeks minimum w tablicy.
3 9 5 7 8 2 1 6 4
1 9 5 7 8 2 3 6 4
1 2 5 7 8 9 3 6 4
Algorytmy sortowania
Sortowanie przez wybieranie
Wygląda ona tak:
procedure Minimum-Index(T )
minIndex ← 0
for all i ∈ 1..Length(T ) do
if T [i] < T [minIndex] then
minIndex ← i
end if
end for
return minIndex
end procedure
Algorytmy sortowania
Sortowanie przez wybieranie
Jaka jest złożoność obliczeniowa tego algorytmu?
Spójrzmy najpierw na Minimum-Index, którego czas wykonania
zależy liniowo od długości tablicy. To daje nam O(|T |).
Następnie Selection-Sort wywołuje powyższą procedurę |T |
razy.
W sumie otrzymujemy O(|T |2 ), czyli czas kwadratowy.
Algorytmy sortowania
Inne algorytmy sortowania w czasie kwadratowym
Sortowanie przez wstawianie (ang. insertion sort) — zacznij od
pustego ciągu. Wstawiaj do niego kolejne elementy ciągu do
posortowania w odpowiednie miejsca.
Sortowanie bąbelkowe (ang. bubble sort) — przejdź przez ciąg,
porównując każde dwa sąsiadujące elementy. Zamień każdą parę,
która jest w złej kolejności. Powtarzaj aż otrzymasz posortowany
ciąg.
Algorytmy sortowania
Sortowanie przez scalanie
Rozwiązania wielu problemów algorytmicznych dzielą się na dwie
grupy:
Łatwe do zrozumienia, ale powolne (sortowanie przez wybieranie).
Szybkie, ale trudniejsze do zrozumienia (sortowanie przez scalanie).
Mimo wszystko, spróbujmy zrozumieć to drugie.
Algorytmy sortowania
Sortowanie przez scalanie
Zacznijmy od scalania — mając dane dwa posortowane ciągi, scal
je w jeden posortowany ciąg.
Scalanie możemy wykonać w czasie liniowym.
Najmniejszy element obu ciągów znajduje się z przodu jednego lub
drugiego z nich.
Wybieramy mniejszy z początkowych elementów i umieszczamy go
na końcu scalonego ciągu.
1 3 4 8 9
2 5 6 7
Algorytmy sortowania
Sortowanie przez scalanie
procedure Merge(A, B)
M ← Zeros(Length(A) + Length(B))
a ← 0, b ← 0, m ← 0
while a < Length(A) and b < Length(B) do
if A[a] < B[b] then M[m] ← A[a], a ← a + 1
else M[m] ← B[b], b ← b + 1
end if
m ←m+1
end while
while a < Length(A) do
M[m] ← A[a], m ← m + 1, a ← a + 1
end while
while b < Length(B) do
M[m] ← B[b], m ← m + 1, b ← b + 1
end while
return M
end procedure
Algorytmy sortowania
Sortowanie przez scalanie
Teraz cały algorytm sortowania przez scalanie możemy zapisać
jako:
1. Podziel ciąg na dwie części
2. Posortuj obie części
3. Scal je
Tylko. . . Jak posortować dwa podciągi?
Algorytmy sortowania
Sortowanie przez scalanie
Teraz cały algorytm sortowania przez scalanie możemy zapisać
jako:
1. Podziel ciąg na dwie części
2. Posortuj obie części
3. Scal je
Tylko. . . Jak posortować dwa podciągi?
Sortowaniem przez scalanie!
Algorytmy sortowania
Sortowanie przez scalanie
procedure Merge-Sort(T )
if Length(T ) � 1 then
� Jeżeli ciąg ma 1 element,
return T
� to jest już posortowany!
end if
m ← �Length(T )/2�
� Dzielimy na pół.
L ← Merge-Sort(T [0..m])
� Sortujemy
R ← Merge-Sort(T [m..Length(T )])
� obie strony.
return Merge(L, R)
� Scalamy.
end procedure
2 6 4 7 8 9 1 5
Algorytmy sortowania
Sortowanie przez scalanie
Jaka jest złożoność obliczeniowa sortowania przez scalanie?
Spróbujmy rozwiązać zależność rekurencyjną:
T (n) = O(n) + 2T (n/2)
Dla ułatwienia obliczeń podstawimy n = 2x , otrzymując:
T (2x ) = O(2x ) + 2T (2x−1 )
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
...
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
...
= kO(2x ) + 2k (2x−k )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
...
= kO(2x ) + 2k (2x−k )
= xO(2x ) + 2x · 20
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
...
= kO(2x ) + 2k (2x−k )
= xO(2x ) + 2x · 20
= O(x2x ) + O(2x )
�
Algorytmy sortowania
Sortowanie przez scalanie
T (2x ) = O(2x ) + 2T (2x−1 )
�
= O(2x ) + 2 O(2x−1 ) + 4T (2x−2 )
= O(2x ) + O(2x ) + 4T (2x−2 )
= 2O(2x ) + 22 (2x−2 )
= 3O(2x ) + 23 (2x−3 )
...
= kO(2x ) + 2k (2x−k )
= xO(2x ) + 2x · 20
= O(x2x ) + O(2x )
= O(x2x ) = O(n log n)
�
Algorytmy sortowania
Sortowanie przez scalanie
Intuicyjnie złożoność sortowania przez scalanie zrozumieć można
tak:
Każdy poziom sortowania wprowadza nowy podział. Na poziomie 0
mamy 1 ciąg, na poziomie 1 mamy 2 ciągi, potem 4 ciągi, 8
ciągów itd.
Scalenie każdego poziomu zajmuje O(n) czasu (przechodzimy raz
po wszystkich elementach), a poziomów mamy O(log n) (bo za
każdym razem dzielimy na dwa).
Stąd sumaryczna złożoność wynosi O(n log n).
Letnie Warsztaty Matematyczno-Informatyczne
Algorytmy i struktury danych
Sortowanie szybkie
Sortowanie szybkie
Istota algorytmu
Sortowanie szybkie (ang. quick sort przypomina nieco sortowanie
przez scalanie).
Oba działają na zasadzie: dzielimy ciąg na dwie części, sortujemy
obie części i łączymy je z powrotem w jeden ciąg.
Sortowanie przez scalanie wykonuje właściwe sortowanie na etapie
scalania.
Sortowanie szybkie wykonuje właściwe sortowanie na etapie
dzielenia.
Sortowanie szybkie
Istota algorytmu
Ogólny zarys algorytmu wygląda tak:
1. Wybierz dowolny element ciągu (piwot).
2. Podziel ciąg na dwie części: jedna z elementami mniejszymi
od piwotu, druga z pozostałymi.
3. Posortuj rekurencyjnie obie części.
Punkt podziału może wypadać w różnych miejscach!
Sortowanie szybkie
Pseudokod
procedure Quick-Sort(T )
if Length(T ) � 1 then
return T
end if
m ← Partition(T )
L ← Quick-Sort(T [0..m])
R ← Quick-Sort(T [m + 1..Length(T )])
return Concatenate(L, [T [m]], R)
end procedure
Zauważ:
1. Partition wykonuje podział i zwraca indeks piwotu po
podziale.
2. Przy sortowaniu obu części pomijamy piwot!
3. Concatenate łączy listy w jedną (skleja je).
5 2 8 9 1 4 6 7 3
5 2 3 9 1 4 6 7 8
5 2 3 4 1 9 6 7 8
1 2 3 4 5 9 6 7 8
Sortowanie szybkie
Pseudokod
procedure Partition(T )
pivot ← T [0]
p ← 0, q ← Length(T ) − 1
while p < q do
while T [p] < pivot do
p ←p+1
end while
while T [q] � pivot do
q ←q−1
end while
if p < q then
Swap(T [p], T [q])
end if
end while
Swap(T [0], T [q])
return q
end procedure
� Szukamy T [p] < pivot
� Szukamy T [q] � pivot
� Zamieniamy miejscami.
� Umieszczamy piwot w środku.
Sortowanie szybkie
Złożoność obliczeniowa
Podobnie jak sortowanie przez scalanie, sortowanie szybkie ma
złożoność obliczeniową O(n log n).
Ale tylko przy założeniu, że podział jest zawsze mniej-więcej w
połowie!
Co jeżeli piwot za każdym razem będzie najmniejszym lub
największym elementem listy? Wtedy dostaniemy O(n 2 ).
Sortowanie szybkie
Złożoność obliczeniowa
Sortowanie szybkie jest przykładem algorytmu, którego średnia
złożoność obliczeniowa (czyli taka, która występuje zazwyczaj),
jest różna od złożoności pesymistycznej (czyli najgorszej możliwej).
Dzieje się tak dlatego, ponieważ złożoność obliczeniowa sortowania
szybkiego zależy nie tylko od wielkości danych, ale i ich rozkładu.
Sortowanie szybkie
Przyspieszanie sortowania szybkiego
Sortowanie szybkie z reguły jest szybsze od sortowania przez
scalanie.
W przypadku pesymistycznym może być jednak dużo wolniejsze.
Możemy zmniejszyć prawdopodobieństwo wystąpienia przypadku
pesymistycznego.
Sortowanie szybkie
Przyspieszanie sortowania szybkiego
Sposób 1: wybieramy piwot losowo.
Prawdopodobieństwo, że za każdym razem wybierzemy minimum
lub maksimum jest bardzo nikłe.
Dzięki temu prawdopodobieństwo otrzymania pesymistycznego
przypadku też jest nikłe.
Sortowanie szybkie
Przyspieszanie sortowania szybkiego
Sposób 2: wybieramy medianę podciągu.
Znalezienie mediany całego ciągu jest kosztowne i wymaga
podobnej ilości pracy, co posortowanie połowy ciągu.
Możemy jednak wybrać medianę mniejszego podciągu jako piwot,
na przykład pierwszych 20 elementów.
W ten sposób nie możemy nigdy wybrać ekstremum całego ciągu.
W wersji pesymistycznej podział nadal będzie bardzo nierówny,
jednak lepszy niż bez tej optymalizacji.
Sortowanie szybkie
Przyspieszanie sortowania szybkiego
Sposób 3: sprawdzamy ułożenie ciągu.
Możemy spróbować oszacować jak blisko posortowania znajduje się
nasz ciąg liczb w czasie O(n).
Jeżeli ciąg jest blisko posortowania, zamiast sortowania szybkiego
stosujemy sortowanie przez scalanie, sortowanie bąbelkowe lub w
ogóle nie sortujemy, jeżeli ciąg okaże się całkowicie posortowany.
Sortowanie szybkie
Algorytmy hybrydowe
Okazuje się, że sortowanie szybkie (ale także sortowanie przez
scalanie), jest wolniejsze od sortowania przez wstawianie dla bardzo
krótkich ciągów (kilka-kilkanaście elementów).
Możemy sortować algorytmem O(n log n) do pewnego momentu, a
dalej stosować sortowanie przez wstawianie.