Zauważmy, szukaj
Transkrypt
Zauważmy, szukaj
Wstęp do programowania Wykład 5 Podstawowe techniki programownia w przykładach Janusz Szwabiński Plan wykładu: Metoda babilońska wyliczania pierwiastka Liczby pierwsze i sito Eratostenesa Metoda bisekcji Sortowanie bąbelkowe Metoda babilońska wyliczania pierwiastka Szukamy aproksymacji √S dowolnej liczby nieujemnej S . Jednym z pierwszych algorytmów jest metoda opisana przez matematyka greckiego Herona z Aleksandrii. Przypuszcza się jednak, że była znana już wcześniej i stosowana w Babilonii. Metoda składa się z następujących kroków: 1. Rozpocznij z dowolną dodatnią wartością początkową x 0 (im bliżej szukanego pierwiastka, tym lepiej). 2. Znajdź kolejne przybliżenie według wzoru 1 x n+1 = 2 S (x n + ) xn 3. Powtarzaj krok 2 do osiągnięcia pożądanej dokładności. Wzór z kroku 2 można dość łatwo wyprowadzić. Zauważmy mianowicie, że jeżeli x jest przybliżeniem √S z błedem ϵ, to 2 S = (x + ϵ) Rozwijając dwumian i rozwiązując względem ϵ otrzymamy (przy założeniu ϵ S − x 2 ϵ = S − x ≃ 2 . 2x + ϵ 2x Przybliżenie x możemy zatem poprawić o błąd ϵ: S + x x → x + ϵ = x + = 2x Zaimplementujmy teraz ten algorytm w Pythonie: 2 2 S x ≪ x ): In [1]: S = 345 eps = 10e-6 #błąd przybliżenia old = 1.0 new = 0.5*(1+S) #nowe przybliżenie dla old=1 while abs(new-old)>eps: old, new = new, 0.5*(new + S/new) print(new) 18.57417562100671 In [2]: import math print(math.sqrt(S)) 18.57417562100671 Powyższa implementacja metody babilońskiej działa, jest jednak mało elegancka. Chcąc policzyć wartość pierwiastka innej liczby, musimy wyedytować kod, zmienić w nim wartość zmiennej S i uruchomić całość raz jeszcze. Dlatego lepiej jest opakować powyższy kod w funkcję: In [3]: def heron_sq(s, eps): old, new = 1.0, 0.5 * (1 + s) while abs(new - old) > eps: old, new = new, 0.5 * (new + s/new) return new Teraz możemy wywoływać go dla dowolnych wartości S i dokładności eps : In [4]: heron_sq(S,eps) Out[4]: 18.57417562100671 In [5]: heron_sq(S,10e-2) Out[5]: 18.57421350446529 In [6]: heron_sq(25,10e-8) Out[6]: 5.0 In [9]: heron_sq(25,10e-2) Out[9]: 5.000023178253949 Liczby pierwsze i sito Eratostenesa Sito Eratostenesa (https://pl.wikipedia.org/wiki/Sito_Eratostenesa (https://pl.wikipedia.org/wiki/Sito_Eratostenesa)) to przypisywany Eratostenesowi z Cyreny algorytm wyznaczania liczb pierwszych z przedziału [2, n] . Metoda składa się z następujących kroków: 1. Ze zbioru liczb naturalnych z przedziału [2, n] , tzn. ze zbioru {2, 3, 4, … , n} wybieramy najmniejszą, czyli 2, i wykreślamy wszystkie jej wielokrotności większe od niej samej, to jest 4, 6, 8, … . 2. Z pozostałych liczb wybieramy najmniejszą niewykreśloną, czyli 3 i wykreślamy wszystkie jej wielokrotności. 3. Bierzemy kolejną najmniejszą liczbą niewykreśloną i usuwamy jej wielokrotności. 4. Powtarzamy krok 3 dla pozostałych liczb niewykreślonych. Najprostsza implementacja w Pythonie mogłaby wyglądać tak: In [39]: n = 15 primes = [True]*(n+1) jemy jako pierwsze) #interesuje nas przedział [2,n] #tablica liczb pierwszych (na początku wszystkie traktu #zbudowana w ten sposób, że indeks elementu jest równy rozważanej liczbie for i in range(2,n+1): #zaczynamy od 2 if primes[i]: #True oznacza liczbę niewykreśloną for j in range(i+i,n+1,i): #pętla usuwająca wieloktrotności primes[j] = False #zaznaczamy usunięcie for i in range(2,n+1): ne if primes[i]: print(i) #wyświetlamy na ekran wszystko, co nie zostało wykreślo 2 3 5 7 11 13 Podobnie, jak w poprzednim przykładzie, możemy zdefiniować własną funkcję: In [41]: def erat(n): primes = [True]*(n+1) for i in range(2,n+1): if primes[i]: for j in range(i+i,n+1,i): primes[j] = False result = [] for i in range(2,n+1): if primes[i]: result.append(i) return result In [42]: erat(3) Out[42]: [2, 3] In [43]: erat(10) Out[43]: [2, 3, 5, 7] In [44]: erat(20) Out[44]: [2, 3, 5, 7, 11, 13, 17, 19] Nie jest to oczywiście najbardziej wydajna implemetacja (przegląd takich można znaleźć pod adresem http://www.macdevcenter.com/pub/a/python/excerpt/pythonckbk_chap1/index1.html?page=2 (http://www.macdevcenter.com/pub/a/python/excerpt/pythonckbk_chap1/index1.html?page=2)). Możemy ją jednak usprawnić niewielkim nakładem sił. Przeglądanie tablicy można mianowicie przerwać już dla i = √n : In [45]: import math def erat2(n): imax = int(math.sqrt(n))+1 primes = [True]*(n+1) for i in range(2,imax+1): #tu nastąpiła zmiana if primes[i]: for j in range(i+i,n+1,i): primes[j] = False result = [] for i in range(2,n+1): if primes[i]: result.append(i) return result In [46]: erat2(20) Out[46]: [2, 3, 5, 7, 11, 13, 17, 19] In [47]: erat2(4) Out[47]: [2, 3] Metoda bisekcji Metoda bisekcji (połowienia przedziału, https://pl.wikipedia.org/wiki/Metoda_r%C3%B3wnego_podzia%C5%82u (https://pl.wikipedia.org/wiki/Metoda_r%C3%B3wnego_podzia%C5%82u)) to jedna z metod rozwiązywania równań nieliniowych, opierająca się na twierdzeniu BolzanoCauchy'ego: Jeżeli funkcja ciągła f (x) ma na końcach przedziału domkniętego wartości różnych znaków, to wewnątrz tego przedziału istnieje co najmniej jeden pierwiastek równania f (x) = 0. Aby można było stosować tę metodę, muszą być więc spełnione następujące warunki: 1. Funkcja f (x) jest ciągła w przedziale domkniętym [a, b]. 2. Funkcja przyjmuje różne znaki na końcach przedziału, tzn.: f (a)f (b) < 0 Algorytm składa się z następujących kroków: 1. Sprawdź, czy x 1 = a+b 2 jest pierwiastkiem równania, tzn. czy f (x 1 ) = 0 . Jeżeli tak, algorytm kończy działanie, a punkt x 1 jest poszukiwanym rozwiązaniem. 2. W przeciwnym razie, dopóki nie osiągniemy pożądanej dokładności, tzn. dopóki |a − b| > ϵ: Zgodnie ze wzorem z punktu 1 ponownie wyznacz x 1 . Traktując x 1 jako punkt podziału przedziału wyjściowego, wybierz podprzedział, w którym leży miejsce zerowe: jeżeli f (x 1 )f (a) < 0, to b = x 1, jeżeli f (x 1 )f (b) < 0, to a = x 1 . 3. Powtarzaj punkt drugi do osiągnięcia żądanej dokładności Prosta implementacja tego algorytmu w Pythonie może wyglądać tak: In [68]: def bisec(fun,a,b,eps): low, high = a, b feps = 10e-16 #dokładność, z jaką przyrównamy f(x) do 0 if fun(low)*fun(high) > 0: print("Podano błędny przedział!") return while abs(low-high)>eps: midpoint = (low + high)/2 if abs(fun(midpoint)) < feps : #sprawdź, czy to rozwiązanie return midpoint if fun(low)*fun(midpoint)>0: low = midpoint else: high = midpoint return midpoint Zauważmy, że z metody tej możemy skorzystać przy szukaniu pierwiastka z liczby nieujemnej. Z faktu, że √S = x wynika mianowicie x czyli otrzymaliśmy postać f (x) In [69]: def f(x): return x**2 -2 In [72]: bisec(f,1,2,10e-6) Out[72]: 1.4142074584960938 In [71]: math.sqrt(2) Out[71]: 1.4142135623730951 2 − S = 0 = 0 . Wykorzystajmy zatem bisekcję do znalezienia pierwiastka z 2: In [73]: bisec(f,0,1,10e-6) Podano błędny przedział! Sortowanie bąbelkowe Sortowanie bąbelkowe to prosta metoda sortowania tablic, polegająca na porównywaniu dwóch kolejnych elementów w tablicy i zamianie ich kolejności, jeżeli zaburza ona porządek sortowania. Sortowanie kończy się, gdy podczas kolejnego przejścia nie dokonano żadnej zmiany. Niezależnie od uporządkowania elementów potrzeba n − 1 przejść przez tablicę, aby w pełni ją posortować. W każdym przejściu dokonuje się n − k porównań, gdzie k to numer przejścia. Innymi słowy In [78]: def bubble(alist): n = len(alist) #liczba elementów do posortowania for i in range(n-1): #liczba przejść for j in range(i+1, n): if alist[j] < alist[i]: alist[j], alist[i] = alist[i], alist[j] In [79]: alist = [54,26,93,17,77,31,44,55,20] bubble(alist) print(alist) [17, 20, 26, 31, 44, 54, 55, 77, 93] Zwróćmy przy okazji uwagę, że funkcja bubble dokonuje wszystkich zmian w liście wejściowej! Wrócimy do tej cechy Pythona na kolejnych wykładach: