Złożoność obliczeniowa

Transkrypt

Złożoność obliczeniowa
Złożoność obliczeniowa algorytmu – ilość zasobów komputera jakiej potrzebuje dany algorytm. Pojęcie to
wprowadzili J. Hartmanis i R. Stearns. Najczęściej przez zasób rozumie się czas oraz pamięć – dlatego też używa się
określeń "złożoność czasowa" i "złożoność pamięciowa".
Za jednostkę złożoności pamięciowej przyjmuje się pojedyncze słowo maszyny (np. Bajt).
W przypadku złożoności czasowej nie można podać bezpośrednio jednostki czasu, np. milisekundy, bowiem nie wiadomo na
jakiej maszynie dany algorytm będzie wykonywany. Dlatego też wyróżnia się, charakterystyczną dla algorytmu, operację
dominującą1. Liczba wykonań tej operacji jest proporcjonalna do wykonań wszystkich operacji.
Rozróżnia się dwa rodzaje złożoności:
złożoność pesymistyczna
określa złożoność w "najgorszym" przypadku, dla najbardziej złośliwych danych.
złożoność oczekiwana
określa złożoność średnią czyli wartość oczekiwaną.
Złożoność obliczeniowa nie jest wygodna w stosowaniu, bowiem operacja dominująca na jednym komputerze może
wykonywać się błyskawicznie, na innym zaś musi być zastąpiona szeregiem instrukcji.
Dlatego też częściej stosuje się złożoność asymptotyczną, która mówi o tym jak złożoność kształtuje się dla bardzo dużych,
granicznych rozmiarów danych wejściowych.
Do opisu złożoności obliczeniowej asymptotycznej stosuje się trzy notacje:
1. Notacja wielkie O (omikron)
2. Notacja Ω(Omega)
3. Notacja Ɵ(Teta)
1
Operacja elementarna (dominująca) – w teorii obliczeń operacja charakterystyczna dla danego algorytmu, na ogół zajmująca w nim najwięcej czasu.
1
O (omikron) – przykłady (dalej będą kolejne ;-) ):
O( n )
Algorytm o liniowej zależności czasu wykonania od ilości danych. Dwukrotny wzrost liczby
przetwarzanych danych powoduje dwukrotny wzrost czasu wykonania.
O( n2 )
Algorytm, w którym czas wykonania rośnie z kwadratem liczby przetwarzanych elementów.
Dwukrotny wzrost liczby danych powoduje czterokrotny wzrost czasu wykonania.
O( n log2n )
Dobre algorytmy sortujące mają taką właśnie złożoność obliczeniową. Czas wykonania przyrasta dużo
wolniej od wzrostu kwadratowego.
O( n! )
O( an )
Bardzo pesymistyczne algorytmy, czas wykonania rośnie bardzo szybko ze wzrostem liczby elementów
wejściowych, czyli znalezienie rozwiązania może zająć najszybszym komputerom całe wieki lub
tysiąclecia. Takich algorytmów należy unikać jak ognia !
np.:



jeżeli mamy jakiś ciąg o długości n, i przechodzimy go dwiema pętlami for(int i=0;i<n;i++) for(int j=0;j<n;j++) to
złożoność wynosi n^2,
jeżeli jedna pętla to złożoność n,
natomiast gdy masz wyszukiwanie binarne ciągu uporządkowanego, czyli przy każdym przejściu odrzucasz
połowę ciągu to złożoność wynosi log(n) przy podstawie z 2
2
Zapis O(...) określamy mianem klasy złożoności obliczeniowej algorytmu. Złożoność obliczeniowa umożliwia
porównywanie wydajności różnych algorytmów sortujących. Z reguły proste algorytmy posiadają wysoką złożoność
obliczeniową - długo dochodzą do wyniku końcowego. Algorytmy bardziej skomplikowane posiadają mniejszą złożoność
obliczeniową - szybko dochodzą do rozwiązania. Złożoność obliczeniowa wszystkich algorytmów sortujących została
dokładnie oszacowana.
Przykład
Załóżmy, iż mamy program sortujący dane zbudowany na bazie algorytmu sortującego o klasie złożoności obliczeniowej
O(n2). Sto elementów jest sortowane w czasie 1 sekundy. Ile czasu zajmie posortowanie za pomocą tego programu zbioru
o tysiącu elementach? Odpowiedź brzmi - 100 sekund. Ponieważ ilość danych wzrosła 10 razy, to czas obliczeń wzrósł 10 2,
czyli 100 razy. Poniżej przedstawiamy odpowiednią tabelkę.
Lp.
1.
2.
3.
4.
Ilość elementów
100
1.000
10.000
100.000
5.
1.000.000
6.
10.000.000
Czas obliczeń
1 sekunda
100 sekund = 1 minuta 40 sekund
10.000 sekund = 2 godziny 46 minut 40 sekund
1.000.000 sekund = 11 dni 13 godzin 46 minut 40 sekund
100.000.000 sekund = 3 lata 2 miesiące 9 godzin 46 minut
40 sekund
1x1010 sekund = 317 lat 1 miesiąc 4 dni 17 godzin 46 minut
40 sekund
Widzimy więc, iż algorytm ten spisuje się dobrze tylko przy niewielkiej liczbie elementów. Gdy liczba sortowanych
elementów jest duża, czas oczekiwania na rozwiązanie może być nie do zaakceptowania. Dlatego właśnie informatycy
poświęcili tak dużo swojego wysiłku na opracowanie odpowiednio szybkich algorytmów sortujących (te najszybsze mają
klasę złożoności O(n log2n) ).
3
UWAGA:
Notacja „O” eliminuje składniki wolniej rosnące oraz zaniedbuje czynniki stałe, przez które pomnożone są funkcje.
Przykład:
f(n) = 5n2 – 2*n + 2 = O(n2)
Ponieważ:

