Wieczorowe Studia Licencjackie Wrocław, Wykład nr 6 (w oparciu o notatki K. Lorysia, z modyfikacjami) Sito Eratostenesa

Podobne dokumenty
Rekurencja (rekursja)

Algorytmy i struktury danych. Wykład 4

Rekurencja. Przykład. Rozważmy ciąg

6. Pętle while. Przykłady

FUNKCJA REKURENCYJNA. function s(n:integer):integer; begin if (n>1) then s:=n*s(n-1); else s:=1; end;

Algorytmika i programowanie. Wykład 2 inż. Barbara Fryc Wyższa Szkoła Informatyki i Zarządzania w Rzeszowie

ALGORYTMY I STRUKTURY DANYCH

Podstawy programowania 2. Temat: Funkcje i procedury rekurencyjne. Przygotował: mgr inż. Tomasz Michno

Wstęp do programowania INP001213Wcl rok akademicki 2018/19 semestr zimowy. Wykład 5. Karol Tarnowski A-1 p.

Algorytm. a programowanie -

ZADANIE 1. Ważenie (14 pkt)

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

do instrukcja while (wyrażenie);

Podstawy programowania. Wykład: 4. Instrukcje sterujące, operatory. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Opis zagadnieo 1-3. Iteracja, rekurencja i ich realizacja

Konstrukcje warunkowe Pętle

Programowanie - wykład 4

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

Język C++ zajęcia nr 2

WHILE (wyrażenie) instrukcja;

2.8. Algorytmy, schematy, programy

7. Pętle for. Przykłady

DYNAMICZNE PRZYDZIELANIE PAMIECI

Podstawy programowania skrót z wykładów:

ZASADY PROGRAMOWANIA KOMPUTERÓW

Zaprojektować i zaimplementować algorytm realizujący następujące zadanie.

Wskaźniki w C. Anna Gogolińska

WHILE (wyrażenie) instrukcja;

PODSTAWY INFORMATYKI 1 PRACOWNIA NR 6

Pętle. Dodał Administrator niedziela, 14 marzec :27

Programowanie komputerowe. Zajęcia 2

Programowanie komputerowe. Zajęcia 3

Poprawność semantyczna

Programowanie strukturalne i obiektowe. Funkcje

Programowanie komputerowe. Zajęcia 4

Podstawy programowania. Wykład: 13. Rekurencja. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Funkcje i tablice. Elwira Wachowicz. 23 maja 2013

Algorytmika i pseudoprogramowanie

Obliczenia na stosie. Wykład 9. Obliczenia na stosie. J. Cichoń, P. Kobylański Wstęp do Informatyki i Programowania 266 / 303

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Pętle i tablice. Spotkanie 3. Pętle: for, while, do while. Tablice. Przykłady

Zadanie 1. Test (6 pkt) Zaznacz znakiem X w odpowiedniej kolumnie P lub F, która odpowiedź jest prawdziwa, a która fałszywa.

EGZAMIN MATURALNY Z INFORMATYKI. 10 maja 2017 POZIOM ROZSZERZONY. Godzina rozpoczęcia: 14:00 CZĘŚĆ I

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Podstawy informatyki. Informatyka stosowana - studia niestacjonarne. Grzegorz Smyk. Wydział Inżynierii Metali i Informatyki Przemysłowej

Informacje wstępne #include <nazwa> - derektywa procesora umożliwiająca włączenie do programu pliku o podanej nazwie. Typy danych: char, signed char

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Paradygmaty programowania

Indukcja. Materiały pomocnicze do wykładu. wykładowca: dr Magdalena Kacprzak

Wstęp do programowania INP001213Wcl rok akademicki 2018/19 semestr zimowy. Wykład 4. Karol Tarnowski A-1 p.

4. Funkcje. Przykłady

Wykład 4. Określimy teraz pewną ważną klasę pierścieni.

Wykład II Tablice (wstęp) Przykłady algorytmów Wstęp do języka C/C++

for (inicjacja_warunkow_poczatkowych; wyrazenie_warunkowe; wyrazenie_zwiekszajace) { blok instrukcji; }

Programowanie i techniki algorytmiczne

Podstawy Programowania C++

1. Nagłówek funkcji: int funkcja(void); wskazuje na to, że ta funkcja. 2. Schemat blokowy przedstawia algorytm obliczania

Algorytm Euklidesa. Największy wspólny dzielnik dla danych dwóch liczb całkowitych to największa liczba naturalna dzieląca każdą z nich bez reszty.

a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] a[10]

- - Ocena wykonaniu zad3. Brak zad3

Uwaga: Funkcja zamień(a[j],a[j+s]) zamienia miejscami wartości A[j] oraz A[j+s].

Pętla for. Wynik działania programu:

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Programowanie w języku C++ Grażyna Koba

5. Rekurencja. Przykłady

Logiczny model komputera i działanie procesora. Część 1.

Wymiar musi być wyrażeniem stałym typu całkowitego, tzn. takim, które może obliczyć kompilator. Przykłady:

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Wstęp do informatyki- wykład 12 Funkcje (przekazywanie parametrów przez wartość i zmienną)

Wstęp do Informatyki zadania ze złożoności obliczeniowej z rozwiązaniami

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 26 marca kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 40

Rekurencja. Przygotowała: Agnieszka Reiter

Rozwiązanie. #include <cstdlib> #include <iostream> using namespace std;

INFORMATYKA Z MERMIDONEM. Programowanie. Moduł 5 / Notatki

Instrukcje sterujące

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Podstawowe algorytmy i ich implementacje w C. Wykład 9

Ćwiczenie nr 3. Wyświetlanie i wczytywanie danych

r. Tablice podstawowe operacje na tablicach

3. Instrukcje warunkowe

EGZAMIN MATURALNY Z INFORMATYKI

Klasa 2 INFORMATYKA. dla szkół ponadgimnazjalnych zakres rozszerzony. Założone osiągnięcia ucznia wymagania edukacyjne na. poszczególne oceny

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

Zapisywanie algorytmów w języku programowania

for (inicjacja_warunkow_poczatkowych(końcowych); wyrazenie_warunkowe; wyrazenie_zwiekszajace(zmniejszające)) { blok instrukcji; }

Ćwiczenie 3 z Podstaw programowania. Język C++, programy pisane w nieobiektowym stylu programowania. Zofia Kruczkiewicz

Złożoność obliczeniowa zadania, zestaw 2

Zaawansowane algorytmy i struktury danych

WYKŁAD 8. Funkcje i algorytmy rekurencyjne Proste przykłady. Programy: c3_1.c..., c3_6.c. Tomasz Zieliński

Programowanie w C++ Wykład 4. Katarzyna Grzelak. 19 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 37

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Wstęp do programowania

Największy wspólny dzielnik Algorytm Euklidesa (także rozszerzony) WZAiP1: Chińskie twierdzenie o resztach

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Wstęp do programowania

2. Tablice. Tablice jednowymiarowe - wektory. Algorytmy i Struktury Danych

Podstawy programowania w języku C++

Temat 20. Techniki algorytmiczne

Transkrypt:

