Transcript O(n!)

ALGORYTMY
i STRUKTURY DANYCH
WYKŁAD 02
dr Marek Siłuszyk
WSFiZ
Zapisywanie algorytmów
Plan wykładu:
Ocena złożoności algorytmów
Koszty algorytmów

Pamięć

Czas
Notacje złożoności algorytmicznej
1. Notacja O (omikron).
2. Notacja Θ (theta)
3. Notacja Ω (omega).
Porównanie klas złożoności obliczeniowych
Problemy: P & NP
Jak zarobić 1 000 000$ ?
FLOPS
Zapisywanie i Porównywanie
Algorytmów
Formułujemy zadania i problemy w języku naturalnym ale...
język naturalny nie jest jednoznaczny !!! 
• prostota
• czytelność
• długość kodu
• poprawność
• czas realizacji
• zajętość pamięci
Idealny algorytm to taki, który ma:
• prosty kod,
• napisany w ogólnie dostępnym języku programowania,
• łatwo go zrozumieć,
• liczy szybko,
• nie wymaga dużo miejsca w pamięci
• zawsze daje poprawne wyniki.
Algorytm musi być:
• poprawny, tzn. dla każdego poprawnego zestawu danych, po
wykonaniu skończonej liczby czynności, prowadzi do
poprawnych wyników.
• jednoznaczny- w każdym wypadku jego zastosowania, dla
tych samych danych uzyskamy ten sam wynik
• szczegółowy, aby wykonawca algorytmu rozumiał opisane
czynności i potrafił je wykonać
• uniwersalny, aby służył do rozwiązywania pewnej grupy
zadań, a nie tylko jednego konkretnego przypadku zadania.
Koszty algorytmu; miara:
• liczba
zmiennych
• ilość miejsca
potrzebna dla
danych
• liczba instrukcji
• liczba operacji
arytmetycznych
• liczba wywołań
procedury
Ogólnie: wybór miary zależy od typu problemu,
rodzaju rozwiązania.
Złożoność obliczeniowa - ilość zasobów komputera użytych do
rozwiązywanego przez algorytm problemu.
Pamięciowa złożoność obliczeniowa informuje o ilości pamięci
komputera wymaganej przez dany algorytm dla n danych. Jednostką jest
komórka pamięci przechowująca jedną przetwarzaną daną. Systemy
komputerowe są wyposażane w coraz więcej taniej pamięci, zatem
parametr ten stracił nieco na znaczeniu - kiedyś walczono o każdy bajt, a
dzisiaj nikt nie przejmuje się dziesiątkami megabajtów. Postęp 
Czasowa złożoność obliczeniowa informuje nas o czasie wykonania
zadania przez algorytm dla n danych wejściowych. Wielkość ta musi być
niezależna od typu maszyny, na której uruchamiamy program, za
jednostkę czasowej złożoności obliczeniowej przyjęto wykonanie tzw.
operacji dominującej, czyli takiej, wokół której koncentruje się
przetwarzanie danych w algorytmie. Ilość wykonań operacji
dominujących jest proporcjonalna do czasu wykonania algorytmu.
Mnożenie macierzy
Operacje + , *
Wyszukiwanie elementu w tablicy
porównywanie
Sortowanie
Mając dany algorytm, konkretne środowisko i konkretne
dane możemy policzyć liczbę operacji dominujących.
Koszt algorytmu dla danych d:
algorytm
dane
Rząd wielkości służy do opisu czasu działania algorytmu.
Istnieją 3 notacje :
1. Notacja O (omikron).
2. Notacja Θ (theta)
3. Notacja Ω (omega).
Niech f, g : N  R+
1. Notacja O (omikron) - ograniczenie funkcji od góry.
f n  Og n  c   n0  N , n  n0  f n  c  g n
Mówimy, że pewna f(n) funkcja jest rzędu g(n), co zapisujemy f(n)=O(g(n)),
znaczy to, że istnieje taki argument n0, od którego począwszy dla każdego
niemniejszego od n0 wartości funkcji f(n) są niewiększe od wartości funkcji
g(n) z dokładnością do stałej c.
Jest to asymptotyczna granica górna. Służy do szacowania czasu działania
algorytmu w przypadku pesymistycznym.
Notacja O(g(n)) pozwala nam oszacować zachowanie się złożoności
obliczeniowej wraz ze wzrostem n do nieskończoności.
Przykład
Wyznaczyliśmy oczekiwaną złożoność obliczeniową pewnego
algorytmu i otrzymaliśmy następujący wzór:
f(n) = n2 + 3n
Gdy n będzie dążyło do nieskończoności czynnik 3n stanie się coraz
mniej znaczący w stosunku do czynnika n2. Stąd wnioskujemy, iż
jest to algorytm o złożoności O(n2). Aby to udowodnić, musimy
znaleźć stałą c oraz no, dla których zachodzi:
n2 + 3n ≤ cn2 dla
n ≥ no
n2 + 3n ≤ cn2 dla
Wystarczy przyjąć:
n ≥ no
c = 4 i no = 1:
n2 + 3n ≤ 4n2 dla n ≥ 1
podany algorytm ma rząd złożoności obliczeniowej równy O(n2).
1600
1400
1200
1000
f=n^2+3n
800
g=4*n^2
600
400
200
0
0
5
10
15
20
2. Notacja Θ (theta)
f n  g n  c1, c2   n0  N , n  n0
 c2  g n  f n  c1  g n
