Strona główna » Algorytmy » Artykuły » Macierze w C++ cz. 1
1011

Macierze w C++ cz. 1

· część 1 · część 2 · część 3 · Wyznacznik ·

Macierze w C++

Macierze najczęściej zapisuje się w postaci prostokątnej tablicy. Możliwe jest również zapisanie ich w postaci wektora długość równej ilości wierszy macierzy pomnożonej przez ilość kolumn macierzy. W C++ najbardziej popularną metodą jest przechowywanie macierzy w listach podwójnie indeksowanej czyli prościej mówiąc dwuwymiarowych. Jednak prócz samej tabeli musimy przechowywać wielkość tabeli: ilość wierszy i kolumn. W celu uproszczenia zapisu użyjemy struktury.

Implementacja

Struktura

W strukturze będziemy przechowywać 2D listę zawierającą liczby w zmiennej data, ilość wierszy w n oraz ilość kolumn w m.

  1. struct Matrix{
  2.   double** data;
  3.   int n;
  4.   int m;
  5. };

Tworzenie macierzy

Nowo utworzona macierz będzie miała zadaną wielkość przez argumenty n i m. Zwrócimy wskaźnik na nowo utworzoną macierz, a wszystkie jej liczby ustawimy na 0.

  1. Matrix* createMatrix(int n, int m){
  2.   Matrix* matrix = new Matrix();
  3.   matrix->n = n;
  4.   matrix->m = m;
  5.   double** table = new double*[n];
  6.   for(int i = 0; i < n; i++){
  7.     double* row = new double[m];
  8.     for(int j = 0; j < m; j++){
  9.       row[j] = 0;
  10.     }
  11.     table[i] = row;
  12.   }
  13.   matrix->data = table;
  14.   return matrix;
  15. }

(2.) Przy tworzeniu uzyskujemy wskaźnik na macierz, więc aby zmodyfikować pola danych nowo utworzonej macierzy to będziemy używać "->" zamiast "."(, którą używamy dla typ, a nie typ*). (3. - 4.) Następnie przypisujemy wielkość naszej zmiennej macierzy. (5.) Zmienna table to nasza 2D tablica, którą na sam koniec przekażemy do matrix. (7.) Następnie dla każdego wiersza tworzymy listę double i (9.) każdy element ustawiamy na 0. Na koniec iteracji (11.) przekazujemy wskaźnik do zmiennej table i (12.) zwracamy macierz.

Bardzo często przy macierzach omawia się macierze kwadratowe, dlatego warto dodać specjalną funkcję do tworzenia macierzy kwadratowej. Jednak pisanie całkowicie od nowa jest bezsensowne. Warto tu skorzystać z już utworzonej createMatrix() i podać dwa takie same argumenty:

  1. Matrix* createSquareMatrix(int n){
  2.   return createMatrix(n,n);
  3. }

Napiszmy jeszcze funkcję createIdentityMatrix(), która stworzy macierz jednostkową, która na swojej głównej diagonali ma same jedynki. Prościej mówiąc każda liczba o współrzędnych x i y, gdzie x = y ma wartość 1.

  1. Matrix* createIdentityMatrix(int n){
  2.   Matrix* m = createSquareMatrix(n);
  3.   for(int i = 0; i < n; i++)
  4.     m->data[i][i] = 1;
  5.   return m;
  6. }

Modyfikowanie macierzy

Załóżmy, że chcemy napisać funkcję, która usunie i-ty wiersz macierzy. Oznacza to, że musimy utworzyć nową tablicę wielkości n-1 x m. Następnie przepisać każdy wiersz po kolei pomijając wiersz, który chcemy usunąć.

  1. void deleteRow(Matrix* m, int v){
  2.   double** table = new double*[m->n-1];
  3.   for(int i = 0; i < v-1; i++)
  4.     table[i] = m->data[i];
  5.   for(int i = v; i < m->n; i++)
  6.     table[i-1] = m->data[i];
  7.  
  8.   delete m->data;
  9.   m->data = table;
  10.   m->n--;
  11. }

Należy pamiętać, że nieużywaną pamięć należy zwolnić, dlatego przed (9.) przypisaniem nowej listy liczb (8.) usuwamy starą po przepisaniu. Linijki (3. - 6.) można przepisać jako jedną pętle for z dodatkowym warunkiem if. Jest to jednak bardziej skomplikowane i mniej efektywne. Na koniec należy pamiętać, że rozmiar macierzy się zmienił co oznacza, że (10.) ilość wierszy macierzy należy zmniejszyć.

Usuwanie kolumny macierzy jest trochę bardziej skomplikowane, ale opiera się na identycznym schemacie:

  1. void deleteColumn(Matrix* m, int v){
  2.   for(int i = 0; i < m->n; i++){
  3.     double* row = new double[m->m-1];
  4.     for(int j = 0; j < v-1; j++)
  5.       row[j] = m->data[i][j];
  6.     for(int j = v; j < m->m; j++)
  7.       row[j - 1] = m->data[i][j];
  8.     delete m->data[i];
  9.     m->data[i] = row;
  10.   }
  11.   m->m--;
  12. }

Mnożenie macierzy

Mnożenie macierzy przez skalar oznacza pomnożenie każdego liczby w macierzy przez skalar:

  1. void multiplyByNumber(Matrix* m, double a){
  2.   for(int i = 0; i < m->n; i++)
  3.     for(int j = 0; j < m->m; j++)
  4.       m->data[i][j] *= a;
  5. }

Wypisywanie macierzy

Struktura Matrix jest naszym wytworem i standardowe biblioteki nie potrafią wypisać jej na konsole. Jednak możemy napisać specjalną funkcje, która nam to umożliwi:

  1. void writeMatrix(Matrix* matrix){
  2.   for(int i = 0; i < matrix->n; i++){
  3.     for(int j = 0; j < matrix->m; j++){
  4.       cout << matrix->data[i][j] << "\t";
  5.     }
  6.     cout << endl;
  7.   }
  8. }

Funkcja używa tu specjalnego znaku \t, który wypisuje tabulator, aby macierz była wyrównana w kolumnach.

Testowanie funkcji

Poniższa funkcja main() prezentuje użycie niektórych napisanych funkcji:

  1. int main () {
  2.   setlocale(LC_ALL,"");
  3.   Matrix* matrix = createIdentityMatrix(4);
  4.   cout << "Nowa macierz: \n";
  5.   writeMatrix(matrix);
  6.   cout << "\n\nUsuwamy drugi wiersz macierzy: \n";
  7.   deleteRow(matrix, 2);
  8.   writeMatrix(matrix);
  9.   cout << "\n\nUsuwamy trzecią kolumnę macierzy: \n";
  10.   deleteColumn(matrix, 2);
  11.   writeMatrix(matrix);
  12.   cout << "\n\Mnożymy macierz przez skalar 3: \n";
  13.   multiplyByNumber(matrix, 3);
  14.   writeMatrix(matrix);
  15.   system("pause");
  16.   return 0;
  17. }

Zadania

Zadanie 1

Napisz funkcję sumEverything(), która zsumuje wszystkie liczby w macierzy i zwróci wynik jako liczbę rzeczywistą.

Zadanie 2

Napisz funkcję readMatrix(), która pozwoli użytkownikowi wprowadzić macierz czyli: