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