Notacja ta ogranicza funkcję f(n) od góry, tak jak notacja O oraz
dodatkowo od dołu. Oznacza to, że jeżeli f(n)=Θ(g(n)) to istnieje taki
argument n0, od którego począwszy dla każdego argumentu od niego
niemniejszego:
•wartości funkcji f(n) są niewiększe od wartości
funkcji g(n) z dokładnością do stałej c1
•wartości funkcji f(n) są niemniejsze od wartości
funkcji g(n) z dokładnością do stałej c2
3. Notacja Ω (omega).
f n  g n  c   n0  N , n  n0  c  g n  f n
Notacja ta ogranicza funkcję f(n) od dołu. Oznacza to, że jeśli
f(n)=Ω(g(n)) to istnieje taki argument n0, od którego począwszy dla
każdego argumentu od niego niemniejszego funkcja f(n) jest
niemniejsza niż g(n) z dokładnością do stałej c:
Na prawo od punktu od n0 funkcja
f(n) znajduje się nad funkcją c.g(n).
Jest to asymptotyczna granica dolna.
Służy więc do oszacowania działania
algorytmu w najlepszym przypadku.
Złożoność obliczeniowa
Jest jednym z najważniejszych parametrów charakteryzujących algorytm.
Decyduje on o efektywności całego programu. Podstawowymi zasobami
systemowymi uwzględnianymi w analizie algorytmów są czas działania
oraz obszar zajmowanej pamięci. Na złożoność czasową składają się
dwie wartości: pesymistyczna, czyli taka, która charakteryzuje najgorszy
przypadek działania oraz oczekiwana.
Algorytmy mają złożoność czasową proporcjonalną do funkcji:
•
log(n)- złożoność logarytmiczna
•
n - złożoność liniowa
•
n log(n) - złożoność liniowo-logarytmiczna
•
n2 - złożoność kwadratowa
•
nk - złożoność wielomianowa
•
2n - złożoność wykładnicza
•
n! - złożoność wykładnicza, ponieważ n!>2n już od n=4
Złożoność obliczeniowa stała - O(1)
Algorytm wykonuje stałą ilość operacji dominujących bez względu
na rozmiar danych wejściowych. W takim algorytmie czas
wykonania jest stały i nie zmienia się przy zmianie liczby
przetwarzanych elementów.
Złożoność obliczeniowa liniowa - O(n)
Dla każdej danej algorytm wykonuje stałą ilość operacji
dominujących. Czas wykonania jest proporcjonalny do liczby n
danych wejściowych. Czas wykonania rośnie liniowo ze wzrostem
liczby elementów.
Złożoność obliczeniowa kwadratowa - O(n2)
Algorytm dla każdej danej wykonuje ilość operacji dominujących
proporcjonalną do liczby wszystkich przetwarzanych danych. Czas
wykonania jest proporcjonalny do kwadratu liczby danych n.
Inne złożoności tego typu O(n3), O(n4)...
wielomianowych złożoności obliczeniowych.
noszą
nazwę
Złożoność obliczeniowa logarytmiczna - O(log n)
W algorytmie zadanie rozmiaru n da się sprowadzić do zadania
rozmiaru n/2. Przykładem jest wyszukiwanie binarne w zbiorze
uporządkowanym. Sprawdzenie środkowego elementu pozwala
określić, w której z dwóch połówek zbioru może znajdować się
poszukiwany element.
W określeniu klasy złożoności obliczeniowej nie podajemy podstawy logarytmu,
ponieważ logarytmy o różnych podstawach z tej samej wartości różnią się od siebie
jedynie iloczynem przez stałą
a = bc oraz logbx = c logax
Z definicji notacji Omikron wynika, iż klasa złożoności dwóch
funkcji różniących się jedynie iloczynem przez stałą jest taka sama.
Pytanie:
Dlaczego w Informatyce bardzo często stosuje się logarytmy przy
podstawie 2 ?
Złożoność obliczeniowa liniowo logarytmiczna - O(n log n)
Zadanie rozmiaru n daje się sprowadzić do dwóch podzadań
rozmiaru n/2 plus pewna ilość operacji, których liczba jest
proporcjonalna do ilości danych n. Tego typu złożoność
obliczeniową posiadają dobre algorytmy sortujące.
Złożoność obliczeniowa wykładnicza - O(2n), O(n!)
Złożoność obliczeniową O(2n) posiada algorytm, w którym
wykonywana jest stała liczba operacji dla każdego podzbioru n
danych wejściowych.
Złożoność obliczeniową O(n!) posiada algorytm, w którym
wykonywana jest stała liczba operacji dla każdej permutacji n
danych wejściowych.
Złożoność obliczeniowa wykładnicza jest bardzo niekorzystna,
ponieważ czas wykonania rośnie bardzo szybko wraz ze wzrostem
liczby danych wejściowych.
Porównanie
Posiadamy dwa komputery. Pierwszy potrafi wykonać milion
operacji dominujących w ciągu sekundy.
Drugi jest superkomputerem i potrafi tych operacji wykonać
milion razy więcej (bardzo optymistyczne założenie ), czyli
1000 miliardów w ciągu sekundy. W tabeli przedstawione są dane
o czasie wykonania algorytmu klasy O(2n) na tych dwóch
Czasy wykonania algorytmu klasy O(2 )
komputerach.
n
Rozmiar danych n
Czas wykonania
dla komputera 1
Czas wykonania dla
superkomputera 2
20
50
100
200
1,05
sekund
35,7
lat
40169423
mld lat
5x1037
mld lat
10-6
sekund
1126
sekund
40,17
mld lat
5x1031
mld lat
Porównanie klas złożoności obliczeniowych
Klasa złożoności
obliczeniowej
Nazwa klasy złożoności
obliczeniowej
Cechy algorytmu
O(1)
stała
działa prawie natychmiast
O(log n)
logarytmiczna
niesamowicie szybki
O(n)
liniowa
szybki
O(n log n)
liniowo-logarytmiczna
dosyć szybki
O(n2)
kwadratowa
wolny dla dużych n
O(n3)
sześcienna
wolny dla większych n
O(2n), O(n!)
wykładnicza
nierealizowalny dla większych n
Klasa złożoność obliczeniowej informuje nas, iż czas t w funkcji liczby elementów
n jest proporcjonalny do funkcji określonej przez klasę złożoności, a stała c jest
współczynnikiem tej proporcjonalności.
Fakt ten pozwala w przybliżeniu oszacować czas wykonania algorytmu dla n
elementów, jeśli jest znana jego klasa złożoności obliczeniowej oraz czas
wykonania dla innej liczby elementów.
Przykład
Załóżmy, iż dla n1 = 100 elementów algorytm o klasie złożoności obliczeniowej
O(n2) wykonywał się przez czas t1 = 5 sekund. Ile przypuszczalnie czasu t2 zajmie
przetworzenie w tym algorytmie n2 = 400 elementów?
Rozwiązanie:
Klasa złożoności obliczeniowej algorytmu jest rzędu O(n2), to czas wykonania jest
proporcjonalny do kwadratu liczby przetwarzanych elementów.
2
2
2

 n1 
