Algorytmy i struktury danych - Letnie Warsztaty Matematyczno
Transkrypt
Algorytmy i struktury danych - Letnie Warsztaty Matematyczno
Letnie Warsztaty Matematyczno-Informatyczne Algorytmy i struktury danych Mariusz Różycki University of Cambridge Zajęcia będą mieć formę wykładową. Slajdy można znaleźć na stronie kursu: http://lw.mi.edu.pl/informatyka/algorytmy. Pomocne będzie pobieżne zapoznanie się z nimi przed zajęciami. Wersja zaanotowana będzie umieszczana po każdych zajęciach. Po piątkowych zajęciach opublikowany zostanie test, który pozostanie otwarty do soboty do godziny 18.00. Na osoby, które poradzą sobie z nim najlepiej, czekają drobne nagrody. Ciekawostka: w Anglii nadal można kupić w sklepach gwiazdki Milky Way. Tak tylko mówię. Letnie Warsztaty Matematyczno-Informatyczne Algorytmy i struktury danych Wprowadzenie Wprowadzenie Definicja algorytmu algorytm (ang. algorithm) — w matematyce skończony ciąg jasno zdefiniowanych czynności, koniecznych do wykonania pewnego rodzaju zadań. — Wikipedia Wprowadzenie Algorytmy w codziennym życiu Przykładem algorytmu jest przepis kucharski. Przepis na naleśniki 1. Do miski wrzucić jajka, wsypać mąkę i wlać mleko. 2. Mieszać do uzyskania jednolitej masy. 3. Wlać porcję ciasta na rozgrzaną patelnię. 4. Gdy masa się zetnie, odwrócić naleśnik. 5. Zdjąć naleśnik z patelni. 6. Powtarzać do wyczerpania ciasta. Wprowadzenie Sposoby zapisywania algorytmów – lista kroków – schemat blokowy – język programowania – pseudokod Wprowadzenie Pseudokod Metoda pośrednia między językiem programowania a listą kroków. Posiada zalety obu rozwiązań: – przejrzysty nawet dla skomplikowanych algorytmów – wymaga dokładności przy zapisie – nie wymaga znajomości detali implementacyjnych języków programowania – łatwy do odczytania nawet dla osób nie potrafiących programować Wprowadzenie Pseudokod Przypisanie i wartości: n←5 T ← [1, 2, 3] R ← 1..5 T [1] ← n � Liczba � Lista � Zakres (czyli lista), [1, 2, 3, 4], bez 5 � Teraz T = [1, 5, 3] Blok if: if warunek then zrób coś else zrób coś innego end if � Jeżeli warunek jest spełniony � wykonaj ten fragment kodu. � W przeciwnym razie � wykonaj ten fragment kodu. Wprowadzenie Pseudokod Pętla for all: for all t ∈ T do zrób coś end for Pętla while: while warunek do zrób coś end while � Dla każdego t należącego do T � wykonaj ten fragment kodu. � Dopóki warunek jest spełniony � powtarzaj ten fragment kodu. Wprowadzenie Pseudokod Procedura: procedure Sum(T ) sum ← 0 for all t ∈ T do sum ← sum + t end for return sum end procedure � Procedura Sum przyjmuje T . � Przypisz 0 do sum. � Dla każdego t � zwiększ sum o t. � Zwróć wartość sum. Wprowadzenie Znajdowanie minimum Spróbujmy zapisać przy użyciu pseudokodu jakiś prosty algorytm, jak znajdowanie minimum w ciągu liczb. Proste, prawda? Wystarczy spojrzeć na wszystkie liczby i wybrać najmniejszą. Wprowadzenie Znajdowanie minimum Spróbujmy zapisać przy użyciu pseudokodu jakiś prosty algorytm, jak znajdowanie minimum w ciągu liczb. Proste, prawda? Wystarczy spojrzeć na wszystkie liczby i wybrać najmniejszą. Problem polega na tym, że komputer tak nie potrafi. Ty w sumie też nie, jeżeli liczb jest kilka tysięcy (a nawet milionów). Więc jak to zrobić? Wprowadzenie Znajdowanie minimum Spróbujmy zapisać przy użyciu pseudokodu jakiś prosty algorytm, jak znajdowanie minimum w ciągu liczb. Proste, prawda? Wystarczy spojrzeć na wszystkie liczby i wybrać najmniejszą. Problem polega na tym, że komputer tak nie potrafi. Ty w sumie też nie, jeżeli liczb jest kilka tysięcy (a nawet milionów). Więc jak to zrobić? Musimy spojrzeć na każdą wartość w ciągu po kolei, zapamiętując wartość tej najmniejszej spośród dotychczas napotkanych. Wprowadzenie Znajdowanie minimum procedure Minimum(T ) min ← ∞ � Każda liczba jest mniejsza od ∞. for all t ∈ T do if t < min then � Jeżeli t jest mniejsze od min min ← t � zmień dotychczasowe minimum. end if end for return min � Zwróć najmniejszą napotkaną liczbę. end procedure Wprowadzenie Algorytm Euklidesa Przykładem nieco mniej oczywistego algorytmu jest algorytm Euklidesa. Przy jego użyciu możemy znaleźć najmniejszy wspólny dzielnik dwóch liczb (ang. Greatest Common Divisor, w skrócie GCD). Mając dane dwie liczby odejmujemy od większej mniejszą, aż jedna z nich będzie równa 0. Wtedy druga z liczb będzie wynikiem algorytmu. Wprowadzenie Algorytm Euklidesa procedure GCD(a, b) if a < b then Swap(a, b) end if while b > 0 do c ←a−b a←b b←c end while return a end procedure � Jeżeli a jest mniejsze od b � zamień je miejscami. � Teraz a będzie zawsze większe od b. � Dopóki b nie jest zerem � odejmuj b od a. � Przy okazji zamień je miejscami, � żeby a było nadal większe od b. � b jest zerem, więc a jest wynikiem. procedure GCD(a,b): while b > 0: if a < b then Swap(a,b) end if a <- a-b end while return a end procedure Wprowadzenie Algorytm Euklidesa Algorytm Euklidesa można przyspieszyć, używając dzielenia modulo: procedure Fast-GCD(a, b) while b > 0 do c ← amodb a←b b←c end while return a end procedure Wprowadzenie Zadania 1. Napisz, używając pseudokodu, procedurę Length(T ), która zwróci długość (liczbę elementów) listy przekazanej jako argument. 2. Napisz procedurę Average(T ), która dla danej listy T obliczy i zwróci sumę arytmetyczną jej elementów. Podpowiedź: w jednej pętli możesz policzyć zarówno sumę elementów jak i liczbę elementów listy. Wynikiem procedury będzie wynik ich dzielenia. Wprowadzenie Zadania 3. Jaki wynik zwróci procedura GCD, jeżeli oba jej argumenty będą równe 0? Czy jest to poprawny wynik? 4. Procedura Fast-GCD nie sprawdza, czy a jest większe od b. Dlaczego nie jest to konieczne? Podpowiedź: jeżeli początkowo a < b, jakie będą wartości a i b po jednym przebiegu pętli while? Letnie Warsztaty Matematyczno-Informatyczne Algorytmy i struktury danych Złożoność obliczeniowa Złożoność obliczeniowa Czas wykonania algorytmu GCD(100, 1): 1. a = 100, b = 1 2. a = 99, b = 1 ... 99. a = 1, b = 1 100. a = 1, b = 0 Wynik: 1 Złożoność obliczeniowa Czas wykonania algorytmu GCD(100, 1): 1. a = 100, b = 1 Fast-GCD(100, 1): 2. a = 99, b = 1 ... 1. a = 100, b = 1 99. a = 1, b = 1 100. a = 1, b = 0 Wynik: 1 2. a = 1, b = 1 3. a = 1, b = 0 Wynik: 1 Złożoność obliczeniowa Czas wykonania algorytmu Pomysł: uznać, że każda operacja zajmuje 1 jednostkę czasu. Policzyć ile jednostek otrzymamy. Złożoność obliczeniowa Czas wykonania algorytmu Pomysł: uznać, że każda operacja zajmuje 1 jednostkę czasu. Policzyć ile jednostek otrzymamy. Problem: w praktyce różne operacje wymagają różnej ilości czasu. Złożoność obliczeniowa Czas wykonania algorytmu Pomysł: uznać, że każda operacja zajmuje 1 jednostkę czasu. Policzyć ile jednostek otrzymamy. Problem: w praktyce różne operacje wymagają różnej ilości czasu. Pomysł: przypisać każdej operacji jakąś stałą liczbę jednostek czasu. Złożoność obliczeniowa Czas wykonania algorytmu Pomysł: uznać, że każda operacja zajmuje 1 jednostkę czasu. Policzyć ile jednostek otrzymamy. Problem: w praktyce różne operacje wymagają różnej ilości czasu. Pomysł: przypisać każdej operacji jakąś stałą liczbę jednostek czasu. Problem: czas wykonania pojedynczej operacji zależy od wielu czynników i nie jest stały. Złożoność obliczeniowa Czas wykonania algorytmu Patrzeć będziemy na to, jak czas wykonania zmienia się w zależności od wielkości danych wejściowych. Złożoność obliczeniowa Czas wykonania algorytmu Patrzeć będziemy na to, jak czas wykonania zmienia się w zależności od wielkości danych wejściowych. Na przykład czas wykonania procedury Minimum zmienia się liniowo względem liczby elementów T . To jest: jeżeli zwiększymy liczbę elementów T dwukrotnie, czas wykonania zwiększy się dwukrotnie. Złożoność obliczeniowa Czas wykonania algorytmu Patrzeć będziemy na to, jak czas wykonania zmienia się w zależności od wielkości danych wejściowych. Na przykład czas wykonania procedury Minimum zmienia się liniowo względem liczby elementów T . To jest: jeżeli zwiększymy liczbę elementów T dwukrotnie, czas wykonania zwiększy się dwukrotnie. Czas wykonania procedury GCD zmienia się liniowo względem ilorazowi większej i mniejszej z liczb a i b. Złożoność obliczeniowa Czas wykonania algorytmu Patrzeć będziemy na to, jak czas wykonania zmienia się w zależności od wielkości danych wejściowych. Na przykład czas wykonania procedury Minimum zmienia się liniowo względem liczby elementów T . To jest: jeżeli zwiększymy liczbę elementów T dwukrotnie, czas wykonania zwiększy się dwukrotnie. Czas wykonania procedury GCD zmienia się liniowo względem ilorazowi większej i mniejszej z liczb a i b. Czas wykonania procedury Fast-GCD zmienia się proporcjonalnie do logarytmu większej z liczb a i b. Złożoność obliczeniowa Kres górny i dolny Ponieważ z reguły ciężko jest okreslić dokładną zależność między wielkością danych a czasem wykonania algorytmu, będziemy posługiwać się kresem górnym i dolnym (najczęściej tylko górnym). Brace yourselves, winter maths is coming. Złożoność obliczeniowa Kres górny i dolny Funkcja g jest kresem dolnym funkcji f , jeżeli dla każdego x ∈ Df zachodzi g (x) � f (x). Podobnie funkcja h jest kresem górnym funkcji f , jeżeli dla każdego x ∈ Df zachodzi h(x) � f (x). Oczywiście zarówno g jak i h muszą mieć tę samą dziedzinę i przeciwdziedzinę co f . Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Kresy górny i dolny nie stanowią same w sobie rozwiązania problemu. Jeżeli czas wykonania procedury rośnie liniowo, to jaki jest kres górny? g (x) = 2x? g (x) = 1000x? g (x) = G64 x? Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Kresy górny i dolny nie stanowią same w sobie rozwiązania problemu. Jeżeli czas wykonania procedury rośnie liniowo, to jaki jest kres górny? g (x) = 2x? g (x) = 1000x? g (x) = G64 x? Z pomocą przychodzi notacja wielkiego „O” (i podobne). Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Kresy górny i dolny nie stanowią same w sobie rozwiązania problemu. Jeżeli czas wykonania procedury rośnie liniowo, to jaki jest kres górny? g (x) = 2x? g (x) = 1000x? g (x) = G64 x? Z pomocą przychodzi notacja wielkiego „O” (i podobne). Więcej matematyki! Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Funkcja f należy do O (g (n)), jeżeli istnieje takie K i x0 , że dla każdego x � x0 zachodzi f (x) � K · g (x). Funkcja f należy do Ω (g (n)), jeżeli istnieje takie K i x0 , że dla każdego x � x0 zachodzi f (x) � K · g (x). W praktyce głównie używa się tego pierwszego zapisu. Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Funkcja f należy do O (g (n)), jeżeli istnieje takie K i x0 , że dla każdego x � x0 zachodzi f (x) � K · g (x). Co daje nam notacja wielkiego „O”? 1. Nie musimy martwić się stałymi. Jeżeli funkcja należy do O(2x), to należy też to O(100x) i O(x). Stałe będziemy więc pomijać. 2. Nie musimy przejmować się nieistotnymi składnikami. Jeżeli funkcja należy do O(2x + x 3 ), to należy również do O(2x ). Wolniej rosnące składniki sumy będziemy pomijać. Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Jeżeli więc czas wykonania algorytmu rośnie liniowo względem jakiegoś n, to mówimy, że ma złożoność obliczeniową O(n). Inne możliwości to między innymi O(2n ), O(n2 ), O(n log n), O(n), O(log n), O(1). . . Złożoność obliczeniowa Notacja wielkiego „O” (i podobne) Jeżeli więc czas wykonania algorytmu rośnie liniowo względem jakiegoś n, to mówimy, że ma złożoność obliczeniową O(n). Inne możliwości to między innymi O(2n ), O(n2 ), O(n log n), O(n), O(log n), O(1). . . Zwróć uwagę, że O opisuje kres górny. Zatem jeżeli algorytm ma złożoność O(n), to możemy powiedzieć również, że ma złożoność O(n2 ). W praktyce będziemy starali się znajdować jak najciaśniejszy kres, ponieważ niesie on ze sobą najwięcej informacji.