1 Podstawy algorytmiki i programowania - wykład 2 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 2003.
Tablice wielowymiarowe 2 Tablica jednowymiarowa to jakby rząd liczb napisanych jedna za drugą: 1 20 3 100 32 5 4 0 19 Ta tablica ma rozmiar 9. Tablica dwuwymiarowa to jakby arkusz liczb, tabela, macierz: 1 0 2 14 4 5 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: int t[3][2];
Tablice wielowymiarowe 3 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
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 dwuwymiarowe musimy zawsze jawnie specyfikować drugi wymiar tablicy. 4
Przykład 2 - zamiana dwóch wierszy w macierzy /*Funkcja zamienia dwa wiersze o podanych indeksach Pierwszymi parametrami funkcji są liczba kolumn i tab. dwuwymiarowa Indeksy wierszy są ostatnimi parametrami funkcji. Zakładamy, ze są one poprawne */ void zamien(int k, float tab[][10], int w1, int w2) { float temp; //zmienna robocza //przechodzimy wzdluz wiersza (po kolumnach) for (int j = 0; j < k ;j++) { //zamieniamy j-te elementy temp = tab[w1][j]; tab[w1][j] = tab[w2][j]; tab[w2][j] = temp; 5
6 Przykład 2 - funkcja main() int main() { float mac[3][10] = {{1,2,3,{4,5,6,{7,8,9; //f-cja z poprzedniego przykładu drukmacierz(3,3,mac); zamien(3,mac,1,2); cout<<"macierz po zamianie: "<<endl; drukmacierz(3,3,mac); return 0;
7 OUTPUT: 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 macierz po zamianie: 1.00 2.00 3.00 7.00 8.00 9.00 4.00 5.00 6.00 Przykład 2 - funkcja main()
8 Przykład 3 Przykład 3. 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. Przetestuj funkcję. 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) //jesli elem. jest <0 return ile; ile++;//zwiekszamy licznik
9 int main() Przykład 3 { 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 4. Napisz funkcję void sumywierszami(double t[][10],int w, int k, double sw[]); która w kolejnych składowych wektora sw zapisze sumy odpowiednich wierszy macierzy t. Macierz t ma w wierszy i k kolumn (k<=10). Przetestuj funkcję.
Przykład 4. Suma elementów w wierszach void sumywierszami(double t[][10], int w, int k, double sw[]) { double s; //zmienna na sumę for(int i=0; i<w; i++) { //dla i-tego wiersza: s = 0.0; //zerujemy sumę lub sw[i]=0; //przechodzimy po kolumnach for(int j=0; j<k; j++) s += t[i][j]; //zwiększamy sumę o kolejne el. //lub: sw[i]+= t[i][j]; sw[i] = s; //przypisujemy obliczoną sume i-tego //wiersza i-temu elementowi tablicy sw 10
11 int main() Przykład 4 - funkcja main() { double m1[3][10]={{1,2,3,4,{0,2,3,5, {2,4,6,8; double tabsw[3]; sumywierszami(m1,3,4,tabsw); cout<<"sumy wierszy: "<<endl; cout<<fixed<<setprecision(2);//iomanip for(int i = 0; i < 3; i++) cout << setw(3) << i << " - "<< setw(8) << tabsw[i] << endl; return 0;
Przykład 4 - funkcja main() //double m1[3][10]={{1,2,3,4,{0,2,3,5,{2,4,6,8; OUTPUT: sumy wierszy: 0-10.00 1-10.00 2-20.00 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. 12
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ć rowna //liczbie wierszy macierzy B if(ka!= wb) return false; //obliczamy wymiary macierzy C=A*B wc = wa; kc = kb; 13
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) { return true; c[i][j] += a[i][k] * b[k][j]; 14
Przykład 5 - iloczyn macierzy void drukmacierz(int n, int m, double a[][10]) { //wyswietlanie el. macierzy z dokladnoscia //do 2-ch miejsc po przecinku cout<<fixed<<setprecision(2); for(int i=0; i < n; i++) {//wyswietlamy el. i-tego wiersza for(int j=0; j < n; j++) cout << setw(8) << a[i][j]; //przechodzimy do nowego wiersza cout << endl; 15
16 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;
17 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. niemozliwe"<<endl; return 0; Output: Iloczyn macierzy A*B 1.00 1.00 1.00 2.00 2.00 2.00 3.00 3.00 3.00
18 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).
19 void fun(int x){ if(x > 0) fun(x-1); Funkcje rekurencyjne 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 się do końca, po czym powróci do poprzedniej, która też wykona się do końca, i tak dalej.
20 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.
21 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.
22 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;
23 Funkcje rekurencyjne - przykład n! silnia(5)=5*silnia(4) =5*24 =120 wywołanie powrót 4*silnia(3) =4*6=24 wywołanie powrót 3*silnia(2) =3*2=6 wywołanie wywołanie powrót 2*silnia(1) =2*1 1 powrót
Funkcje rekurencyjne - przykład n! 24