Wieczorowe Studia Licencjackie Wrocław, 7.11.2006 Wstęp do programowania Wykład nr 6 (w oparciu o notatki K. Lorysia, z modyfikacjami) Sito Eratostenesa Zaprezentujemy teraz algorytm na wyznaczanie wszystkich liczb z przedziału [2,n] za pomocą metody zwanej sitem Eratostenesa. W algorytmie tym skorzystamy z tablicy rozmiaru n, i-ta pozycja tej tablicy reprezentować będzie informację o tym, czy liczba i jest liczbą pierwszą. Zauważmy, że każda liczba złożona z zakresu [2,n] musi się dzielić przez mniejszą od siebie liczbę. W oparciu o tę metodę konstruujemy następujący algorytm: - Na początku przyjmujemy, że każda z liczb z zakresu [2,n] jest liczbą pierwszą. W praktyce oznacza to, że wartość każdej komórki tablicy a[0..n] ustawiamy na zero. - Następnie, dla i=2,3,4,...,n-1 zaznaczamy wszystkie liczby dzielące się przez i oraz nie większe od n. A zatem wstawiamy wartość 1 w komórkach a[2i], a[3i], a[4i], itd. aż do a[ki], gdzie (k+1)i jest najmniejszą wielokrotnością i, która jest większa od n. - Po zakończeniu działania powyższej pętli spełniony jest warunek: i jest liczbą pierwszą wtedy i tylko wtedy gdy a[i]=0 (dla każdego i z przedziału [2,n]). { int li[limit], i, j, k, l; for (i=0; i<limit; li[i++]=yes) finish = limit; for (j=2; j<limit; j++) for(k=j+j; k<limit; k=k+j) li[k]=no; Wykorzystując spostrzeżenie: dzielnik liczby n jest nie większy niż n/2, możemy zmienić: finish = limit; na finish = limit / 2; Wykorzystując spostrzeżenie: jeśli j jest liczbą pierwszą to j 2 jest najmniejszą jej wielokrotnością, która nie jest wielokrotnością żadnej liczby pierwszej mniejszej od j, możemy zmienić finish = limit; na finish = sqrt(limit);

Na koniec dokonamy jeszcze ważnej obserwacji: - jeśli liczba n jest wielokrotnością liczby k, to n jest również wielokrotnością każdego pierwszego dzielnika liczby k. Przykład: Skoro 30 jest wielokrotnością 10, to jest wielokrotnością 2 oraz 5. A zatem, wystarczy zaznaczać wielokrotności liczb pierwszych. { int li[limit], i, j, k, l; for (i=0; i<limit; li[i++]=yes) finish = sqrt(limit); for (j=2; j<limit; j++) if (li[j]=yes) for(k=j+j; k<limit; k=k+j) li[k]=no;

Proste programy rekurencyjne Rozważymy kilka prostych problemów, dla których rekurencja stanowi naturalne rozwiązanie. Rozwiązania te poprzedzimy przedstawieniem nierekurencyjnych rozwiązań, dzięki czemu będzie można lepiej docenić moc mechanizmu rekursji. Potęgowanie Zależność między kolejnymi potęgami liczby a można zapisać a n a 1 = n 1 a n = 0 n > 0 O zależności takiej mówimy, że jest zależnością rekurencyjną, ponieważ wyznaczenie wartości funkcji dla większych argumentów możemy sprowadzić (w prosty sposób) do wyznaczania wartości tej samej funkcji dla argumentów mniejszych. W oparciu o taką zależność możemy skonstruować rekurencyjną funkcję języka C, która wyznacza wartość a n. Przez funkcję rekurencyjną rozumiemy tutaj taką funkcję, w której treści występuje odwołanie do niej samej. int potega(int a, int n) { int i, wyn; wyn = 1; for(i=0; i<n; i++) wyn = wyn * a; return wyn; {int n, a, wynik; cin >> a >> n; wynik = potega(a, n); cout << wynik; int potega(int a, int n) { if (n==0) return 1; return a * potega(a, n-1); {int n, a, wynik; cin >> a >> n; wynik = potega(a, n); cout << wynik;

Algorytm Euklidesa Konstruując wcześniej program wyznaczający największy wspólny dzielnik liczb n i m oparty na metodzie Euklidesa, korzystaliśmy z następującej zależności: n nwd( n, m) = nwd( m, n mod m) nwd( m, n) m = 0 n > m n m W oparciu o taką zależność możemy skonstruować rekurencyjną funkcję języka C, która wyznacza wartość nwd(n, m): int nwd(int n, int m) { if (m==0) return n; if (m>n) return nwd(m, n); return nwd(m, n%m); {int n, m, wynik; cin >> n >> m; wynik = nwd(n, m); cout << wynik;

O translacji programu i funkcji (rekurencyjnych) na kod maszynowy Podział pamięci programu Pamięć zajmowana przez program podzielona jest na kilka obszarów, m.in.: Obszar zajmowany przez zmienne globalne (inaczej zewnętrzne). Obszar zajmowany przez instrukcje programu. Obszar przeznaczony na stos wywołań funkcji. Obszar przeznaczony na obiekty dynamiczne (tzw. sterta) Mechanizmu stosu: Kompilator przekłada instrukcję wywołania funkcji na ciąg rozkazów, który wykonuje m.in. następujące czynności: 1) Rezerwuje na szczycie stosu wywołań odpowiedni dla danej funkcji fragment pamięci, w którym zostanie przydzielone miejsce m.in. na: a) Parametry funkcji b) Zmienne lokalne c) Poprzednią wartość wskaźnika szczytu stosu d) Adres powrotu 2) Oblicza wartości wyrażeń z wywołania funkcji przypisuje je odpowiednim parametrom. 3) Uaktualnia wskaźnik szczytu stosu. 4) Zmienia licznik rozkazów tak, by wskazywał na pierwszą instrukcję funkcji (a dokładniej na adres bajtu pamięci, w którym znajduje się pierwsza instrukcja przekładu treści funkcji). Zakończenie wykonywania funkcji (poprzez instrukcje return czy też poprzez wykonanie ostatniej instrukcji) powoduje m.in.: 1) Przywrócenie wskaźnikowi szczytu stosu wartości jaką miał przed wywołaniem tej funkcji. 2) Przypisanie licznikowi rozkazów wartości adresu powrotu zapamiętanemu w trakcie wywołania. Licznik rozkazów: Program, który ma być wykonany sprowadzany jest do pamięci operacyjnej komputera a licznik rozkazów (zwykle specjalny rejestr w procesorze) ustawiany jest na adres komórki (bajtu) pamięci, w którym znajduje się pierwsza do wykonania instrukcja (a dokładniej początek jej przekładu). Wykonywanie programu sprowadza się do wykonywania przez procesor pętli, w której każdej iteracji najpierw wykonywana jest instrukcja zapamiętana w komórce wskazywanej przez licznik rozkazów, a następnie zwiększana jest zawartość licznika rozkazów tak, by wskazywał on na komórkę z następną instrukcję. Wyjątkiem są instrukcje skoków, których działanie polega przypisaniu licznikowi rozkazów zadanego adresu (po tych instrukcjach nie następuje automatyczne zwiększenie zawartości licznika). W tym kontekście zakończenie wykonywania funkcji polega na wykonaniu rozkazu skoku z argumentem równym adresowi komórki zawierającej przekład następnej po wywołaniu funkcji instrukcji (adres ten nazywany jest adresem powrotu). UWAGI: - Funkcje mogą być wywoływane z treści innych funkcji. Wówczas stos zawiera informację o kolejnych wywołaniach. Przyjmijmy, że o program główny wywołuje funkcję f1, o w treści funkcji f1 wywołamy funkcję f2, o a w treści funkcji f2 zostanie wywołana funkcja f3. Wówczas, o na spodzie stosu umieszczone będą informacje o wywołaniu funkcji f1, o nad nimi informacje o wywołaniu f2, o i jeszcze wyżej o wywołaniu f3. Usuwanie przebiegać będzie w odwrotnej kolejności: o po zakończeniu f3 usuniemy informacje o jej wywołaniu, (i wrócimy do odpowiedniego miejsca w kodzie f2)

