Złożoność obliczeniowa

Transkrypt

Złożoność obliczeniowa
ZŁOŻONOŚĆ OBLICZENIOWA
Wyznaczanie złożoności obliczeniowej dokładnej i asymptotycznej
Złożoność obliczeniowa algorytmów
Chcemy podać miarodajną ocenę efektywności algorytmu, abstrahując od komputera, techniki
(języka) programowania, szczegółów technicznych implementacji. W tym celu dążymy do
podania zależności funkcyjnej wiążącej złożoność (pracochłonność) algorytmu z rozmiarem
problemu. Dlatego definiujemy następujące pojęcia:

Operacja podstawowa – pozwala ocenić złożoność czasową algorytmu, abstrahując od
komputera i języka programowania (czas wykonania nie jest miarodajny). Na ogół jest to
operacja wykonywana najczęściej (najbardziej zagnieżdżona w strukturze algorytmu).
Złożoność obliczeniową algorytmu wiążemy ze złożonością jego najbardziej
czasochłonnego fragmentu.

Rozmiar danych wejściowych n

Krotność wykonania operacji podstawowej Z = f(n)
Rozsądny i nierozsądny czas działania
W analizie złożoności algorytmów mamy do czynienia z dwoma kategoriami funkcji
wiążących rozmiar danych wejściowych z czasem wykonania algorytmu.

funkcje rozsądne (wielomianowe) – problemy algorytmiczne łatwo rozwiązywalne:
log(n), n, nlog(n), n2,n3, nk

funkcje nierozsądne (niewielomianowe, wykładnicze i ponadwykładnicze) – problemy
algorytmiczne trudno rozwiązywalne:
nlog(n), 2n, 3n, kn, n!, nn
Mówimy tak mimo oczywistej względności tych problemów.
Np. „rozsądny” algorytm nk, k=20 ma gorszą złożoność obliczeniową niż „nierozsądny”
algorytm kn, k=1,05 dla rozmiaru problemu n<3324. Jednakże zawsze znajdziemy taką
wartość graniczną rozmiaru problemu obliczeniowego, dla której algorytm trudno
rozwiązywalny będzie rozwiązywany dłużej niż algorytm łatwo rozwiązywalny.
Problem ułożenia 4 puzzli (2x2):
(14)(24)(34)(44) = 4!44 = 6144 warianty
W najgorszym przypadku dla n puzzli jest do sprawdzenia n!4n przypadków (n – ilość puzzli)
Problem ułożenia 9 puzzli (3x3):
(14)(24)(34) ... (84) (94) = 9!49 = 95 miliardów wariantów
Gdyby puzzli było 25 (tylko 5x5) to do sprawdzenia będzie już:
(14)(24)(34) ... (244)(254) = 25!425 = liczba 41-cyfrowa
Rys. 1. Porównanie wartości osiąganych przez funkcje wielomianowe i wykładnicze
Rys. 2. Porównanie wartości osiąganych przez funkcje wielomianowe i wykładnicze
Rzędy wielkości funkcji, rzędy złożoności obliczeniowej,
symbole oszacowań asymptotycznych (notacja Landaua)
Poniższe notacje opisują asymptotyczne zachowanie funkcji.
a) Notacja duże O
Określa ograniczenie funkcji od góry.
Funkcja f(n) jest rzędu g(n), gdy istnieje dodatnia wartość stałej c dla której począwszy od
pewnej wartości n0 wartości funkcji f(n)  cg(n). Zapisujemy to jako f(n)=O(g(n))
(można także używać zapisu f(n)O(g(n)))
Rys. 3. Ograniczenie górne funkcji - notacja O(n)
np:
log(n) = O(n log(n))
n3+5n+12 = O(n3)
Zależność pomiędzy funkcjami f i g można wyznaczyć obliczając granicę ich ilorazu:
f(n) = O(g(n))
gdy
f ( n)
c0
n g ( n)
lim
Notacja małe o
Określa ograniczenie funkcji od góry.
Funkcja f(n) jest rzędu g(n), gdy istnieje dodatnia wartość stałej c dla której począwszy od
pewnej wartości n0 wartości funkcji f(n) < cg(n). Zapisujemy to jako f(n)=o(g(n))
np:
n = o(n)
1/n = o(1)
Zależność pomiędzy funkcjami f i g można wyznaczyć obliczając granicę ich ilorazu:
f(n) = o(g(n))
gdy
lim
n
f ( n)
0
g ( n)
b) Notacja 
Określa ograniczenie funkcji od dołu.
Funkcja f(n) jest rzędu g(n), gdy istnieje dodatnia wartość stałej c dla której począwszy od
pewnej wartości n0 wartości funkcji f(n)  cg(n). Zapisujemy to jako f(n)=(g(n))
Rys. 4. Ograniczenie dolne funkcji - notacja 
np:
n3 =  (n2)
2n2+1 =  (n2)
Zależność pomiędzy funkcjami f i g można wyznaczyć obliczając granicę ich ilorazu:
f(n) =  (g(n))
gdy
f ( n)
f ( n)
c0
  lub lim