5n2 - 2n + 2  5n2
(dla każdego…) n  N
Przykłady określania złożoności czasowej:
1.
Przeszukiwanie elementów w celu znalezienia danego elementu x
i:=0;
Repeat i:=i+1 until (a[i]=x) or (i=n);
złożoność  O(n)
4
2.
Jeżeli elementy są wcześniej posortowane, stosujemy metodę połowienia przedziałów
(metoda bisekcji lub przeszukiwania połówkowego)
i:=1;
j:=n;
repeat k:=(i+j) div 2
if x>a[k] then i:= k+1 else j:= k-1
until (x=a[k]) or (i>j);
złożoność  O(log n)
3.
Znajdowanie min i max metodą iteracyjną
min:=a[1];
max:=a[1];
for i:=2 to n do
begin
if min >a[i] then min:=a[i];
if max <a[i] then max:=a[i]
end;
złożoność  O(n)
5
4. wyszukiwanie wartości maksymalnej w ciągu nieposortowanym
Załóżmy, że masz n liczb. Potrzebujesz przejrzeć każdą z nich po to, by określić która z nich jest największa. Potrzebujesz
zatem n operacji ("spojrzeń) - liczba potrzebnych operacji jest proporcjonalna do rozmiaru ciągu - więc złożoność liniowa,
O(n).
5. wyszukiwanie danej liczby w ciągu posortowanym
Pomyśl jakąś liczbę od 1 do 1000. Teraz ja zgadnę jaką pomyślałeś, zadając Ci maksymalnie 10 pytań:
Czy ta liczba jest mniejsza od 500?
Jeśli tak, to czy jest mniejsza od 250?
Jesli nie, to czy jest mniejsza od 375?
Jeśli nie, to czy jest mniejsza od 438?
Jeśli nie, to czy jest mniejsza od 469?
Jeśli tak, to czy jest mniejsza od 443?
Jeśli tak, to czy jest mniejsza od 440?
Jeśli tak, to tą liczbą jest 439.
Idea jest taka, że dzięki temu, że ciąg jest posortowany, cały czas pomniejszasz sobie zakres, w którym masz szukać o
połowę - dzięki temu potrzebujesz tylko ok. log21000 operacji - a więc masz złożoność O(log2n) - logarytmiczną.
6
6. sortowanie przez wybieranie
Masz nieposortowany ciąg o n elementach i posortowany o 0 elementach. Szukasz najmniejszego elementu w ciągu
nieposortowanym i wstawiasz go na koniec posortowanego ciągu, tyle razy, aż posortowany ciąg będzie miał n elementów,
a nieposortowany 0. Taki stan uzyskasz wtedy, gdy wszystkie n elementów z nieposortowanego przerzucisz w
posortowany. Musisz zatem n razy wyszukać najmniejszy element w ciągu - a wyszukiwanie najmniejszego elementu, jak
wiemy z 1. przykładu, ma złożoność O(n) (liniową). Zatem wykonanie n razy operacji o złozoności n wymaga n*n operacji,
czyli ma złożoność O(n*n) = O(n2) - kwadratową.
7
Z grubsza złożoności można posortować następująco (od najlepszej (najmniejszej)) :
log(n)
n
nlog(n)
n^2
n^3
...
2^n
3^n
...
n!
Na załączonym wykresie widać jak czas wykonania programu rośnie w zależności od wielkości
rozwiązywanego problemu.
8