t2 c  n1
t1  c  n1
 
  

2
2
t
n
c

n

t

c

n
1
2

2
2
2
Z otrzymanej nierówności wyznaczamy czas t2:
Do otrzymanego wzoru podstawiamy
wartości liczbowe i otrzymujemy:
 n1 
t2  t1   
 n2 
2
Porównywanie rzędów funkcji
Twierdzenie (Porównywanie rzędów funkcji)
lim
n  
 0, to f i g są tego samego rzędu.
f n 

 c   0 , to f = O(g) oraz f   (g).
g n 
 ,
to f ma rząd większy niż g, g = O(f) i g  (f).

Przykład 1 Niech f(n)=100n, g(n)= 2n+100,
h(n) = 0.1 n2 +n.
Mamy f = O(n) f = (n)
h = O(n2) = O(n3 )
g= O(n2) g = (n)
h O(n) h =  (n)
Przykład 2
f(n) = 0.3 n3 + 10n + 100
g(n)= n3
h(n) = log n
lim n f(n)/g(n)= 0.3
Czyli f = (g)
lim f(n)/h(n) = + 
Czyli h = O(f), h  (f).
f(n) =
2n
f(n) =0.25 n2
f(n)=n
f(n)= log n
Porównanie szybkości wzrostu funkcji
Złożoność a rozmiar i czas
Ile czasu potrzeba na rozwiązanie zadania o ustalonym
rozmiarze i złożoności?
wymiar
n=102
n= 104
lg n
n
n lg n
n2
n3
2n
6.6ms
0.1ms
0.6ms
10ms
1s
106lat
0.1s
100s
13.3 ms 10ms
11dni 10100lat
Jaki jest maksymalny rozmiar problemu, który można
rozwiązać w ustalonym czasie, znając złożoność algorytmu?
cza
s 1s
1h
lg n
n
n lg n
n2
n3
2n
2 1000000
106
63*103
103
102
19
36*108 13* 107 60* 103 15* 102
31
Czy szybkość może
pokonać złożoność?
Mamy 5 algorytmów A1, A2, A3, A4, A5 rozwiązujących ten sam problem.
Niech si oznacza maksymalny rozmiar problemu, który można rozwiązać na
komputerze 1 przy pomocy algorytmu Ai w ustalonym czasie t. Jaki jest
maksymalny rozmiar problemu, który można rozwiązać w tym samym czasie t na
komputerze 10 razy szybszym?
lg n
n
n2
n3
2n
Komputer 1
s1
s2
s3
s4
s5
Komputer 2
(s1) 10
10*s2
10*s3
2*s4
?
Przykład A5. Dla komputera 1: T(A5,s5)= 2 s5 = t .
Dla komputera 2: T(A5,s5)= 2 s5 = t /10.
Szukamy takiego x, że T(A5,x)= t.
Mamy więc t = 10* 2 s5 = 2 x = 2 s5+lg10 . Czyli x = 3.2 +s5. 
Porównanie szybkości
wzrostu funkcji
lg n! = S i=1..n lg i  n lg n
lg n!
n 1
n 1
1
1
 lg x  