n g ( n)
n g ( n)
lim
Notacja mała 
Określa ograniczenie funkcji od dołu.
Funkcja f(n) jest rzędu g(n), gdy istnieje dodatnia wartość stałej c dla której począwszy od
pewnej wartości n0 wartości funkcji f(n) > cg(n). Zapisujemy to jako f(n)=(g(n))
np:
3n =  (2n)
n2 =  (n ln(n))
Zależność pomiędzy funkcjami f i g można wyznaczyć obliczając granicę ich ilorazu:
f(n) =  (g(n))
gdy
lim
n
f ( n)

g ( n)
c) Notacja 
Określa ograniczenie funkcji zarówno od dołu jak i od góry.
Funkcja f(n) jest rzędu g(n), gdy istnieją takie dodatnie wartości stałych c1 i c2 dla których
począwszy od pewnej wartości n0 wartości funkcji f(n)  c1g(n) i zarazem f(n)  c2g(n).
Zapisujemy to jako f(n)= (g(n))
Rys. 5. Ograniczenie górne i dolne funkcji - notacja 
np:
3n3 + 5n =  (n3)
(1+1/n)n =  (1)
Zależność pomiędzy funkcjami f i g można wyznaczyć obliczając granicę ich ilorazu:
f(n) =  (g(n))
gdy
f ( n)
c0
n g ( n)
lim
Rząd złożoności obliczeniowej jest najważniejszym czynnikiem określającym przydatność
algorytmu.
Podsumowując:
dla
f(n) = O(g(n))

dla
f(n) = o(g(n))

dla
f(n) =  (g(n))

dla
f(n) =  (g(n))

dla
f(n) =  (g(n))

f ( n)

n g (n)
f ( n)
lim
0
n   g ( n)
f ( n)
lim
0
n   g ( n)
f ( n)
lim

n   g ( n)
f ( n)
0  lim

n   g ( n)
lim
Wzajemne zależności
notacji oszacowań asymptotycznych

 


Tab. 1. Klasy funkcji opisujących złożoność obliczeniową algorytmów
Klasa funkcji
subliniowa
wielomianowa
f(n)=(nc)
c>0
niewielomianowa
Przykładowe funkcje
Typ funkcji
stała
f(n)=O(1)
polilogarytmiczna
f(n)=(1) i f(n)=(logcn),
liniowa
f(n)=(n)
quasi-liniowa
f(n)=(n) i f(n)=O(n logn)
kwadratowa
f(n)= (n2)
superwielomianowa
f(n)=(nc) i f(n)=o(dn),
wykładnicza
f(n)=(en) i f(n)=O(fn)
superwykładnicza
f(n)=(cn)
sin(n), 1/n
c>0
log(log(n)), log2(n)
n, 3n(1+1/n)n
n log(n), n log(log(n))
n2, n(n-1)+3
c>0, d>1
nlog(n), en
e>1, f>1
2n, n2 3n
c>1
2n n, n!, nn
Funkcje polilogarytmiczne spotyka się przy analizie algorytmów równoległych.
Funkcje quasi-liniowe i kwadratowe określają złożoność obliczeniową algorytmów
sortujących.
Funkcje niewielomianowe spotyka się przy rozwiązywaniu trudnych problemów
kombinatorycznych. Algorytmy niewielomianowe są użyteczne tylko dla bardzo małych
wartości n
Właściwości relacji asymptotycznych
Jeżeli
f(n)=O(g(n)) i g(n)= (h(n))
to
f(n)=O(h(n))
Jeżeli
f(n)= (p(n)) i g(n)= (q(n))
to
f(n) g(n)= (p(n) q(n))
Jeżeli
f(n)=O(g(n))
to
f(n)+g(n)= (g(n))
Jeżeli
f(n)=O(g(n)) i g(n)= (h(n))
to
f(n)+g(n)= (h(n))
Kategorie złożoności
Kategorie złożoności opisuje się notacją . Dowolna funkcja należąca do pewnej kategorii
funkcji może ją (kategorię) reprezentować. Na ogół kategorię funkcji reprezentuje najprostsza
funkcja należąca do niej. Dlatego kategorię funkcji liniowych reprezentuje funkcja (n).
Właściwości rzędów wielkości funkcji







