Złożoność obliczeniowa

Slides:



Advertisements
Podobne prezentacje
Wprowadzenie w problematykę związaną z twierdzeniem Gödla
Advertisements

Instrukcje - wprowadzenie
Rekurencja 1 Podprogram lub strukturę danych nazywamy rekurencyjną, (recursive subprogram, recursive data structure) jeżeli częściowo składa się z samej.
Programowanie obiektowe
METODY ANALIZY PROGRAMÓW
Grażyna Mirkowska PJWSTK 15 listopad 2000
Wykład 06 Metody Analizy Programów System Hoare
Metody rozwiązywania układów równań liniowych
Programowanie I Rekurencja.
Języki programowania C++
PROGRAMOWANIE STRUKTURALNE
ALGORYTM Co to jest algorytm?
Badania operacyjne. Wykład 2
Iteracja, indukcja i rekurencja
Teoretyczne podstawy informatyki
Liczby Pierwsze - algorytmy
ZŁOŻONOŚĆ OBLICZENIOWA
Opracowała: Elżbieta Fedko
Turbo pascal – instrukcje warunkowe, iteracyjne,…
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 8: Wykorzystanie procedur i funkcji © Jan Kaczmarek.
Jakość sieci geodezyjnych. Pomiary wykonane z największą starannością, nie dostarczają nam prawdziwej wartości mierzonej wielkości, lecz są zwykle obarczone.
Materiały pomocnicze do wykładu
Algorytmy i struktury danych
Matura z informatyki Arkusz I.
Wstęp do interpretacji algorytmów
Dr Anna Kwiatkowska Instytut Informatyki
ALGORYTMY.
Podstawy programowania
Podstawy programowania II
POJĘCIE ALGORYTMU Pojęcie algorytmu Etapy rozwiązywania zadań
Podstawy programowania
Programowanie w języku Matlab
Instrukcje sterujące część 2
© A. Jędryczkowski – 2006 r. © A. Jędryczkowski – 2006 r.
TABLICE C++.
Ocena przydatności algorytmu – czas działania (złożoność czasowa)
Semantyczna poprawność algorytmów – dowodzenie za pomocą niezmienników
Algorytmy i struktury danych
Podstawy analizy matematycznej II
Równania rekurencyjne
PHP: warunki, pętle, switch, break, continue
Elementy Rachunku Prawdopodobieństwa i Statystyki
Zakładamy a priori istnienie rozwiązania α układu równań.
Szereg czasowy – czy trend jest liniowy?
Podstawy analizy matematycznej I
Elżbieta Fiedziukiewicz
Informatyka MZT1 Wykład 6 Iteracje while i repeat Tablice Rekordy
Wykład 10 typ zbiorowy rekurencja.
Wykład 7 Synchronizacja procesów i wątków
ZAPIS BLOKOWY ALGORYTMÓW
Algorytmika.
Instrukcje iteracyjne
Algorytmika Iteracje autor: Tadeusz Lachawiec.
ALGORYTMY Co to jest algorytm ? Cechy algorytmu Budowa algorytmów
Metody matematyczne w Inżynierii Chemicznej
Algorytmy- Wprowadzenie do programowania
Wstęp do interpretacji algorytmów
Pętle – instrukcje powtórzeń
Wstęp do programowania Wykład 4
Instrukcje warunkowe w php. Pętla FOR Czasem zachodzi potrzeba wykonania jakiejś czynności określoną ilość razy. Z pomocą przychodzi jedna z najczęściej.
PHP jest językiem skryptowym służącym do rozszerzania możliwości stron internetowych. Jego składnia jest bardzo podobna do popularnych języków programowania.
Algorytmy. Co to jest algorytm? Przepis prowadzący do rozwiązania zadania.
Programowanie strukturalne i obiektowe Klasa I. Podstawowe pojęcia dotyczące programowania 1. Problem 2. Algorytm 3. Komputer 4. Program komputerowy 5.
Metody matematyczne w Inżynierii Chemicznej
Efektywność algorytmów
Zrozumieć, przeanalizować i rozwiązać
ALGORYTMY I STRUKTURY DANYCH
Programowanie I Rekurencja.
ALGORYTMY I STRUKTURY DANYCH
POJĘCIE ALGORYTMU Wstęp do informatyki Pojęcie algorytmu
Zapis prezentacji:

