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 dowolnej liczby nieujemnej. 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ą (im bliżej szukanego pierwiastka, tym lepiej). 2. Znajdź kolejne przybliżenie według wzoru 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 jest przybliżeniem z błedem, to ϵ Rozwijając dwumian i rozwiązując względem otrzymamy (przy założeniu ): x Przybliżenie możemy zatem poprawić o błąd : Zaimplementujmy teraz ten algorytm w Pythonie: S x n+1 S x0 1 S = ( + ) 2 x n S = (x + ϵ) 2 ϵ S x 2 x n ϵ =. 2x + ϵ ϵ S x 2 2x S + x x x + ϵ = 2 = 2x x + S x 2 x ϵ x S
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 i dokładności : S 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. Metoda składa się z następujących kroków: [2, n] 1. Ze zbioru liczb naturalnych z przedziału, tzn. ze zbioru 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: [2, n] {2, 3, 4,, n} In [39]: n = 15 #interesuje nas przedział [2,n] primes = [True]*(n+1) #tablica liczb pierwszych (na początku wszystkie traktu jemy jako pierwsze) #zbudowana w ten sposób, że indeks elementu jest równy rozważanej liczbie for i in range(2,n+1): #zaczynamy od 2 #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 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): for j in range(i+i,n+1,i): primes[j] = False result = [] for i in range(2,n+1): 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/inde.html?page=2 (http://www.macdevcenter.com/pub/a/python/excerpt/pythonckbk_chap1/inde.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 for j in range(i+i,n+1,i): primes[j] = False result = [] for i in range(2,n+1): 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 Bolzano Cauchy'ego: Jeżeli funkcja ciągła f(x) ma na końcach przedziału domkniętego wartości różnych znaków, f(x) = 0 to wewnątrz tego przedziału istnieje co najmniej jeden pierwiastek równania. Aby można było stosować tę metodę, muszą być więc spełnione następujące warunki: f(x) [a, b] 1. Funkcja jest ciągła w przedziale domkniętym. 2. Funkcja przyjmuje różne znaki na końcach przedziału, tzn.: Algorytm składa się z następujących kroków: a+b 1. Sprawdź, czy jest pierwiastkiem równania, tzn. czy. Jeżeli tak, algorytm = 2 kończy działanie, a punkt jest poszukiwanym rozwiązaniem. 2. W przeciwnym razie, dopóki nie osiągniemy pożądanej dokładności, tzn. dopóki : Zgodnie ze wzorem z punktu 1 ponownie wyznacz. Traktując jako punkt podziału przedziału wyjściowego, wybierz podprzedział, w którym leży miejsce zerowe: jeżeli, to, f( )f(a) < 0 f( )f(b) < 0 b = a = jeżeli, to. f(a)f(b) < 0 3. Powtarzaj punkt drugi do osiągnięcia żądanej dokładności f( ) = 0 a b > ϵ 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 wynika mianowicie S = x x 2 S = 0 f(x) = 0 2 czyli otrzymaliśmy postać. Wykorzystajmy zatem bisekcję do znalezienia pierwiastka z : 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
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 przejść przez tablicę, aby w pełni ją posortować. W każdym przejściu dokonuje się porównań, gdzie to numer przejścia. Innymi słowy n k n 1 k 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 bubbledokonuje wszystkich zmian w liście wejściowej! Wrócimy do tej cechy Pythona na kolejnych wykładach: