Tablice dwuwymiarowe cd Funkcje rekurencyjne
Treści prezentowane w wykładzie zostały oparte o:
● S. Prata, Język C++. Szkoła programowania. Wydanie VI, Helion, 2012
● www.cplusplus.com
● Jerzy Grębosz, Opus magnum C++11, Helion, 2017
● B. Stroustrup, Język C++. Kompendium wiedzy. Wydanie IV, Helion, 2014
● S. B. Lippman, J. Lajoie, Podstawy języka C++, WNT, Warszawa
Podstawy algorytmiki i programowania -
wykład 2
Tablica jednowymiarowa to jakby rząd liczb napisanych jedna za drugą:
Ta tablica ma rozmiar 9.
Tablica dwuwymiarowa to jakby arkusz liczb, tabela, macierz:
Ta dwuwymiarowa tablica liczb ma dwa rozmiary: 3 wiersze(rzędy) i 2 kolumny elementów.
W C++ tablicę dwuwymiarową mogącą przechować te liczby definiuje się tak:
Tablice wielowymiarowe
1 20 3 100 32 5 4 0 19
1 0
2 14
4 5
W tablicy t do elementu o współrzędnych wiersz: i kolumna: j
odnosimy się wyrażeniem t[i][j]
Zatem elementy naszej tablicy to:
t[0][0]=1 t[0][1]=0 t[1][0]=2 t[1][1]=14 t[2][0]=4 t[2][1]=5
Pętla for, która wypisuje na ekranie wartości elementów tablicy for(int i = 0 ; i < 3 ; i++) {
for(int j = 0 ; j < 2 ; j++) { cout << t[i][j] << " ";
}
cout<<endl;//po każdym wierszu
// przejście do nowej linii }
Tablice wielowymiarowe
Przekazywanie tablic wielowymiarowych do funkcji
Elementy tablicy wielowymiarowej umieszczone są kolejno w pamięci komputera tak, że najszybciej zmienia się najbardziej skrajny prawy indeks.
Oznacza to że tablica przechowywana jest „rzędami”.
Dlatego powyższą tablicę dwuwymiarową możemy inicjalizować następująco:
int t[3][2]={1,0,2,14,4,5};
Lepiej jest inicjalizacjować tablicę ujmując poszczególne wiersze w nawiasy klamrowe (przy czym ewentualne brakujące elementy są inicjalizowane zerami)
int tab[4][2] = {{10, 20},{30},{60,70},{80 }};
W funkcjach mających jako parametry tablice
Przykład 1 – wyświetlenie macierzy /* Napisz funkcję wyświetla na ekranie
macierz a o w wierszach i k kolumnach (k<=10).
Przetestuj funkcję.*/
void drukMacierz(int n, int m, double a[][10]) {
//wyświetlanie el.macierzy
//z dokladn.do 2-ch miejsc po przecinku cout<<fixed<<setprecision(2);
for(int i=0; i < n; i++)
{//wyświetlamy el. i-tego wiersza for(int j=0; j < m; j++)
cout << setw(7) << a[i][j];
cout << endl;//przejście do nowego wiersza }
Przykład 2- liczba elementów ujemnych
/*Funkcja oblicza liczbę ujemnych elementów w tablicy t będącej jej parametrem. Macierz t ma w wierszy i k kolumn (k<=10)- parametry funkcji. */
int ileElUjMacierz(double t[][10], int w, int k) { int ile = 0;//licznik elem. ujemnych
for(int i=0; i<w; i++)//po wierszach
{ //przechodzimy wzdłuż i-tego wiersza for(int j=0; j<k; j++)
if( t[i][j]<0) //jeśli elem. jest <0 ile++;//zwiększamy licznik
}
return ile;
Przykład 2 – main() int main()
{
double m[3][10]= {{1,-2,-3,4},{0,2,-3,5}, {-2,4,-6,8}};
int ileuj = ileElUjMacierz(m,3,4);
cout<<"W macierzy jest: "<<ileuj
<<" elementow ujemnych "<<endl;
}
Przykład 3. Napisz funkcję
void sumyKolumnami(int t[][10],int w, int k, int sk[]);
która w kolejnych składowych wektora sk zapisze sumy odpowiednich kolumn macierzy t. Macierz t ma w
wierszy i k kolumn (k<=10). Przetestuj funkcję.
Przykład 3. Suma elementów w kolumnach void sumyKolumnami1(int t[][10],int w, int k,
int sk[]) {
for(int j=0; j<k; j++)//przechodzimy po kolumnach {
//dla j-tej kolumny:
sk[j]=0;//zerujemy j-ty elem wektora sk for(int i=0; i<w; i++)
//przechodzimy po wierszach j-tej kolumny {
sk[j]+=t[i][j]; //zwiększamy nasza sumę //o kolejne wartości z j-tej kolumny }
}
Przykład 3. Suma elementów w kolumnach sposób 2
/*wersja 2 przechodzimy po tablicy dwuwym. zgodnie z ułożeniem elementów w pamięci, czyli wierszami */
void sumyKolumnami2(int t[][10],int w, int k, int sk[]){
//zerujemy tablice sk przed pętla for(int j=0; j<k; j++)
sk[j]=0;
for (int i=0;i<w;i++)//przechodzimy po wierszach for(int j=0;j<k;j++)//dla i-tego wiersza
//po jego kolumnach
sk[j]+=t[i][j];//zwiększamy dla każdej // kolumny jej sumę elem /*po przejsciu 0-go wiersza w tablicy sk mamy:
sk[0]=t[0][0],sk[1]=t[0][1],...,sk[k-1]=t[0][k-1]
po przejsciu 1-go wiersza zwiększamy każdą sumę o jeden element z 1- go wiersza, itd. */
Przykład 3. Suma elementów w kolumnach – f-cja pomocnicza //funkcja wyswietlajaca na ekranie tablica
void drukTab(int t[], int n){
for(int i=0; i<n ;i++)
cout<<t[i]<<((i != (n-1)) ? ", " : "\n");
}
Przykład 3 - funkcja main() int main () {
int m1[3][10]={{1,2,3,4},{0,2,3,5}, {2,4,6,8}};
int tabsk1[4];
sumyKolumnami1(m1,3,4,tabsk1);
cout<<"sumy kolumn - v1: "<<endl;
drukTab(tabsk1, 4);
int tabsk2[4];
sumyKolumnami2(m1,3,4,tabsk2);
cout<<"sumy kolumn - v2: "<<endl;
drukTab(tabsk2, 4);
}Run:
sumy kolumn - v1:
3, 8, 12, 17
sumy kolumn - v2:
Przykład 4 – minima wierszy w macierzy
/* Napisz funkcję
void MinimaWierszami(int t[][10],int w, int k, int m[]);
która w kolejnych składowych wektora m zapisze minimalne wartości z odpowiednich wierszy macierzy t. Macierz t ma w wierszy i k kolumn (k<=10). Przetestuj funkcję.*/
void MinimaWierszami(int t[][10],int w, int k, int m[]) { for (int i=0;i<w;i++){//przechodzimy po wierszach
//jestesmy w i-tym wierszu, dla i-tego wiersza:
m[i]=t[i][0]; //pod minimum i-tego wiersza
//podstawiamy 0-wy elem z tego wiersza czyli t[i][0]
for(int j=1;j<k;j++){//przechodzimy wzdłuż wiersza // czyli po kolumnach
if(t[i][j]<m[i]){ //jeśli kolejny elem w wierszu // jest mniejszy
m[i]=t[i][j]; //to mamy nowe min }
}
Przykład 4 - funkcja main()
int main () {
int mac[3][10] = {{1, 2, 0, 5}, {6, 2, 1, 8}, {4, 5, 3, 9}};
int tab_min[3];
minimaWierszami(mac, 3, 4, tab_min);
//wyswietlamy minima w poszczegolnych wierszach drukTab(tab_min, 3);
}
Run:0, 1, 3
Przykład 5 - Iloczyn macierzy
Przykład 5.
Funkcja, która oblicza, o ile to możliwe iloczyn dwóch macierzy A i B. Jeżeli nie można obliczyć iloczynu A*B (liczba kolumn mac. A != liczba wierszy mac. B) funkcja zwraca false, a w przeciwny razie true i oblicza iloczyn.
Parametrami funkcji są macierze A i B, ich wymiary oraz
macierz wynikowa i jej wymiary.
Przykład 5 - iloczyn macierzy
#include <iostream>
#include<iomanip>
using namespace std;
bool iloczynMac(double a[][10], int wa,int ka, double b[][10],int wb, int kb,
double c[][10], int &wc,int &kc) {
//liczba kolumn A musi być równa //liczbie wierszy macierzy B
if(ka != wb)
return false;
//obliczamy wymiary macierzy C=A*B wc = wa;
kc = kb;
Przykład 5 - iloczyn macierzy for(int i = 0; i < wc; ++i)
{ for(int j = 0; j < kc; ++j) {
//obliczmy element c[i][j]
//i-ty wiersz mac.A * j-ta kolumna mac.B c[i][j] = 0.0;
for(int k=0; k<ka; ++k) {
c[i][j] += a[i][k] * b[k][j];
} }
}
return true;
}
Przykład 5 - iloczyn macierzy int main() {
//deklaracja mac a
double a[10][10] = {{1}, {2}, {3}};
int wa = 3, ka = 1;
//deklaracja mac b
double b[10][10] = {{1, 1, 1}};
int wb = 1, kb = 3;
//deklaracja mac c double c[10][10];
int wc, kc;
Przykład 5 - iloczyn macierzy if(iloczynMac(a,wa,ka,b,wb,kb,c,wc,kc)) { cout<<"Iloczyn macierzy A*B "<<endl;
drukMacierz(wa,wb,c);
}
else
cout<<"Mnozenie mac. niemożliwe"<<endl;
return 0;
}
Run:
Iloczyn macierzy A*B 1.00 1.00 1.002.00 2.00 2.00 3.00 3.00 3.00
Funkcje rekurencyjne
Funkcje mogą wywoływać same siebie - takie funkcje nazywamy funkcjami rekurencyjnymi.
void f(int x){
f(x);
}
Przy definiowaniu takich funkcji trzeba zadbać, aby ciąg wywołań skończył się (aby nie doprowadzić do
nieskończonej pętli wywołań jak w powyższym przykładzie).
Zatem przed wywołaniem samej siebie funkcja zwykle
sprawdza pewien warunek i jeśli nie jest on spełniony, nie dokonuje już samo-wywołania.
Warunek ten nazywamy warunkiem zatrzymującym
rekurencję (lub warunkiem stopu).Funkcje rekurencyjne void fun(int x){
if(x > 0) fun(x-1);
}
Funkcja ma warunek if(x > 0), dzięki któremu jej wywoływanie ma szanse się kiedyś zakończyć. Jeżeli wywołamy tę funkcję tak:
fun(3),
to wywoła ona siebie samą z argumentem 3 – 1 = 2.
Zacznie się więc ponowne wykonywanie funkcji: fun(2).
W niej nastąpi kolejne wywołanie, tym razem z argumentem 2 – 1 = 1, czyli
fun(1).
Teraz nastąpi kolejne wywołanie, tym razem z argumentem zero (bo:
1 – 1 = 0) fun(0).
To wywołanie funkcji sprawdzi warunek if(x>0) – i okaże się on niespełniony, więc dalsze wywołanie już nie nastąpi. Funkcja wykona
Obiekty definiowane w funkcji rekurencyjnej
Wiemy, że w zwykłych sytuacjach, jeżeli jedna funkcja A wywoła funkcję B, to lokalne zmienne funkcji A (przechowywane na stosie) nie giną.
Jeżeli ta funkcja B zdefiniuje jakieś swoje lokalne zmienne
(obiekty), także pojawiają się one na stosie. Po zakończeniu pracy funkcji B jej lokalne zmienne są usuwane ze stosu, a funkcja A
pracuje na tych swoich, które na nią czekały.
W przypadku wywołań rekurencyjnych jest podobnie. Gdy funkcja A wywołuje siebie samą (czyli drugi raz funkcję A), na stosie są już zmienne będące własnością pierwszego wywołania.
Drugie wywołanie spowoduje, że na stosie pojawią się nowe zmienne dla tego drugiego wywołania. Oczywiście będą to zupełnie odrębne zmienne.
Funkcja n!- wersja iteracyjna
unsigned long long silniaIt(unsigned short n){
//silnia: n!=1*2*...*n, 0!=1!=1 unsigned long long sil=1;
for(int i=1; i<=n; i++) sil *= i;
return sil;
}
int main() {
cout << "5!=" << silniaIt(5)<<endl;
unsigned short n;
cout<<"podaj liczbe";
cin>>n;
cout<<n<<"!="<<silniaIt(n)<<endl;
Funkcje rekurencyjne - przykład n!
Przykład 1. Funkcja obliczająca n!, dla n>=0 //silnia: n!=(n-1)!*n, 0!=1!=1
unsigned long long silnia(unsigned short n)
{ if(n<=1)// warunek stopu:n==0 lub n==1
return 1;// 0! =1 1!=1 return n*silnia(n-1);
}
Dopóki zmienna n nie stanie się <=1, funkcja wywołuje się rekurencyjnie. Z analizy teoretycznej wynika, że po skończonej liczbie kroków wartość n musi stać się równa 0 lub 1. Tak więc warunek zakończenia (tzw. warunek stopu) jest zapewniony i funkcja działa prawidłowo.
Funkcje rekurencyjne - przykład n!
int main() {
cout << "5!=" << silnia(5)<<endl;
unsigned short n;
cout<<"podaj liczbe";
cin>>n;
cout<<n<<"!="<<silnia(n)<<endl;
}
Funkcje rekurencyjne - przykład n!
silnia(5)=5*silnia(4) =5*24 =120
wywołanie powrót4*silnia(3) =4*6=24 wywołanie powrót
3*silnia(2) =3*2=6 wywołanie powrót
2*silnia(1) =2*1
wywołanie powrót
1
Funkcje rekurencyjne - przykład n!