Złożoność obliczeniowa Teoretyczne podstawy informatyki Wykład 2b Poprawność programu Złożoność obliczeniowa

Poprawność programu Złożoność obliczeniowa Weryfikacja poprawności programu Niezmienniki pętli Problem STOP-u Złożoność obliczeniowa i asymptotyczna Notacja „wielkie O ” Notacja W i Q Przykłady rzędów złożoności Znajdowanie złożoności asymptotycznej

Weryfikacja poprawności programu Sprawdzenie częściowej poprawności: Weryfikacja opiera się o sprawdzenie specyfikacji, która jest czymś niezależnym od kodu programu. Specyfikacja wyraża, co program ma robić, i określa związek miedzy jego danymi wejściowymi i wyjściowymi. W specyfikacji definiujemy warunek dotyczący danych wejściowych, który musi być spełniony na początku programu, tzw. warunek wstępny. Jeżeli dane nie spełniają tego warunku, to nie ma gwarancji że program będzie działał poprawnie ani nawet że się zatrzyma. W specyfikacji definiujemy również warunek końcowy, czyli co oblicza program przy założeniu że się zatrzyma. Warunek końcowy jest zawsze prawdziwy jeżeli zachodzi warunek wstępny.

Weryfikacja poprawności programu Aby wykazać, że program jest zgodny ze specyfikacją, trzeba podzielić ją na wiele kroków, podobnie jak dzieli się na kroki sam program. Każdy krok programu ma swój warunek wstępny i warunek końcowy. Prosta instrukcja przypisania x = v , x-zmienna, v-wyrażenie warunek wstępny: x  0 wyrażenie: x = x+1 warunek końcowy: x  1 Dowodzimy poprawności specyfikacji przez podstawienie wyrażenia x+1 pod każde wystąpienie x w warunku końcowym. Otrzymujemy formułę x+1  1, która wynika z warunku wstępnego x  0 Zatem specyfikacja tej instrukcji podstawienia jest poprawna

Weryfikacja poprawności programu Istnieją cztery sposoby łączenia prostych kroków w celu otrzymania kroków bardziej złożonych. 1. stosowanie instrukcji złożonych (bloków instrukcji wykonywanych sekwencyjnie) 2. stosowanie instrukcji wyboru 3. stosowanie instrukcji powtarzania (pętli) 4. wywołania funkcji Każdej z tych metod budowania kodu odpowiada metoda przekształcania warunku wstępnego i końcowego w celu wykazania poprawności specyfikacji kroku złożonego: Aby wykazać prawdziwość specyfikacji instrukcji wyboru, należy dowieść, że warunek końcowy wynika z warunku wstępnego i warunku testu w każdym z przypadków instrukcji wyboru. Funkcje też mają swoje warunki wstępne i warunki końcowe; musimy sprawdzić że warunek wstępny funkcji wynika z warunku wstępnego kroku wywołania, a warunek końcowy pociąga za sobą warunek końcowy kroku wywołania. Wykazanie poprawności programowania dla pętli wymaga użycia niezmiennika pętli.

Niezmienniki pętli Niezmiennik pętli określa warunki jakie muszą być zawsze spełnione przez zmienne w pętli, a także przez wartości wczytane lub wypisane (jeżeli takie operacje zawiera). Warunki te muszą być prawdziwe przed pierwszym wykonaniem pętli oraz po każdym jej obrocie. (mogą stać się chwilowo fałszywe w trakcie wykonywania wnętrza pętli, ale musza ponownie stać się prawdziwe przy końcu każdej iteracji) Dowodzimy że pewne zdanie jest niezmiennikiem pętli wykorzystując zasadę indukcji matematycznej. Mówi ona że: jeśli (1) pewne zdanie jest prawdziwe dla n = 0 oraz (2) z tego, że jest prawdziwe dla pewnej liczby n  0 wynika że musi być prawdziwe dla liczby n+1, to (3) zdanie to jest prawdziwe dla wszystkich liczb nieujemnych.

Dowodzenie niezmienników pętli Stwierdzenie jest prawdziwe przed pierwszym wykonaniem pętli, ale po dokonaniu całej inicjacji; wynika ono z warunku wstępnego pętli. Jeśli założymy, że stwierdzenie jest prawdziwe przed jakimś przebiegiem pętli i że pętla zostanie wykonana ponownie, a więc warunek pętli jest spełniony, to stwierdzenie będzie nadal prawdziwe po kolejnym wykonaniu wnętrza pętli. Proste pętle maja zazwyczaj proste niezmienniki. Pętle dzielimy na trzy kategorie pętla z wartownikiem: czyta i przetwarza dane aż do momentu napotkania niedozwolonego elementu pętla z licznikiem: zawczasu wiadomo ile razy pętla będzie wykonana pętle ogólne: wszystkie inne

Problem „STOP-u” Uwagi końcowe Aby udowodnić że program się zatrzyma, musimy wykazać, że zakończą działanie wszystkie pętle programu i wszystkie wywołania funkcji rekurencyjnych. Dla pętli z wartownikiem wymaga to umieszczenia w warunku wstępnym informacji, że dane wejściowe zawierają wartownika. Dla pętli z licznikiem wymaga to dodefiniowania granicy dolnej (górnej) tego licznika. Uwagi końcowe Istnieje wiele innych elementów, które muszą być brane pod uwagę przy dowodzeniu poprawności programu: możliwość przepełnienia wartości całkowitoliczbowych możliwość przepełnienia lub niedomiar dla liczb zmiennopozycyjnych możliwość przekroczenia zakresów tablic prawidłowość otwierania i zamykania plików

Złożoność obliczeniowa i asymptotyczna Miara służąca do porównywania efektywności algorytmów. Mamy dwa kryteria efektywności czas pamięć Do oceny efektywności stosujemy jednostki logiczne wyrażające związek miedzy rozmiarem danych N (wielkość pliku lub tablicy) a ilością czasu T potrzebną na ich przetworzenie. Funkcja wyrażająca zależność miedzy N a T jest zwykle bardzo skomplikowana, a jej obliczenie ma znaczenie jedynie w odniesieniu do dużych rozmiarów danych przybliżona miara efektywności czyli tzw. złożoność asymptotyczna

Szybkość wzrostu poszczególnych składników funkcji n – ilość wykonywanych operacji Funkcja: f(n) = n2 + 100•n + log10 n + 1000 n f(n) n2 100•n log10 n 1000 1 10 100 103 104 105 1 101 2 101 21 002 1 101 003 0.1% 4.8% 48% 91% 99% 99.9% 9% 1% 0.0% 0.05% 0.001% 0.0003% 0.09% 0.0000% Dla dużych wartości n, funkcja rośnie jak n2, pozostałe składniki mogą być zaniedbane.

(wprowadzona przez P. Bachmanna w 1894r) Notacja „wielkie O ” (wprowadzona przez P. Bachmanna w 1894r) Definicja: f(n) jest O (g(n)), jeśli istnieją liczby dodatnie c i N takie że, f(n) < c•g(n) dla wszystkich n  N. Przykład: f(n) = n2 + 100*n + log10 n + 1000 możemy przybliżyć jako f(n) ~ n2 + 100*n + O(log10 n) albo jako f(n) ~ O(n2) Notacja „wielkie O ” ma kilka pozytywnych własności które możemy wykorzystać przy szacowaniu efektywności algorytmów

Własności notacji „wielkie O” Własność 1 (przechodniość) Jeśli f(n) jest O(g(n)) i g(n) jest O(h(n)), to f(n) jest O(h(n)) Własność 2 Jeśli f(n) jest O(h(n)) i g(n) jest O(h(n)), to f(n)+g(n) jest O(h(n)) Własność 3 Funkcja ank jest O(nk) Własność 4 Funkcja nk jest O(nk+j) dla dowolnego dodatniego j Z tych wszystkich własności wynika, że dowolny wielomian jest „wielkie O” dla n podniesionego do najwyższej w nim potęgi, czyli f(n) = aknk + ak-1 nk-1 + ..... + a1n +a0 jest O(nk) (jest też oczywiście O(nk+j) dla dowolnego dodatniego j)

Własności notacji „wielkie O” Jeśli f(n)=c g(n), to f(n) jest O(g(n)) Własność 6 Funkcja logan jest O(logbn) dla dowolnych a i b większych niż 1 Własność 7 logan jest O(log2n) dla dowolnego dodatniego a Jedną z najważniejszych funkcji przy ocenianiu efektywności algorytmów jest funkcja logarytmiczna. Jeżeli można wykazać że złożoność algorytmu jest rzędu logarytmicznego, algorytm można traktować jako bardzo dobry. Istnieje wiele funkcji lepszych w tym sensie niż logarytmiczna, jednak zaledwie kilka spośród nich, jak O(log2 log2n) czy O(1) ma praktyczne znaczenie.