o po zakończeniu f2 usuniemy informacje o wywołaniu f2, o po zakończeniu f1 usuniemy informacje o wywołaniu f1. Koszt pamięciowy: Z powyższego wynika, że wywołania rekurencyjne funkcji F powodują utworzenie nowej kopii informacji o wywołaniu F dla każdego wywołania (i nowej kopii zmiennych lokalnych). Oznacza to dodatkowy koszt pamięciowy, który nie jest uwidoczniony w postaci zmiennych. Koszt czasowy: Ocena kosztu czasowego funkcji rekurencyjnych wymaga wyznaczenia liczby wywołań funkcji dla parametrów o danej wartości (rozmiarze). Nie zawsze jest to proste, czasem okazuje się, że funkcja rekurencyjna jest dużo bardziej kosztowna od wariantu iteracyjnego. Choć często daje bardziej zwarty i zrozumiały zapis. Tę kwestię omawiamy na poniższych przykładach.

Obliczanie liczb Fibonacciego Definicja. Liczby Fibonacciego definiujemy następująco: F 0 =F 1 =1 oraz dla i>1 F i = F i-1 +F i-2 Początkowy fragment ciągu liczb Fibonacciego: 1,1,2,3,5,8,13,21,34,55,89,144,233,377,... n + 1 1 Własność. Liczby Fibonacciego rosną w tempie wykładniczym: F n 1+ 5 5 2 Uwaga. Celem prezentowanych poniżej programów obliczających liczby Fibonacciego jest ilustracja rekursji. Dlatego będziemy pomijać w nich szczegóły mogące tę ilustrację zaciemnić. W szczególności będziemy ignorować fakt szybkiego wzrostu liczb Fibonacciego i na pamiętanie liczb Fibonacciego przeznaczymy pojedyncze zmienne. Wersja 1 Program nierekurencyjny {int n, i; long f0,f1,x; cin >> n; f0=f1=1; i=2; while (i<=n) { x=f1; f1+=f0; f0=x; i++; cout << f1 << endl; Mankamenty: - obliczenie stanowiące funkcjonalną całość powinno wykonywać się w odrębnej funkcji, a nie w programie głównym; - naturalniejsza byłaby pętla for (wiemy ile razy należy wykonać iterację). Wersja 2 #include <stdio.h> long fib(int k) { long f0, f1,x; f0=f1=1; for (i=2; i; i++) { x=f1; f1+=f0; f0=x; return f1; Zwróć uwagę na to, że zmienne f0, f1 oraz x uczyniliśmy zmiennymi lokalnymi w funkcji. Ich znaczenie ogranicza się do treści funkcji i nie powinny być widoczne na zewnątrz. Korzystanie w ich miejsce ze zmiennych globalnych nie jest błędem, lecz świadczy o złym stylu. {int n, i; cin >> n; cout << fib(n) << endl;

Program rekurencyjny long fib(int i) { return i<=1? 1 : fib(i-1)+fib(i-2); {int n; cin >> n; cout << fib(n) << endl; Ostrzeżenie Co prawda rekurencja stanowi bardzo naturalną metodę obliczania liczb Fibonacciego, to jednak nie powinna być tu stosowana. Poniższy rysunek przedstawia drzewo wywołań funkcji fib podczas obliczania 6-tej liczby Fibonacciego. fib(6) fib(4) fib(5) fib(3) fib(3) fib(4) fib(3) Jak widać w trakcie obliczeń funkcja jest wielokrotnie wywoływana dla tych samych wartości parametru. W szczególności podczas obliczania F n funkcja fib zostanie wywołana F n-2 razy z parametrem 0 i F n-1 razy z parametrem 1. W efekcie mamy F n-1 +F n-2 =F n wywołań funkcji fib!ponieważ wartości F n rosną szybko, ten sposób obliczania jest niedopuszczalnie czasochłonny. Dla porównania: wersja nierekurencyjna do obliczenia liczby F n wymaga O(n) operacji. Uwaga: Istnieje znacznie szybsza metoda obliczania liczb Fibonacciego. Do obliczenia liczby F n używa ona O(log n) operacji arytmetycznych. Oczywiście trzeba pamiętać, że będą to operacje na coraz dłuższych liczbach i sama ich ilość nie świadczy o koszcie całego algorytmu.