lg n
ln x
dx  lg e( x ln x  x)1n 1  cn lg n
ln 2
Czy wszystkie algorytmy da się rozwiązać w czasie
wielomianowym?
Niestety Nie . Przykładem może być algorytm wyznaczania cyklu
Hamiltona. Rozdzieliliśmy już wyraźnie algorytmy na dwie grupy,
teraz opiszemy je trochę formalnej:
Wszystkie problemy decyzyjne, które w co najwyżej
wielomianowym czasie są rozwiązywane przez DTM
(Deterministyczna Maszyn Turinga) tworzą klasę P (P- ang.
polynomial: wielomianowy).
Wszystkie problemy decyzyjne, które w co najwyżej
wielomianowym czasie są rozwiązywane przez NDTM
(Niedeterministyczna Maszyna Turinga) tworzą klasę NP (NP- ang.
nondeterministic polynomial: niedeterministycznie wielomianowy).
Jak dotąd nikomu nie udało się udowodnić, ani zaprzeczyć, że
P=NP. Problem ten został sformułowany w roku 1971 i pozostaje
otwarty do dziś. Większość naukowców uważa jednak, że klasa P i
NP nie są sobie równe, chodź : nie ma na to dowodu!
Na wykresie tym widać trzecią klasę: NPI (and. NPIntermediate: NP pośrednie),
jest to klasa pośrednia między klasami P i NP.
Temat NP-Zupełności jest bardzo obszerny i zawiera całą masę dowodów.
W szczególności wszystkie problemy klasy P są NP, ponieważ można je
sprawdzić w czasie wielomianowym. Innymi słowy, klasa P zawiera się
nieostro w NP (
). Nie wiadomo natomiast, czy istnieje problem
NP, który nie jest w klasie P (czyli, czy P rożni się od NP, lub
inaczej
lub inaczej
). Jest to jedno z wielkich
nierozwiązanych zagadnień matematyki
Różnica pomiędzy problemami P i NP polega na tym, że w przypadku P
znalezienie rozwiązania ma mieć złożoność wielomianową, podczas gdy dla
NP sprawdzenie podanego z zewnątrz rozwiązania ma mieć taką złożoność.
Problemy milenijne (Millennium Prize Problems) zostały
ogłoszone przez Clay Mathematics Institute 24 maja 2000. Jest
to zestaw siedmiu zagadnień, które zostały uznane przez instytut
za ważne dla nauki, a za rozwiązanie każdego z nich
wyznaczono 1 000 000 $ nagrody. Niektóre z nich zostały już
rozwiązane, inne wciąż czekają na rozwiązanie.
Na problemy milenijne składają się:
•Zagadnienie klas P/NP
•Hipoteza Hodge'a
•Hipoteza Poincarégo
•Hipoteza Riemanna
•Teoria Yanga-Millsa
•Równania Naviera-Stokesa
•Hipoteza Bircha i Swinnertona-Dyera
Metody rozwiązywania układów równań
można podzielić na dwie grupy:
DOKŁADNE:
wzory Cramera,
metoda Gaussa,
metoda Gaussa-Jordana,
metoda wyboru elementu
podstawowego,
metoda Choleskiego (rozkład na
macierze L i U)
...
ITERACYJNE:
metoda Jacobiego (metodą
iteracji prostej)
metoda Seidela
metoda relaksacji)
...
 a11 x1  a12 x 2  a1n x n  b1