Notacja W i Q Notacja „wielkie O” odnosi się do górnych ograniczeń funkcji. Istnieje symetryczna definicja dotycząca dolnych ograniczeń: Definicja f(n) jest W (g(n)), jeśli istnieją liczby dodatnie c i N takie że, f(n)  c g(n) dla wszystkich n  N. Równoważność f(n) jest W (g(n)) wtedy i tylko wtedy, gdy g(n) jest O(f(n)) Definicja f(n) jest Q (g(n)), jeśli istnieją takie liczby dodatnie c1, c2 i N takie że, c1g(n)  f(n)  c2g(n) dla wszystkich n  N.

Możliwe problemy Celem wprowadzonych wcześniej sposobów zapisu (notacji) jest porównanie efektywności rozmaitych algorytmów zaprojektowanych do rozwiązania tego samego problemu. Jeżeli będziemy stosować tylko notacje „wielkie O” do reprezentowania złożoności algorytmów, to niektóre z nich możemy zdyskwalifikować zbyt pochopnie. Przykład Załóżmy, że mamy dwa algorytmy rozwiązujące pewien problem, wykonywana przez nie liczba operacji to odpowiednio 108n i 10n2. Pierwsza funkcja jest O(n), druga O(n2). Opierając się na informacji dostarczonej przez notacje „wielkie O” odrzucilibyśmy drugi algorytm ponieważ funkcja kosztu rośnie zbyt szybko. To prawda ...... ale dopiero dla odpowiednio dużych n, ponieważ dla n<107 drugi algorytm wykonuje mniej operacji niż pierwszy. Istotna jest więc też stała (108), która w tym przypadku jest zbyt duża aby notacja była znacząca.

Przykłady rzędów złożoności Algorytmy można klasyfikować ze względu na złożoność czasową lub pamięciową. W związku z tym wyróżniamy wiele klas algorytmów. Algorytm stały: czas wykonania pozostaje taki sam niezależnie od ilości przetwarzanych elementów. Algorytm kwadratowy: czas wykonania wynosi O(n2). Algorytm logarytmiczny: czas wykonania wynosi O(log n) itd... Analiza złożoności algorytmów jest niezmiernie istotna i nie można jej lekceważyć argumentując potencjalną szybkością obliczeń komputera. Nie sposób jej przecenić szczególnie w kontekście doboru struktury danych.

liczba operacji i czas wykonania Klasy algorytmów i ich czasy wykonania na komputerze działającym z szybkością 1 instrukcja/ms klasa złożoność liczba operacji i czas wykonania n 10 103 stały logarytm liniowy kwadratowy wykładniczy O(1) O(log n) O(n) O(n2) O(2n) 1 3.32 102 1024 1ms 3ms 10ms 100ms 9.97 106 10301 1 ms 10 ms 1s >> 1016 lat

Znajdowanie złożoności asymptotycznej Efektywność algorytmów ocenia się przez szacowanie ilości czasu i pamięci potrzebnych do wykonania zadania, dla którego algorytm został zaprojektowany. Najczęściej jesteśmy zainteresowani złożonością czasową, mierzoną zazwyczaj liczbą przypisań i porównań realizowanych podczas wykonywania programu. Przykład 1: Prosta pętla for (i=sum=0; i<n; i++) sum+=a[i]; Powyższa pętla powtarza się n razy, podczas każdego jej przebiegu realizuje dwa przypisania: aktualizujące zmienną „sum” zmianę wartości zmiennej „i” Mamy zatem 2n przypisań podczas całego wykonania pętli; jej asymptotyczna złożoność wynosi O(n)

Znajdowanie złożoności asymptotycznej Przykład 2: Pętla zagnieżdżona for (i=0; i<n; i++) { for (j=1, sum=a[0]; j<=i; j++) sum+=a[j]; } Na samym początku zmiennej „i” nadawana jest wartość początkowa. Pętla zewnętrzna powtarza się n razy, a w każdej jej iteracji wykonuje się wewnętrzna pętla oraz instrukcja przypisania wartości zmiennym „i”, „ j”, „ sum”. Pętla wewnętrzna wykonuje się „i” razy dla każdego i{1,...,n-1}, a na każdą iteracje przypadają dwa przypisania: jedno dla „sum”, jedno dla„j”. Mamy zatem: 1+3n+2(1+2+...+n-1) = 1+3n+n(n-1) = O(n)+O(n2) = O(n2) przypisań wykonywanych w całym programie. Jej asymptotyczna złożoność wynosi O(n2). Pętle zagnieżdżone maja zwykle większą złożoność niż pojedyncze, jednak nie musi tak być zawsze.

