a(1)

Transkrypt

a(1)
Podstawy Informatyki
Metalurgia, I rok niestacjonarne
Wykład 3
Rekurencja
Krótki kurs C++
Rekurencja
z łacińskiego oznacza to przybiec z powrotem - osiągniesz rzecz
wielką, jeśli zawrócisz po to, by osiągnąć rzeczy małe
Przykład:
Małe dziecko otrzymuje polecenie
posprzątania rozrzuconych
klocków do pudełka.
„ zebrać wszystkie klocki
naraz” ????
-
skomplikowane zadanie
„wziąć jeden klocek, przełożyć go
do pudełka, a następnie zrobić to
samo z pozostałymi klockami” ???
- prosta czynność
„Duży” problem został rozłożony na problem
elementarny, który umiemy rozwiązać.
Problem elementarny jest mniej skomplikowany
niż problem początkowy.
Zakończenie algorytmu jest jasno określone („w
momencie gdy na podłodze nie ma rozrzuconych
klocków”)
Matematycznie:
Mamy obliczyć: an
Wiemy że: an =a x an-1 (an-1 jest łatwiej
obliczyć niż an)
Jeśli jest nadal trudno, to zawsze
można wstawić: an-1 =a x an-2,
gdzie: an-2 =a x an-3 itd.
wystarczy wiedzieć tylko, że: a0 =1
aby obliczyć dowolną potęgę a
Def:
Program rekurencyjny jest to program, który wywołuje
sam siebie
Problem:
Dysponujemy tablicą n liczb całkowitych A(n) o
elementach:
a(1),a(2), a(3), ...., a(n)
Zadanie: sprawdzić, czy w tablicy A występuje liczba x
Rozwiązanie:
wziąć pierwszy niezbadany element tablicy n elementowej,
jeśli aktualnie analizowany element tablicy jest równy x to:
wypisz „sukces” i zakończ działanie
w przeciwnym wypadku:
zbadaj pozostałą część tablicy n-1 elementów
W programach rekurencyjnych:
zakończenie programu jest jasno określone (znaleziony
element, przekroczony zakres tablicy),
duży problem zostaje „rozłożony” na problemy
elementarne, które umiemy rozwiązać
Podstawowe błędy:
złe określenie warunku zakończenia programu,
niewłaściwa (nieefektywna) dekompozycja problemu.
Algorytmy sortowania danych
Sortowanie przez selekcję
Opis:
trzeba wyznaczyć najmniejszy element w
ciągu;
zamienić go miejscami z pierwszym
elementem ciągu,
wyznaczyć najmniejszy element w a[2..n] i
zamienić go z drugim elementem w ciągu,
itd.
Sortowanie przez selekcję - algorytm
dla i := 1 do n-1 wykonuj
początek
min := i;
dla j := i+1 do n wykonuj
jeśli a[ j ] < a[ min ] to min := j;
a[ min ] <-> a[ j ] {zamiana miejscami a[ min ] z a[ j ]}
koniec
Sortowanie przez wstawianie
(układanie kart do brydża)
Opis:
metoda
polega na wstawianiu następnej karty we
właściwe miejsce uporządkowanych uprzednio kart
9 8 3 5 7
9
8
3
3
3
8
9
8
5
5
3
3
9
8
7
5
5
5
9
8
7
7
7
7
9
Sortowanie przez wstawianie - algorytm
dla i=2,...,liczba elementów tablicy wykonuj
{
j :=i
podczas gdy j > 1 i x[ j-1 ] > x[ j ] wykonuj
{
pom := x[ j ]
x[ j ] := x[ j-1]
x[ j-1] := pom
j := j-1
}
}
Sortowanie bąbelkowe
0
40
2
39
6
18
¬
4 ¬ 4
20 20 20
¬
4
18
20
¬
4
6
18
20
¬
4
39
6
18
20
¬
2
4
39
6
18
20
2
40
4
39
6
18
20
1
2
40
4
39
6
18
20
Tablica jest przeszukiwana od dołu. Element zacieniony jest
tym, który w pojedynczym przebiegu uleciał do góry jako
„najlżejszy”.
Analizowane są zawsze 2 sąsiadujące ze sobą elementy. Jeśli
nie są uporządkowane (u góry jest element „cięższy”) to
następuje ich zamiana.
Wynik sortowania bąbelkowego
0
40
2
39
6
18
4
20
1
2
40
4
39
6
18
20
2
2
4
40
6
39
18
20
3
2
4
6
40
18
39
20
4
2
4
6
18
40
20
30
5
2
4
6
18
20
40
39
6
2
4
6
18
20
39
40
Algorytm sortowania bąbelkowego
TAB : Tablica [1..n] elementów typu rzeczywistego
i,j : całkowite
pom: rzeczywiste
Początek {programu}
Dla i:=1 do n-1 wykonuj
dla j:=n do i+1 z krokiem -1wykonuj
Jeśli TAB[j]<TAB[j-1] to
początek
pom:=TAB[j-1];
TAB[j-1]:=TAB[j];
TAB[j]:=pom
koniec
Koniec {programu}
.
• Dość często zdarzają się „puste przebiegi” (nie jest
dokonywana żadna wymiana, bowiem elementy są
już posortowane.
• Algorytm jest bardzo wrażliwy na konfigurację
początkową danych.
Wersja 1: 4 2 6 18 20 39 40
Wersja 2: 4 6 18 20 39 40 2
(wersja 1 wymaga jednej zamiany, a wersja 2 wymaga aż
sześciu przebiegów)
Jak wykonują się programy rekurencyjne?
Zadanie:
Obliczyć n!
0! = 1
N! = N*(n-1)!
Dla n>=1
Jak to wygląda w praktyce dla n = 3 (3! = ?)
n = 0? nie
3*2!
n = 0? nie
2*1!
n = 0? nie
1*0!
n = 0? tak
1
-pionowe strzałki oznaczają „zagłębianie się programu” z poziomu ‘n’ na
‘n-1’, itd. , aż do przypadku elementarnego 0!,
- pozioma strzałka oznacza obliczanie wyników cząstkowych,
- ukośna strzałka prezentuje przekazywanie wyniku cząstkowego z poziomu
niższego na wyższy)
Nie rekurencyjne, ale ciekawe algorytmy
Algorytm sprawdzania parzystości liczby
We:
sprawdzana liczba x
y = (x/2)*2
N
WY: x nieparzyste
T
?
x=y
WY: x parzyste
Algorytm sprawdzania parzystości liczby
Program parzystosc;
{ Deklaracja zmiennych }
x,y:
całkowita;
Początek
pisz(‘podaj liczbe x’);
czytaj(x);
y := x/2;
y := y*2;
jeżeli (x=y) to pisz („liczba x jest parzysta”)
w przeciwnym razie pisz („liczba x jest nieparzysta”)
koniec.
Uwaga:
Program ma sens tylko dzięki własności dzielenia liczb całkowitych
.
Sito Erastotenesa
Wyszukiwanie liczb pierwszych
1
2
3
4
5
6
7
8
9
10 11 12 13
14 15 16 17 18 19 20 21 22 23 24 25 26
2
3
4
5
6
7
8
9
10 11 12 13
↑
14 15 16 17 18 19 20 21 22 23 24 25 26
Sito Erastotenesa
2
3
5
7
9
11
13
↑
15
2
17
3
19
5
21
23
7
25
11
13
↑
17
19
Kiedy koniec? Dla √n
23
25
program sito
a: tablica [1...1000] elementów całkowitych
i,j,n,m,x,y:całkowite
pisz 'podaj górną granicę przedziału, max 1000'
czytaj n
m=int(sqrt(n)) {maksymalny mnoznik – pierwiastek z n)
dla i=1 do n wykonuj
a[i]=i {podstawienie kolejnych liczb do tablicy}
dla j=2 do m wykonuj
początek
jeśli a[j] <>0 to
początek
dla i=j+1 do n wykonuj
początek
x=a[i]
y=(x/j)*j {sprawdzanie podzielności}
jeśli x=y to a[i]=0
koniec
koniec
koniec
pisz 'w podanym zakresie liczby pierwsze to:'
dla i=2 do n
jeśli a[i] <>0 to drukuj a[i]
koniec
Obliczenia numeryczne
Obliczanie pierwiastka kwadratowego:
Przykład:
Jak obliczyć pierwiastek z x?
Metoda Newtona:
y + xy
y=
2
x- liczba pierwiastkowana
y - wynik pierwiastkowania
.
Obliczanie pierwiastka kwadratowego - algorytm:
Program pierwiastek kwadratowy;
{ Deklaracja zmiennych }
x,y:
rzeczywista;
i:
całkowita;
Początek
pisz(‘podaj x);
y := 1;
dla i:=0 do i:= 20
y:=(y+x/y)/2;
pisz („pierwiastek z”, x, „ = ” , y)
.
koniec.
Język C++
Historia
•Lata 70-te XX w – język C (do pisania systemów operacyjnych)
•"The C programming language" B. Kernighan, D. Ritchie
– pierwszy standard
•Koniec lat 80 – standard ANSI C
•1983 - C++ (Bjarne Stroustrup)
•Do chwili obecnej nie stosuje się jednego standardu!
Kompilatory C++
•Microsoft Visual C++ (w ramach licencji MSDN AA)
Darmowe:
Linux (PC), Solaris (SUN) :
•http://gcc.gnu.org/ - g++
Windows :
•http://www.bloodshed.net/dev/devcpp.html - Dev-C++
•http://www.delorie.com/djgpp/ - DJGPP
•http://www.mingw.org/ - MinGW
Kompilacja
*.cpp
nagłówki
moduły
biblioteki
kompilator
linker
błędy
błędy
UNIX
Kompilacja:
1) g++ prog.cpp
2) g++ -o prog prog.cpp
Uruchomienie:
1) a.out
2) prog
programm
Pierwszy program
#include <iostream>
#include <string>
using namespace std;
int main()
{
string komunikat;
double a,b,c;
komunikat = "Koniec obliczeń";
cout <<"Podaj a i b: ";
cin >>a>>b;
c=a+b;
cout <<" suma "<<a<<" i "<<b<<" wynosi
"<<c<<'\n';
cout<<komunikat<<'\n';
return 0;
}
Dyrektywy preprocesora
#include <iostream>
#include <string>
Program przed kompilacją dołącza zewnętrzne pliki – np pliki
nagłówkowe.
Poniżej fragment pliku iostream:
extern _IO_istream_withassign cin;
// clog->rdbuf() == cerr->rdbuf()
extern _IO_ostream_withassign cout, cerr;
Zdefiniowanie przestrzeni nazw:
using namespace std;
Program główny
int main()
{
...
...
return 0;
}
Program główny traktowany jest jak funkcja – musi
zwrócić jakąś wartość (typu integer)
Zwracając do systemu operacyjnego wartość 0 informujemy,
że program zakończył się bez błędu.
Deklaracja zmiennych
string komunikat;
double a,b,c;
•short int - typ całkowity krótki
•int - typ całkowity.
•long int - typ całkowity długi
•float - typ zmiennoprzecinkowy pojedynczej precyzji.
•double - typ zmiennoprzecinkowy podwójnej precyzji.
•long double - typ zmiennoprzecinkowy podwójnej precyzji długi.
•char - typ znakowy
•string – ciąg znaków
Nazwy zmiennych mogą składać się z liter,cyfr i podkreślenia _
Nazwa nie może się zaczynać od cyfry
unsigned – zmienna bez znaku (tylko dodatnia) – dla typów int
int (–127,128)
unsigned int (0,255)
Operatory
komunikat = "Koniec obliczeń";
c=a+b;
•operator przypisania
•operator dodawania
•operator odejmowania
•operator mnożenia
•operator dzielenia
•operator reszty z dzielenia (modulo)
•operator znaku liczby (np. -45)
=
+
*
/
%
Operatory złożone
+=
-=
*=
/=
%=
zmienna += 2
zmienna -= 7
zmienna *= 3
zmienna /= 5
zmienna %= 3
≡
≡
≡
≡
≡
zmienna = zmienna + 2
zmienna = zmienna - 7
zmienna = zmienna * 3
zmienna = zmienna / 5
zmienna = zmienna % 3
Operatory inkrementacji i dekrementacji
++ zwiększenie wartości o 1, np. i++ to jest to samo co i=i+1
-- zmniejszenie wartości o 1, np. i-- to jest to samo co i=i-1
Uwaga! Operatory te można stosować przed i po zmiennej, tzn.
liczba++ lub ++liczba. Podobnie dla --. Mimo, że działanie
operatora w obu wypadkach jest podobne, to nie jest jednak
identyczne!
++liczba najpierw dodaje do liczby 1, a potem zwraca jej wartość
liczba++ najpierw zwraca wartość, a potem dodaje
liczba =5;
a) cout<<++liczba;
drukuje 6
b) cout<<liczba++;
drukuje 5
a liczba jest po instrukcji a lub b zawsze równa 6
Operatory relacji
==
!=
>
>=
<
<=
równa się
jest różne
jest większe
jest większe lub równe
jest mniejsze
jest mniejsze lub równe
Operatory logiczne
|| suma - prawdziwe jeśli którekolwiek z wyrażeń jest prawdziwe
&& iloczyn - prawdziwe jeśli oba wyrażenia są prawdziwe
! negacja logiczna - powoduje zaprzeczenie wyrażenia
Uwaga!
Przypisanie = zwraca wartość przypisania czyli np. a=3 zwraca
wartość 3, a każda liczba różna od zera jest w języku C++
traktowana jako prawda. Tylko wartość 0 jest traktowana jako fałsz.
(a=2) || (3==5) ⇒ zawsze prawda;
(a==2)||(3==5) niekoniecznie
Opercje wejścia i wyjścia
Strumienie – biblioteka iostream
cout - powiązany ze standardowym urządzeniem wyjścia
cin - powiązany ze standardowym urządzeniem wejścia
cerr - strumień błędów - połączony ze standardowym
urządzeniem wyjścia
clog - podobnie jak cerr, ale wydajniejszy przy wielu danych
<< - operator wysyłania do strumienia
>> - operator pobierania ze strumienia
np.
cin >>a>>b;
cout <<x1;
cout <<y1<<' '<<y2<<'\n';
Instrukcja warunkowa
if (warunek)
instrukcja1;
else
instrukcja2;
if (warunek)
{
instrukcja1;
instrukcja2;
...
instrukcjan;
}
else
{
instrukcja1;
instrukcja2;
...
instrukcjan;
}
Tablice
int calkowite[20];
char znaki[5];
double liczby[1000];
string napisy[5];
- przechowuje 20 liczb typu int
- p. 5 znaków
- p. 1000 liczb typu double
- p. 5 napisów (wieloznakowych)
Uwaga! Rozmiar tablicy musi być podany przed kompilacją
znaki[0] - pierwszy element tablicy znaki
znaki[4] – piąty element tablicy
Inicjacja tablicy
int calk[5]={2,-3,4,8,12};
int aa[3]={12,-3};
Nie zainicjowane wyrazy otrzymują wartość 0
Nie zainicjowana tablica ma nieokreślone wartości.
int bb[3]={2,-3,4,8}; //Kompilator wykaże błąd!
a=bb[4] – program nie zasygnalizuje błędu, ale
przeczyta nieprzewidywalne dane!
Pętle (1) - for
for (licznik=pocz;licznik<konc;++licznik)
{
Instrukcje w pętli
....
}
licznik – zmienna sterująca pętlą
Wykonuje się dla wartości licznik: od pocz do konc
z dodaniem 1 (++licznik) za każdym cyklem
-------------------------------------------------------------------------------------------for (licznik=pocz;licznik<konc;++licznik)
{
Instrukcje w pętli
....
++licznik
}
Licznik zwiększamy dwa razy!
int licznik;
for (licznik=pocz;licznik<konc;++licznik)
{
Instrukcje w pętli
....
}
Pętla wykona się 9 razy
Po zakończeniu pętli zmienna licznik wynosi 10
--------------------------------------------------------------------------------Nie musimy deklarować oddzielnej zmiennej licznik:
for (int licznik=pocz;licznik<konc;++licznik)
{
Instrukcje w pętli
....
}
Po zakończeniu pętli zmienna licznik... nie istnieje
Odliczanie "z góry"
int licznik;
for (licznik=10;licznik>=0;--licznik)
{
Instrukcje w pętli
....
}
Pętla wykona się 11 razy
Po zakończeniu pętli zmienna licznik wynosi -1
Nie możemy w tym przypadku zadeklarować zmiennej licznik jako
unsigned int
Pętle zagnieżdżone
int i, j,k=1;
for (i=1;i<=10; i+=3)
{
for (j=5;j>=2;--j)
{
Instrukcje w pętli
.......
cout<<i<<j<<k;
++k;
}
}
i
j
k
1
5
1
1
4
2
1
3
3
1
2
4
4
5
5
4
4
6
4
3
7
4
2
8
7
5
9
7
4
10
7
3
11
7
2
12
10
5
13
10
4
14
10
3
15
10
2
16
Pętle (2) – do ... while
do
{
instrukcje w pętli
...
}
while (warunek);
Wykonuje się dla wartości warunek: prawda
Gdy warunek przestaje być prawdziwy – wychodzimy z pętli
-------------------------------------------------------------------------------------------Pętla wykona się co najmniej jeden raz!
W pętli for warunek (zakresu) był sprawdzany na początku i instrukcje
w pętli mogły się nie wykonać ani raz!
Pętle (2) – do ... while
int i=3;
do
{
instrukcje w pętli
.....
++i;
}
while (i<10);
3
4
5
6
7
8
9
int i=3;
do
{
instrukcje w pętli
.....
++i;
}
while (i<1);
3
Pętle (3) – while
while (warunek)
{
instrukcje w pętli
...
}
Wykonuje się dla wartości warunek: prawda
Gdy warunek przestaje być prawdziwy – wychodzimy z pętli
-------------------------------------------------------------------------------------------Pętla może się nie wykonać ani jeden raz!
W pętli while warunek jest sprawdzany na początku i instrukcje w pętli
mogą się w ogóle nie wykonać
Pętle (3) – while
int i=3;
while (i<10)
{
instrukcje w pętli
.....
cout<<i<<'\n';
++i;
}
3
4
5
6
7
8
9
int i=3;
while (i<1)
{
instrukcje w pętli
.....
cout<<i<<'\n';
++i;
}
(nic)
Pętle i tablice
int tabl[n]; //tablica n-elementowa
unsigned int i; //zmienna sterujaca petlami
for (i=0;i<n;++i)
// wczytanie elementow tablicy
{
cout <<"Podaj "<<i+1<<". element tablicy: ";
cin >>tabl[i];
}
for (i=0;i<n;++i)
// dodanie liczby 10 do kazdego elementu tablicy
tabl [i]+=10;
for (i=0;i<n;++i)
// wypisanie elementow tablicy
cout <<tabl[i]<<'\n ';
Pętle i tablice
const int n=26; // rozmiar tablicy z danymi
float dane[n];
unsigned int i; //zmienna sterujaca petlami
float suma,srednia;
// suma wyrazow, petla while
i=0;
suma=0;
while (i<n)
{
suma+=dane[i];
++i;
}
srednia=suma/n;
Break – przerwanie pętli
Liczymy średnią arytmetyczną wczytanych liczb (max 100).
Element równy zero kończy wczytywanie.
int i;
float a,suma=0,srednia;
for (i=0;i<99;++i)
{
cin>>a;
if (a==0)
break;
suma+=a;
}
srednia=suma/i;
cout<<srednia<<'\n'
Jeśli stosujemy zagnieżdżone pętle i użyjemy instrukcji break w pętli
najbardziej wewnętrznej, to zostanie przerwana jedynie ta najbardziej
wewnętrzna pętla, pozostałe pętle będą się wykonywać.
Continue – przerwanie kroku pętli
Z przedziału 1..n podajemy liczby podzielne przez 3
for (unsigned int i=0;i<=n;++i)
{
if ((i%3)!=0)
continue;
cout <<"Liczba "<<i<<"jest podzielna przez 3\n";
}
Program wraca na początek pętli i wykonuje kolejny krok
Etykiety i instrukcje skoku goto
{
instrukcje programu
......
goto NazwaEtykiety2;
.......
NazwaEtykiety1:
.........
........
goto NazwaEtykiety1;
.......
.......
NazwaEtykiety2:
.....
.....
}
W miejscu umieszczenia etykiety dwukropek
Po instrukcji goto średnik
Operator warunkowy
wyrażenie1 ? wyrażenie2 : wyrażenie3;
Jeżeli wyrażenie1 jest prawdziwe, to wykonuj wyrażenie2 ,
a jeśli jest fałszywe – wykonuj wyrażenie3
(a<b) ? cout <<"a jest mniejsze" : cout <<"b jest mniejsze";
zastępuje:
if (a<b)
cout <<"a jest mniejsze";
else
cout <<"b jest mniejsze";
Instrukcja wyboru switch
switch (wyrażenie)
{
case wartosc1:
Instrukcje1;
break;
case wartosc2:
Instrukcje2;
break;
...
case wartoscn:
InstrukcjeN;
break;
default:
InstrukcjaDomyslna;
}
Obliczana jest wartość wyrażenia. Jeśli jest równa wartosc1 – wykonywane są
instrukcje1, jeśli wynik wynosi wartość2 – wykonywane są instrukcje2 itd.
Jeśli wynik wyrażenia nie jest równy żadnemu przypadkowi (case) –
wykonywana jest instrukcja domyślna (default)