g(n) = (f(n))
g(n) = (f(n))
dowolna funkcja należąca do pewnej kategorii funkcji może ją
reprezentować
Jeżeli a>1 i b>1 
loga(n) = (logb(n) i kategorię tę możemy reprezentować przez
funkcję (log(n))
wszystkie funkcje logarytmiczne należą do tej samej kategorii
złożoności
Jeżeli a>1 i b>1 
an = o(bn)
nie wszystkie funkcje wykładnicze należą do tej samej kategorii
złożoności
Dla każdego a>0 
an = o(n!)
funkcja n! jest gorsza obliczeniowo od dowolnej funkcji
wykładniczej
Jeżeli c0 i d>0 oraz f(n) = O(g(n)) i h(n) =  (g(n))

cf(n) + dh(n) =  (g(n))
Dla podstawowych kategorii złożoności:
(log(n)), (n), (nlog(n)), (n2), (na), (nb), (cn), (dn), (n!), (nn)
b>a>2 i d>c>1
funkcja leżąca na prawo jest ograniczeniem górnym kategorii sąsiedniej z lewej strony
fl(n) = o(gp(n))
f(n) = O(g(n))
f(n) = (g(n))


Wykorzystanie granic do określenia rzędu funkcji


 c to f (n)   ( g (n)), dla c  0
f ( n) 
lim
  0 to f (n)  o( g (n))
n   g ( n)

 to f (n)   ( g (n))
reguła L’Hospitala
Jeżeli funkcje f(n) i g(n) są różniczkowalne i ich pochodne wynoszą f’(n) i g’(n) oraz
jeżeli
lim f (n)  lim g (n)  
n
n
to
lim
n
f ( n)
f ' ( n)
 lim
n

g ( n)
g ' ( n)
Przykład:
f(n)= n3 ln(n)=O(n2) ?
Czy


 
3
lim
n 
n ln (n)
ln (n)
1/n
2 n
2
 lim
lim
 lim
 lim
0

2
n 
n  n
n
n H n  1 /( 2 n ) n  n
TAK
Przykłady
„Przyspieszanie” algorytmu badającego planarność grafu
Poniższa tabela prezentuje efekty polepszania złożoności algorytmu, a co za tym idzie zmiany
klasy funkcji opisującej jego złożoność.
Tab. 2. Przykład postępu jaki dokonał się w dziedzinie projektowania algorytmów badania
planarności grafu
Rok
opracowania
algorytmu
Złożoność
algorytmu
Czas obliczeń dla
grafu o n=100
wierzchołkach
Rozmiar analizowanego grafu
możliwy do weryfikacji w
czasie 1 minuty
1930
O(n6)
325 lat
4
1963
O(n3)
2 godz. 48 min.
18
1967
O(n2)
100 s
77
1971
O(n log(n))
7s
643
1974
O(n)
1s
6000
Zwiększanie mocy komputerów a złożoność obliczeniowa
Tab. 3. Wpływ poprawy szybkości komputerów na zwiększenie rozmiarów problemów
algorytmicznych możliwych do rozwiązania w zadanym czasie
Moc
obliczeniowa
komputera
1 sekunda
1godzina
1 rok
złożoność obliczeniowa
n2
2n
n!
n2
2n
n!
n2
2n
n!
1Gflops
(109), 1985
ns=3,1104
ns=29
ns=12
ng=1,8106
ng=41
ng=15
nr=1,7108
nr=54
nr=18
1 Tflops
(1012), 1997
32ns
1,33ns
1,17ns
32ng
1,24ng
1,13ng
32nr
1,18nr
1,11nr
1000ns
1,67ns
1,42ns
1000ng
1,48ng
1,33ng
1000nr
1,36nr
1,28nr
1 Pflops
(1015), 2008
wg listy TOP 500 (500 najszybszych superkomputerów)
„MilkyWay 2” – najszybszy superkomputer na świecie w czerwcu 2014. Składa się z 48 000 koprocesorów Intel Xeon Phi
oraz 32 000 procesorów Intel Xeon i działa ze szczytową wydajnośc3ią 54,9 petaflopsów (54,9 biliarda
operacji zmiennoprzecinkowych na sekundę)
„Sunway” –
najszybszy superkomputer na świecie w czerwcu 2016. Działa ze szczytową wydajnością 125,4
petaflopsów (125,4 biliarda operacji zmiennoprzecinkowych na sekundę)
Moc obliczeniowa komputera a typ funkcji złożoności algorytmu
Tab. 4. Porównanie czasów obliczeń algorytmów: wielomianowego i liniowego dla tego
samego problemu wykonywanych na superkomputerze i komputerze domowym
złożoność obliczeniowa algorytmu
rozmiar problemu n
algorytm 1
wielomianowy: 3n3 ns
(3 ns dla problemu
o rozmiarze n=1)
algorytm 2
liniowy: 107 n ns
(10 ms dla problemu
o rozmiarze n=1)
czas obliczeń
superkomputer
komputer PC
10
3 s
0,1 s
100
3 ms
1s
1000
3s
10 s
104
50 min.
1 min. 40 s
106
95 lat
2 godz. 47 min.
Złożoność czasowa algorytmów

operacja podstawowa – pozwala ocenić złożoność czasową algorytmu, abstrahując od
komputera i języka programowania (czas wykonania nie jest miarodajny). Na ogół jest to
operacja wykonywana najczęściej.

rozmiar danych wejściowych

pesymistyczna złożoność obliczeniowa – złożoność obliczeniowa dla najgorszego
przypadku wystąpienia danych wejściowych
W(n) = max(t(I))
IDn (I jest elementem zbioru danych o rozmiarze n)
t(I) jest liczbą operacji wykonywanych przez algorytm na danych I

optymistyczna złożoność obliczeniowa – złożoność obliczeniowa dla najlepszego
przypadku wystąpienia danych wejściowych
w(n) = min(t(I))

oczekiwana złożoność obliczeniowa – bierzemy pod uwagę prawdopodobieństwo
występowania różnych przypadków danych wejściowych. Często najgorszy przypadek
pojawia się bardzo rzadko. Dlatego istotniejsze jest rozważanie przypadku średniego.
A(n) =  p( I )t ( I )
I Dn
p(I) jest prawdopodobieństwem występowania danych I
Gdy W(n)=A(n) to algorytm jest niewrażliwy czasowo, a jego złożoność czasowa zależy
tylko od rozmiaru danych.
Przykład:
Wyszukiwanie elementu w tablicy
– operacja podstawowa: porównanie elementu z kolejną wartością w tablicy
– rozmiar danych wejściowych: n
– pesymistyczna złożoność obliczeniowa:
W(n) = max (i) = n = (n) (ostatni element)
i1..n
– optymistyczna złożoność obliczeniowa:
w(n) = min (i ) = 1 = (1) (pierwszy element)
i1..n
– oczekiwana złożoność obliczeniowa:
1 n(n  1)
n 1
1 n
=
= (n)
i =

n 2
2
n i 1
i 1
i 1
(średnio połowa listy będzie przejrzana)
n
A(n) =  p(I )t ( I ) =
n
1
ni
=
Wrażliwość algorytmów

Określa na ile funkcje W(n) i A(n) są reprezentatywne dla wszystkich danych
wejściowych.

wrażliwość pesymistyczna (najgorszego przypadku):
(n) = max(t(I1)-t(I2))
I1,I2Dn

wrażliwość oczekiwana (średniego przypadku):
(n) =
w(n) 
 t ( I )  A(n)
2
p( I )
IDn
(n) jest odchyleniem standardowym zmiennej losowej
w(n) jest wariancją zmiennej losowej
n
A(n) =  p(I )t ( I ) jest oczekiwaną złożonością obliczeniową
i 1
Im większe wartości (n) i (n), tym dany algorytm jest bardziej wrażliwy na dane
wejściowe.
Przykład – c.d.:
Wyszukiwanie elementu w tablicy – c.d.
– wrażliwość pesymistyczna:
(n) = max(t(I1)-t(I2)) = t(n)-t(1) = n-1
(w najgorszym przypadku element jest na ostatniej pozycji na liście, w najlepszym
przypadku jest na pierwszej pozycji na liście)
– wrażliwość oczekiwana:
(n) =
 t ( I )  A(n)
2
 n 1 1
=
i 


2  n
i 1 
n
p( I ) =
I Dn
2
n2 1

12
n
 0,29n
12
algorytm jest wrażliwy na dane wejściowe
Złożoność pamięciowa algorytmów
Przez złożoność pamięciową definiujemy liczbę komórek pamięci niezbędną do realizacji
algorytmu.
Dla większości problemów algorytmicznych złożoność pamięciowa ma mniejsze
znaczenie niż złożoność czasowa.
Pamięć wykorzystywaną przez algorytm dzielimy na pamięć zawierającą dane wejściowe
i pamięć dodatkową (pomocniczą) wykorzystywaną przez algorytm do organizacji
obliczeń. Jeżeli pamięć dodatkowa nie zależy od rozmiaru danych wejściowych to
algorytm taki nazywamy działającym w miejscu (in place).
Dla złożoności pamięciowej także można mówić o:
 pesymistycznej złożoności pamięciowej – złożoności pamięciowej najgorszego
przypadku
 oczekiwanej złożoności pamięciowej – złożoności pamięciowej średniego przypadku
Często dla różnych wersji danego algorytmu złożoności czasowa i pamięciowa są do
siebie odwrotnie proporcjonalne.
Dla wielu problemów algorytmicznych istnieje „równowaga” pomiędzy złożonością
czasową i pamięciową, tzn. że można zmniejszyć złożoność czasową kosztem
zwiększenia zapotrzebowania na pamięć i vice-versa.