Znajdowanie złożoności asymptotycznej Analiza tych dwóch przypadków była stosunkowo prosta ponieważ liczba iteracji nie zależała od wartości elementów tablicy. Wyznaczenie złożoności asymptotycznej jest trudniejsze jeżeli liczba iteracji nie jest zawsze jednakowa. Przykład 3 Znajdź najdłuższą podtablice zawierającą liczby uporządkowane rosnąco. for (i=0; len=1; i<n-1; i++) { for (i1=i2=k=i; k<n-1 && a[k]<a[k+1]; k++,i2++); if(len < i2-i1+1) len=i2-i1+1; } Jeśli liczby w tablicy są uporządkowane malejąco, to pętla zewnętrzna wykonuje się n-1 razy, a w każdym jej przebiegu pętla wewnętrzna wykona się tylko raz. Złożoność algorytmu jest więc O(n).

Znajdowanie złożoności asymptotycznej Analiza tych dwóch przypadków była stosunkowo prosta ponieważ liczba iteracji nie zależała od wartości elementów tablicy. Wyznaczenie złożoności asymptotycznej jest trudniejsze jeżeli liczba iteracji nie jest zawsze jednakowa. Przykład 3 Znajdź najdłuższą podtablice zawierającą liczby uporządkowane rosnąco. for (i=0; len=1; i<n-1; i++) { for (i1=i2=k=i; k<n-1 && a[k]<a[k+1]; k++,i2++); if(len < i2-i1+1) len=i2-i1+1; } Jeśli liczby w tablicy są uporządkowane rosnąco, to pętla zewnętrzna wykonuje się n-1 razy, a w każdym jej przebiegu pętla wewnętrzna wykona się i razy dla i{1,...,n-1}. Złożoność algorytmu jest więc O(n2).

Znajdowanie złożoności asymptotycznej Analiza tych dwóch przypadków była stosunkowo prosta ponieważ liczba iteracji nie zależała od wartości elementów tablicy. Wyznaczenie złożoności asymptotycznej jest trudniejsze jeżeli liczba iteracji nie jest zawsze jednakowa. Przykład 3 Znajdź najdłuższą podtablice zawierająca liczby uporządkowane rosnąco. for (i=0; len=1; i<n-1; i++) { for (i1=i2=k=i; k<n-1 && a[k]<a[k+1]; k++,i2++); if(len < i2-i1+1) len=i2-i1+1; } Z reguły dane nie są uporządkowane i ocena złożoności algorytmu jest rzeczą niełatwą ale bardzo istotna. Staramy się wyznaczy złożoność w „przypadku optymistycznym”, „przypadku pesymistycznym” oraz „przypadku średnim”. Często posługujemy się przybliżeniami opartymi o notacje „wielkie O, W i Q”.

Nie przejmuj się efektywnością algorytmu… wystarczy poczekać kilka lat. Taki pogląd funkcjonuje w środowisku programistów, nie określono przecież granicy rozwoju mocy obliczeniowych komputerów. Nie należy się jednak z nim zgadzać w ogólności. Należy zdecydowanie przeciwstawiać się przekonaniu o tym, że ulepszenia sprzętowe uczynią pracę nad efektywnymi algorytmami zbyteczną. Istnieją problemy których rozwiązanie za pomocą zasobów komputerowych jest teoretycznie możliwe, ale praktycznie przekracza możliwości istniejących technologii. Przykładem takiego problemu jest rozumienie języka naturalnego, przetwarzanie obrazów (do pewnego stopnia oczywiście) czy “inteligentna” komunikacja pomiędzy komputerami a ludźmi na rozmaitych poziomach. Kiedy pewne problemy stają się “proste”… Nowa grupa wyzwań, które na razie można sobie tylko próbować wyobrażać, wytyczy nowe granice możliwości wykorzystania komputerów. Do problemu systematycznej analizy czasu działania programu powrócimy jeszcze na wykładzie w styczniu …