a x  a x  a x  b
 21 1
22 2
2n n
2


a n1 x1  a n 2 x 2  a nn x n  bn
Jaka jest złożoność obliczeniowa metod dokładnych i
iteracyjnych stosowanych do rozwiązywania
układów równań liniowych?
Wyznaczmy złożoność obliczeniową metody wyznaczników
Cramera.
Wzory
Cramera
nakazują
obliczenie
n+1
wyznaczników stopnia n. Zatem problem sprowadza się do
znalezienia efektywnego algorytmu obliczania wyznacznika
macierzy. Obliczanie wyznacznika korzystając bezpośrednio z
definicji:
det A 
(1)



Perm k1 ,k2 , ...,kn
I Perm
a1k1 a2k2  ...  ankn
det A 
(1)



I Perm
a1k1 a2k2  ...  ankn
Perm k1 ,k2 , ...,kn
wszystkie permutacje {k1, k2 , ... , kn } zbioru {1, 2 , ... , n}
I Perm - liczba inwersji w permutacji {k1, k2 , ... , kn }
Ilość Działań:
n! dodawań
n  n! mnożeń.
Łącznie
musimy
wykonać (n  1)! działań
zmiennoprzecinkowych.
arytmetycznych
Aby rozwiązać układ równań trzeba obliczyć n+1 wyznaczników
stopnia n oraz wykonać n dzieleń. Zatem metoda ta wymaga
wykonania
(n  1)  (n  1)!  n
działań arytmetycznych.
Praktycznie wobec tak dużej złożoności obliczeniowej, wzory
Cramera stosuje się tylko dla n  4
Tabela daje wyobrażenie o czasie realizacji tego algorytmu przy
założeniu, że do obliczeń używamy komputera wykonującego
108 operacji zmiennoprzecinkowych na sekundę
(n  1)!
n
4
5
6
7
8
9
10
15
20
50
100
Tabela Czas obliczania wyznacznika
Liczba operacji Czas wykonania Czas wykonania
arytmetycznych w sekundach
w latach
120
1.20E-06
720
7.20E-06
5040
5.04E-05
40320
4.03E-04
3.6E+05
3.63E-03
3.6E+06
3.63E-02
4.0E+07
3.99E-01
2.1E+13
2.09E+05
6.63E-03
5.1E+19
5.11E+11
1.62E+04
1.6E+66
1.55E+58
4.92E+50
9.4E+159
9.43E+151
2.99E+144
FLOPS (FLoating Point Operations Per Second) - liczba operacji
zmiennoprzecinkowych
na
sekundę,
jednostka
wydajności
komputerów, a dokładniej wydajności układów realizujących
obliczenia zmiennoprzecinkowe.
Większość
współczesnych
mikroprocesorów
zawiera
specjalizowaną jednostkę FPU (floating-point unit), która jest
odpowiedzialna za wykonywanie obliczeń zmiennoprzecinkowych.
http://pl.wikipedia.org/wiki/FLOPS
Najszybsze współczesne superkomputery osiągają wydajność liczoną w TFLOPSach - najszybszy komputer świata wg. 26-ego rankingu TOP500, to Blue Gene/L z
280,6 TFLOPS-ami.
Jeden z najszybszych komputerów w Polsce znajduje się w Centrum
Informatycznym Trójmiejskiej Akademickiej Sieci Komputerowej udało mu się
uzyskać wynik 1,12 TFLOPS-a.
Dla porównania, w miarę typowy nowoczesny komputer osobisty (np. z
procesorem Pentium 4, czy Athlon 64 i częstotliwości zegara około 2GHz) ma
wydajność rzędu paru GFLOPS-ów.
Ludzie wydają się być jeszcze gorszymi procesorami zmiennoprzecinkowymi.
Podzielenie dwóch liczb 10 cyfrowych zajmuje człowiekowi średnio kwadrans,
stąd można by wnioskować, że szybkość ludzkiego mózgu należałoby mierzyć w
miliFLOPS-ach. Jednak operacje na takich liczbach prawdopodobnie nie są
"naturalne" dla ludzkiego mózgu. Szacuje się, że mózg jest w stanie wykonywać
około 10 biliardów operacji na sekundę, jest to jednak bardzo grube oszacowanie
Dziękuję za Uwagę
Zapraszam na następny Wykład
