Zapiszemy teraz algorytm Euklidesa w postaci pseudokodu
Transkrypt
Zapiszemy teraz algorytm Euklidesa w postaci pseudokodu
Zapiszemy teraz algorytm Euklidesa w postaci pseudokodu. Często instrukcje wyrażane za pomocą słów nie przenoszą się bezpośrednio na pseudokod i w drugą stronę podobnie. Np. problem jest z pętlą. Zanim zaczynamy coś zamieniać na pseudokod to trzeba przeczytać i przeanalizować napisany słowami algorytm i zastanowić się, gdzie i kiedy musimy zawracać. Przypomnijmy jak był napisany ten algorytm słowami: Algorytm Euklidesa 1. Weźmy dwie liczby całkowite dodatnie: a i b. (wejście do algorytmu, czyli jasno widzimy od czego zaczynamy) 2. Jeśli b = 0 idź do 3., w przeciwnym razie wykonaj: 2.1. Jeśli a > b to a := a - b. 2.2. W przeciwnym razie b := b - a. 2.3. Przejdź do 2. 3. a jest szukanym największym dzielnikiem. 4. Koniec (wyraźnie zaznaczony koniec algorytmu) A teraz w pseudokodzie: function NWD(a,b) -- i teraz nie możemy tak sobie spokojnie przez to nawracanie zrobić if, tylko pętlę. Myślimy którą z pętli możemy tutaj zastosować i skoro warunek jest sprawdzany na początku, to odpada pętla do while i odpada for, bo nie wiemy ile razy będzie trzeba powtarzać nawracanie, bo ono jest zależne od względnej wielości liczb a i b. PAMIĘTAĆ O WCIĘCIACH while (b!0) begin if (a>b) then begin a:=a-b; end else begin b:=b-a; end end wypisz (a); end Iteracja Iteracja (łac. iteratio) to czynność powtarzania (najczęściej wielokrotnego) tej samej instrukcji (albo wielu instrukcji) w pętli. Przykład iteracji to to w algorytmie powyżej. Rekurencja Rekurencja (ang. recursion, z łac. recurrere, przybiec z powrotem) to w logice, programowaniu i w matematyce odwoływanie się np. funkcji lub definicji do samej siebie. Przykładem jest definicja silni, czyli w definicji pojęciami używamy pojęcia tego samego pojęcia. Podobnie z fraktalami, kalafiorem (takim jak jemy) itp. Chodzi o to, że kalafior jest obiektem rekurencyjnym, bo jak odłamiemy kawałek kalafiora to ten kawałek będzie pomniejszonym takim dużym kalafiorkiem. Czyli obiekt jest definiowany przez samego siebie. Silnia iteracyjnie n! = 1 * 2 * 3 * ... * n – widać, że to definicja w pętli Silnia rekurencyjnie n! = n * (n-1)! 1 Ta rekurencja się kiedyś zakończy dlatego, bo mamy warunek końcowy w postaci , żę gdy n=0 lub n=1 to n!=1 Silnia – UZUPEŁNIĆ TEN SLAJD BO BĘDZIE ON ZMIENIONY O DEFINICJĘ SilniaI(n) begin i:=0; s:=1; while (i<n) do begin i:=i+1; s:=s*i; end return s; end SilniaR(n) Begin if (n=0) then begin return 1; end else begin return n*SilniaR(n-1); end end Czyli łatwiej jest przenieść rekurencję na bezpośredni kod, a iterację trudniej. Rekurencja jednak ma też swoje wady, co widać poniżej na tym drzewie, bo zanim się policzy pierwszą linijkę, to trzeba policzyć te wszystkie inne rzeczy co są na prawo, program musi pamiętać, że zanim nam da odpowiedz na nasze pytanie to musi nam odpowiedzieć na te inne pytania i odpowiedzi na te pytania pośrednie jest zapamiętywana zwykle w pewnym stosie i często zdarza się zapełnienie owego stosu przy źle napisanej rekurencji. {Przecież u nas w programie będzie tylko pierwsze wywołanie, tylko ta pierwsza linijka, a naprawdę program wywołuje tą funkcję 5 razy by nam dać wynik. Zatem poniższe drzewo ma sens tylko przy małej ilości wywołań. Drzewo wywołań rekurencyjnych podczas obliczania wartości 4! 5*SilniaR(4) . | . 4*SilniaR(3) . . | . . 3*SilniaR(2) . . . | . . . 2*SilniaR(1) . . . . | . . . . 1*SilniaR(0) . . . . . | . . . . . < -------1 . . . . . | . . . . < -------1 * 1 (bo to już z definicji wiem i idę definicją dalej mając te wartości) . . . . | . . . < ------- 2 * 1 (zwracamy kolejne wyniki pośrednie by otrzymać wynik końcowy) . . . | . . < -------3*2 . .| <--------- 4*6 | 24 2 Teraz zaraz się przekonamy, że rekurencja nie zawsze jest dobrym pomysłem. Definicja ciagu Fibonacciego Dla n > 1 mamy fibn = fibn-1 + fibn-2 natomiast wyrazy 1. i 0. przyjmują wartość 1. Czyli wyglądają te wyrazy ciągu następująco: 1,1,2,3,5,8,13,21… Fibonacci rekurencyjnie – próba napisana mniej więcej w pseudokodzie Próba przeniesienia tego bezpośrednio z definicji. A problem polega na czasie, bo tak napisany ten program wykonuje się KOSMICZNIE długo. Mianowicie dla n=10 otrzymujemy niemalże wynik natychmiast. Dla n=20 zajmie komputerowi wyświetlenie wyniku już 0,02 sekundy Dla n=30 będzie to już jednak aż 3,78 sekundy, a aby policzyć 40wyraz ciągu fibonacciego rekurencyjnie potrzeba już będzie 454.27 sekund. Czyli pamiętajmy, ze rekurencja jest naprawdę nie za dobrym rozwiązaniem czasami. Dla wyrazu 10 będzie aż 177 wywołań tej funkcji. Dla wyrazu 20 nim policzymy wyraz dwudziesty, to będzie aż 21891 wywołań! A dla wyrazu 30 będzie to już prawie 2,7 miliona i pamiętajmy, ze te wszystkie wywołania są zapisywane w pamięci. To że tych wywołań jest tak wiele wynika ze specyfiki wywołania rekurencyjnego. Dla silni było ich sporo, ale nie aż tak wiele. Tutaj wywołań jest tak wiele, bo jak chcemy policzyć wyraz drugi, to najpierw musimy policzyć wyraz pierwszy i wyraz zerowy. A jak chcemy policzyć wyraz trzeci to potrzebujemy wyraz drugi z jednej strony a z drugiej to wszystko co było potrzebne dla wyrazu drugiego. I on to dla drugiego wyrazu liczy jakby dwa razy. Tutaj rekurencje zabija to, że liczymy wielokrotnie te same wartości. Przyrost ilość wywołań to mniej więcej 2/3. Bo by wyliczyć wyraz któryś, to musimy obliczyć poprzedni i 2/3 z tego ,co już liczyliśmy wcześniej. FibR(n) begin if ( n=0 or n=1) then begin return 1; end return FibR(n-1)+FibR(n-2); (ten fragment odzwierciedla definicję) end 3 Drzewo wywołań rekurencyjnych podczas obliczania 5. wyrazu ciągu Fibonacciego FibR(5) | +--FibR(4) | | | +--FibR(3) | | | | | +--FibR(2) | | | | | | | +--FibR(1) | | | +--FibR(0) | | | | | +--Fib(1) | | | +--FibR(2) | | | +--FibR(1) | +--FibR(0) +--FibR(3) | +--FibR(2) ... Liczba wywołań liczona przez Pana na kartce, to co było napisane w tekście na poprzedniej stronie. To nie jest jakoś bardzo istotne. 0 1 2 3 4 5 6 7 8 9 10 15 20 25 30 1 1 3 5 9 15 25 41 67 109 177 1973 21891 242785 2692537 Fibonacci iteracyjnie działa błyskawicznie nawet dla bardzo dużych wyrazów. To jest przewaga iteracji, ona zawsze jest bardzo szybka, ale z drugiej strony kod który tu widzimy nie wygląda za bardzo podobnie do definicji, którą mieliśmy na samym początku. Zatem zawsze jak możemy pisać rekurencję, to musimy się zastanowić, czy to ma sens, bo napisany algorytm może być elegancki, ale niekoniecznie działający w tym stuleciu. 4 FibI(n) begin i:=1; x:=1; y:=1; while (i<n) begin z:=x; i:=i+1; x:=x+y; y:=z; end return x; end Liczba Liczba jest pewnym abstrakcyjnym bytem (czyli nie istnieje sama w sobie, istnieje liczba2, liczba3, ale nie istnieje liczba jako taka podobnie jak nie istnieje samochód, bo istnieje samochód Janka, syrenka Cioci, ale samochód sam jako taki nie istnieje) wykorzystywanym do zliczania i mierzenia. Symbol lub słowo języka naturalnego wyrażające liczbę nazywamy numerałem lub cyfrą (ang. numeral, digit). (Choć termin cyfra zasadniczo zarezerwowany jest dla pojedynczego symbolu to jednak np. język angielski zdaje się nie rozróżniać tych dwóch terminów.) Cyfry różnią się od liczb tak jak słowa różnią się od rzeczy, które określają. Symbole: „11”, „ jedynascie” oraz „XI” sa różnymi numerałami reprezentującymi ta sama liczbę. Czyli są to zupełnie różne symbole, ale określają to samo. Liczby nie można zobaczyć, my widzimy tylko to, co ją określa. Czyli my uważamy, że 121 to numerał, który składa się z cyfr. W potocznym znaczeniu słowo liczba używane jest zarówno w pierwotnym znaczeniu abstrakcyjnego bytu wyrażającego ilość i wielkość jak i symbolu. Oto bowiem wyrażenia numeryczne (a wiec złożone z cyfr) używane są jako pewnego rodzaju nazwy (np. numer telefonu), w celu uporządkowania (np. numer seryjny)czy tez jako kod (np. ISBN). System liczbowy System (numerałów) interpretować przedstawienie systemie. liczbowy jest sposobem reprezentacji w jednolity sposób. W zależności od będziemy jako dwójkowe przedstawienie liczby jedynascie lub być może jeszcze inna liczb przy użyciu cyfr kontekstu numerał „11” liczby trzy, dziesiętne liczbę zapisana w innym Unarny system liczbowy Najprostszym systemem liczbowym jest unarny system liczbowy, w którym każda liczba naturalna reprezentowana jest przy pomocy jednego znaku powielonego tyle razy ile wynosi liczba reprezentowana przez tworzony numerał. Jeśli wybranym symbolem będzie „/”, wówczas liczbę siedem zapiszemy jako siedmiokrotne powtórzenie tego znaku, czyli ///////. Wbrew pozorom system ten wciąż funkcjonuje u ludów pierwotnych a i cywilizacje bardziej rozwinięte wykorzystują go do zapisu niewielkich liczb. Systemy tego typu nazywamy także systemami addytywnymi, bowiem wartość liczby otrzymujemy poprzez dodawanie kolejnych wartości wyrażanych przez symbole (w tym przypadku jeden symbol). 5 Skoro jest tak trudno używać systemu unarnego do zapisywania dużych liczb, to dlatego wprowadzono pewne uproszczenia typu / oznacza 1 - oznacza 10 + oznacza 100 by zamiast stu / pisać jednego + Wtedy np. 304 to +++//// Oczywiście liczby rzymskie to też przykład systemu unarnego, choć tam czasami I to 1 a czasami I to minus jeden w zależności od miejsca w którym występuje. Liczenie tutaj jest pewnym problemem, ale interesujące jest to, że w tych systemach w ogóle nie ma potrzeby używania abstrakcyjnego symbolu zera, co widać na powyższym przykładzie 304. Zero się pojawia dopiero w systemach pozycyjnych. Zauważmy, że słowa opisują bardziej ten system unarny a nie pozycyjny, jak mówimy, to mówimy „trzysta cztery” a nie „trzysta zero cztery” ani nic takiego. Obecnie mamy systemy pozycyjne powstałe mniej więcej dopiero w V w naszej ery przez ludy pochodzenia Indoarabskeigo. Dwie postacie zasługują tutaj na szczególna uwagę: Aryabhatta Kusumapura żyjący w V w. wprowadził zapis pozycyjny natomiast Brahmagupta wiek później wprowadził symbol zero. No to teraz zastanowimy się nad systemami pozycyjnymi jako takimi, co one oznaczają itp. Dla nas 111 oznacza sto jedenaście dlatego, bo pierwsza jedynka oznacza 100, druga jako 10 a ostatnia oznacza 1 i z powodów skrótowych przyjęło się do niej podchodzić jako do stojedenastki. 375 = 3*100+7*10+5 = 3*10^2+7*10^1+5*10^0 Czyli u nas w 111 ten sam symbol ma inne znaczenie, znaczenie symbolu zależy od pozycji, a w rzymskim I I I ma zawsze znak I takie samo znaczenie, no prawie takie samo, bo zależy od otoczenia czy to jest 1 czy -1, a z kolei w dziesiętnym wygląd symboli otaczających nasz symbol znaczenia nie ma. W systemie dziesiętnym zawsze podnosimy do pewnej potęgi tą samą podstawę nazywaną podstawą systemu. W systemach addytywnych do wyrażenia dowolnie dużej liczby potrzeba nieskończenie wielu symboli, bo by napisać liczbę 1000 to możemy 1000 razy powtórzyć symbol oznaczający 1, albo sobie stworzyć nowy symbol na liczbę 1000, analogicznie z liczbą 10000 itp., a w systemie pozycyjnym np. dziesiętnym potrzebujemy tylko 10 symboli i za ich pomocą możemy zapisać KAŻDĄ liczbę. Pozycyjnym systemem liczbowym nazywamy pewną parę (q,C), gdzie q jest liczbą naturalną, która jest podstawą danego systemu, a C to skończony zbiór symboli nazywanych właśnie cyframi pozwalający nam wyrazić wszelkie wielkości. Zwykle przyjmuje się, że C={0,…,q-1}. Używanie pozycyjnych systemów liczbowych pociąga za sobą pewne niejednoznaczności, bo 11 to albo 11 albo 3 jeśli to system trójkowy albo jakaś inna liczba w innym systemie. Dlatego zawsze warto przy liczbie pisać podstawę systemu jakim się posługujemy. W systemie dwójkowym pamiętajmy, że podstawą jest 2, czyli q=2 i C={0,1} Używając jednocześnie kilku różnych pozycyjnych systemów liczbowych, zawsze musimy zaznaczyć w jakim z nich dana liczba jest zapisana. Przyjrzyjmy sie przykładom: 1110 - liczba o dziesiętnej wartości 11 zapisana z pozycyjnym systemie liczbowym o podstawie 10, 112 - liczba o dziesiętnej wartości 3 zapisana z pozycyjnym systemie liczbowym o podstawie 2, 115 - liczba o dziesiętnej wartości 6 zapisana z pozycyjnym systemie liczbowym o podstawie 5, 1125 - liczba o dziesiętnej wartości 26 zapisana z pozycyjnym systemie liczbowym o podstawie 25. 6 Założenie Mówiąc liczba rzeczywista mamy na myśli bezznakową liczbę rzeczywista złożona z części całkowitej i ułamkowej. Liczby rzeczywiste w systemie dziesiętnym Najpierw spróbujmy zrozumieć dziesiętna reprezentacje liczby rzeczywistej. Dziesiętna reprezentacja liczby rzeczywistej r jest wyrażeniem postaci i ai gdzie a0 jest nieujemna liczba całkowita, a1, a2,. . . - liczby całkowite i 0 10 spełniające nierówności 0 <= ai <= 9. Powyższe zapisujemy zwykle jako r = a0*a1*a2*a3*… a0 stanowi cześć całkowita liczby r , natomiast a1, a2, a3,. . . sa cyframi tworzącymi cześć ułamkowa liczby r . Arytmetyka w systemie dwójkowym. Przeczytać jak coś z książki dodawanie, odejmowanie i mnożenie. Dzieleniem nie będzie nas nikt męczył ;) Była też na wykładzie zamiana liczb rzeczywistych z dwójkowego na dziesiętny i w drugą stronę, ale nadal to tylko dodatnie, więc nie ma większych problemów ;) Sposób zapisu liczb rzeczywistych NIE w systemie komputerowym Pod pojęciem liczba rzeczywista rozumiemy taką, która ma część całkowitą i ułamkową, tyle że pomijamy jej znak. Wiemy, że każdą liczbę rzeczywistą możemy zapisać jako An10n+ An-110n-1+…+ A1101+ A0+ A-110-1+ A-210-2+… Analogicznie możemy podejść do liczb w systemie dwójkowym np. zapiszmy 11.011=1*21+1*20+0*2-1+1*2-2+1*2-3 = 3.375 Przypomnimy raz jeszcze ze żaden komputer nie wykorzystuje tego zapisu, który my tutaj stosujemy! Zazwyczaj jak mamy powiedzieć ile wynosi liczba zapisana w systemie np. piątkowym w systemie powiedzmy dziesiątkowym, to nie umiemy inaczej jak tylko zamienić najpierw na system dziesiętny i potem zamieniać dopiero z powrotem z niego. Troszkę inaczej jest, gdy mamy zamieniać systemy, których podstawy są wielokrotnością dwójki. By się udało to wypisujemy sobie System 10 System 2 System 16 0 00000 0 1 00001 1 2 00010 2 3 00011 3 4 00100 4 5 00101 5 6 00110 6 7 00111 7 8 01000 8 9 01001 9 10 01010 A 11 01011 B 12 01100 C 13 01101 D 14 01110 E 15 01111 F 7 Jak chcemy mieć po kolei te rzeczy to zaczynamy od ostatniej kolumny w systemie dwójkowym. Najpierw wypisujemy na ostatniej kolumnie na zmianę 01010itd potem w drugiej kolumnie piszemy 00110011 i zauważmy ze się tak zgadza, dalej 00001111 itp. Jak mam odczytać np. ile w systemie 16 to jest liczba zapisana w systemie dwójkowym to dzielę ją od lewej na paczuszki po 4 np. i odczytujemy z powyższej tabelki. Analogicznie w drugą stronę. 10 1101 1011 1010 1110 2 D B A E A w systemie ósemkowym dzielimy po 3 bity i znów odczytujemy 101 101 101 110 101 110 5 5 5 6 5 6 A po dwa jeśli ma to być liczba systemy czwórkowego. 8