Bài 26.5: Bài tập thực hành ma trận trong C++

Bài 26.5: Bài tập thực hành ma trận trong C++
Chào mừng trở lại với series blog C++ của chúng ta! Hôm nay, chúng ta sẽ đi sâu vào một chủ đề rất thú vị và hữu ích trong lập trình: Ma trận. Ma trận không chỉ là khái niệm cơ bản trong toán học, mà còn là công cụ mạnh mẽ được ứng dụng rộng rãi trong đồ họa máy tính, xử lý ảnh, học máy và nhiều lĩnh vực khác.
Trong bài viết này, chúng ta sẽ cùng nhau thực hành các thao tác cơ bản với ma trận trong C++. Chúng ta sẽ tìm hiểu cách biểu diễn ma trận, cách nhập/xuất, và thực hiện một số phép toán đơn giản như cộng, trừ, nhân với số vô hướng, và tìm ma trận chuyển vị. Tất cả sẽ được minh họa bằng các ví dụ code C++ ngắn gọn và dễ hiểu.
Biểu diễn Ma trận trong C++
Có nhiều cách để biểu diễn ma trận trong C++, nhưng phổ biến nhất là sử dụng mảng hai chiều hoặc vector của vector.
Mảng hai chiều truyền thống: Đây là cách đơn giản nhất nếu bạn biết trước kích thước của ma trận tại thời điểm biên dịch.
#include <iostream> int main() { // Khai bao ma tran 3x3 int matrix[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // Truy cap phan tu o hang 1, cot 2 (chi so bat dau tu 0) cout << "Phan tu o [1][2] la: " << matrix[1][2] << endl; // Output: 6 return 0; }
Giải thích:
int matrix[3][3]
khai báo một mảng hai chiều với 3 hàng và 3 cột. Các dấu{}
được sử dụng để khởi tạo giá trị cho ma trận. Bạn truy cập các phần tử bằng cách sử dụngmatrix[chiso_hang][chiso_cot]
. Nhớ rằng chỉ số trong C++ bắt đầu từ 0. Nhược điểm của cách này là kích thước ma trận phải cố định.Vector của Vector (
vector<vector<int>>
): Đây là cách linh hoạt hơn vì nó cho phép bạn làm việc với ma trận có kích thước động (có thể thay đổi lúc chạy chương trình). Đây là phương pháp được ưu tiên sử dụng trong C++ hiện đại khi bạn cần sự linh hoạt về kích thước.#include <vector> #include <iostream> int main() { // Khai bao ma tran 3x4 su dung vector cua vector vector<vector<int>> dynamicMatrix = { {10, 20, 30, 40}, {50, 60, 70, 80}, {90, 100, 110, 120} }; // Lay kich thuoc ma tran int rows = dynamicMatrix.size(); // So hang int cols = dynamicMatrix[0].size(); // So cot (can dam bao ma tran khong rong) cout << "Ma tran dong co kich thuoc: " << rows << " hang x " << cols << " cot." << endl; // Truy cap phan tu o hang 0, cot 3 cout << "Phan tu o [0][3] la: " << dynamicMatrix[0][3] << endl; // Output: 40 return 0; }
Giải thích:
vector<vector<int>>
tạo ra một vector mà mỗi phần tử của nó lại là mộtvector<int>
. Vector "ngoài" chứa các hàng, và mỗi vector "bên trong" chứa các phần tử của một hàng. Bạn có thể lấy số hàng bằng.size()
trên vector ngoài (dynamicMatrix.size()
) và số cột bằng.size()
trên một hàng bất kỳ (ví dụ:dynamicMatrix[0].size()
). Cách truy cập phần tử vẫn tương tự như mảng hai chiều truyền thống:dynamicMatrix[chiso_hang][chiso_cot]
.
Trong các ví dụ tiếp theo, chúng ta sẽ chủ yếu sử dụng vector của vector vì tính linh hoạt của nó.
Hiển thị Ma trận (In ra màn hình)
Để hiển thị ma trận, chúng ta cần duyệt qua tất cả các phần tử của nó. Cách phổ biến nhất là sử dụng hai vòng lặp lồng nhau: vòng lặp ngoài duyệt qua các hàng, và vòng lặp trong duyệt qua các cột trong mỗi hàng.
#include <vector>
#include <iostream>
// Ham in ma tran
void printMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty()) {
cout << "Ma tran rong." << endl;
return;
}
for (const auto& row : matrix) { // Duyet qua tung hang (su dung range-based for loop)
for (int element : row) { // Duyet qua tung phan tu trong hang
cout << element << "\t"; // In phan tu, su dung '\t' de can le
}
cout << endl; // Het mot hang thi xuong dong
}
}
int main() {
vector<vector<int>> myMatrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
cout << "**Ma tran cua toi:**" << endl;
printMatrix(myMatrix);
return 0;
}
Giải thích: Hàm printMatrix
nhận vào một ma trận (bằng hằng tham chiếu const&
để tránh sao chép tốn kém). Vòng lặp ngoài for (const auto& row : matrix)
duyệt qua từng hàng của ma trận. Với mỗi hàng (row
), vòng lặp trong for (int element : row)
duyệt qua từng phần tử (element
) trong hàng đó. cout << element << "\t";
in phần tử, theo sau là ký tự tab (\t
) để tạo khoảng cách giữa các cột. Sau khi in xong một hàng, cout << endl;
xuống dòng để bắt đầu in hàng tiếp theo.
Nhập Ma trận từ người dùng
Để nhập ma trận từ người dùng, chúng ta thường cần biết kích thước của ma trận trước. Sau đó, chúng ta sẽ cấp phát không gian cho ma trận (hoặc định lại kích thước vector) và sử dụng các vòng lặp lồng nhau cùng với cin
để đọc từng phần tử.
#include <vector>
#include <iostream>
int main() {
int rows, cols;
cout << "Nhap so hang cua ma tran: ";
cin >> rows;
cout << "Nhap so cot cua ma tran: ";
cin >> cols;
// Kiem tra kich thuoc nhap vao co hop le khong
if (rows <= 0 || cols <= 0) {
cerr << "Kich thuoc ma tran phai la so duong." << endl;
return 1; // Thoat chuong trinh voi ma loi
}
// Khai bao ma tran voi kich thuoc da nhap va khoi tao cac phan tu mac dinh (vi du 0)
vector<vector<int>> inputMatrix(rows, vector<int>(cols));
cout << "\n*Hay nhap cac phan tu cua ma tran (" << rows << "x" << cols << "):*" << endl;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
cout << "Nhap phan tu [" << i << "][" << j << "]: ";
cin >> inputMatrix[i][j];
}
}
cout << "\n**Ma tran ban vua nhap:**" << endl;
// Su dung lai ham printMatrix hoac in truc tiep
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
cout << inputMatrix[i][j] << "\t";
}
cout << endl;
}
return 0;
}
Giải thích: Đầu tiên, chúng ta yêu cầu người dùng nhập số hàng và số cột. Sau đó, chúng ta tạo một vector<vector<int>>
có kích thước đúng bằng rows
hàng và cols
cột bằng cách sử dụng constructor vector<vector<int>>(rows, vector<int>(cols))
. Phần vector<int>(cols)
tạo ra một vector con (một hàng) có cols
phần tử, và constructor ngoài lặp lại điều này rows
lần. Sau đó, hai vòng lặp lồng nhau duyệt qua từng vị trí [i][j]
và sử dụng cin >> inputMatrix[i][j]
để đọc giá trị từ bàn phím vào vị trí đó.
Các Phép toán Cơ bản trên Ma trận
Bây giờ chúng ta đã biết cách biểu diễn, truy cập và hiển thị ma trận, hãy cùng xem xét một số phép toán cơ bản.
Phép Cộng Ma trận
Hai ma trận chỉ có thể cộng với nhau nếu chúng có cùng kích thước. Kết quả là một ma trận mới có cùng kích thước, trong đó mỗi phần tử là tổng của các phần tử tương ứng từ hai ma trận ban đầu.
#include <vector>
#include <iostream>
// (Include printMatrix function here or assume it's defined)
void printMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty()) { cout << "[]" << endl; return; }
for (const auto& row : matrix) {
for (int element : row) { cout << element << "\t"; }
cout << endl;
}
}
// Ham cong hai ma tran
vector<vector<int>> addMatrices(const vector<vector<int>>& matA,
const vector<vector<int>>& matB) {
// Kiem tra dieu kien: hai ma tran phai cung kich thuoc
if (matA.empty() || matB.empty() || matA.size() != matB.size() || matA[0].size() != matB[0].size()) {
cerr << "Loi: Hai ma tran khong cung kich thuoc de cong." << endl;
return {}; // Tra ve ma tran rong de bao hieu loi
}
int rows = matA.size();
int cols = matA[0].size();
// Tao ma tran ket qua co cung kich thuoc, khoi tao voi gia tri 0
vector<vector<int>> resultMatrix(rows, vector<int>(cols, 0));
// Thuc hien phep cong
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
resultMatrix[i][j] = matA[i][j] + matB[i][j];
}
}
return resultMatrix;
}
int main() {
vector<vector<int>> matrix1 = {
{1, 2},
{3, 4}
};
vector<vector<int>> matrix2 = {
{5, 6},
{7, 8}
};
cout << "**Ma tran 1:**" << endl;
printMatrix(matrix1);
cout << "\n**Ma tran 2:**" << endl;
printMatrix(matrix2);
vector<vector<int>> sumMatrix = addMatrices(matrix1, matrix2);
if (!sumMatrix.empty()) { // Chi in neu phep cong thanh cong
cout << "\n***Tong hai ma tran:***" << endl;
printMatrix(sumMatrix);
}
// Vi du cong hai ma tran khac kich thuoc
// vector<vector<int>> matrix3 = {{1}};
// addMatrices(matrix1, matrix3); // Se in ra thong bao loi
return 0;
}
Giải thích: Hàm addMatrices
kiểm tra xem hai ma trận đầu vào (matA
và matB
) có cùng số hàng (matA.size() == matB.size()
) và cùng số cột (matA[0].size() == matB[0].size()
) hay không. Nếu không, nó in thông báo lỗi và trả về một ma trận rỗng. Nếu có, nó tạo một ma trận resultMatrix
cùng kích thước, rồi dùng hai vòng lặp lồng nhau để duyệt qua từng vị trí [i][j]
. Tại mỗi vị trí, nó tính tổng matA[i][j] + matB[i][j]
và lưu vào resultMatrix[i][j]
. Cuối cùng, ma trận kết quả được trả về.
Phép Trừ Ma trận
Tương tự như phép cộng, hai ma trận chỉ có thể trừ cho nhau nếu chúng có cùng kích thước. Kết quả là một ma trận mới có cùng kích thước, trong đó mỗi phần tử là hiệu của các phần tử tương ứng từ hai ma trận ban đầu.
#include <vector>
#include <iostream>
// (Include printMatrix function here or assume it's defined)
void printMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty()) { cout << "[]" << endl; return; }
for (const auto& row : matrix) {
for (int element : row) { cout << element << "\t"; }
cout << endl;
}
}
// Ham tru hai ma tran (matA - matB)
vector<vector<int>> subtractMatrices(const vector<vector<int>>& matA,
const vector<vector<int>>& matB) {
// Kiem tra dieu kien kich thuoc
if (matA.empty() || matB.empty() || matA.size() != matB.size() || matA[0].size() != matB[0].size()) {
cerr << "Loi: Hai ma tran khong cung kich thuoc de tru." << endl;
return {}; // Tra ve ma tran rong bao hieu loi
}
int rows = matA.size();
int cols = matA[0].size();
vector<vector<int>> resultMatrix(rows, vector<int>(cols, 0));
// Thuc hien phep tru
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
resultMatrix[i][j] = matA[i][j] - matB[i][j];
}
}
return resultMatrix;
}
int main() {
vector<vector<int>> matrix1 = { {10, 20}, {30, 40} };
vector<vector<int>> matrix2 = { {1, 2}, {3, 4} };
cout << "**Ma tran 1:**" << endl;
printMatrix(matrix1);
cout << "\n**Ma tran 2:**" << endl;
printMatrix(matrix2);
vector<vector<int>> diffMatrix = subtractMatrices(matrix1, matrix2);
if (!diffMatrix.empty()) {
cout << "\n***Hieu hai ma tran (Mat1 - Mat2):***" << endl;
printMatrix(diffMatrix);
}
return 0;
}
Giải thích: Cấu trúc hàm subtractMatrices
hoàn toàn tương tự như addMatrices
, chỉ khác ở phép toán thực hiện bên trong vòng lặp: resultMatrix[i][j] = matA[i][j] - matB[i][j];
.
Phép Nhân Ma trận với một số vô hướng
Khi nhân một ma trận với một số vô hướng (một số đơn lẻ), ta nhân mọi phần tử của ma trận đó với số vô hướng. Kích thước của ma trận kết quả sẽ giống ma trận ban đầu.
#include <vector>
#include <iostream>
// (Include printMatrix function here or assume it's defined)
void printMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty()) { cout << "[]" << endl; return; }
for (const auto& row : matrix) {
for (int element : row) { cout << element << "\t"; }
cout << endl;
}
}
// Ham nhan ma tran voi mot so vo huong
vector<vector<int>> scalarMultiplyMatrix(const vector<vector<int>>& matrix, int scalar) {
if (matrix.empty()) {
return {}; // Xu ly ma tran rong
}
int rows = matrix.size();
int cols = matrix[0].size();
vector<vector<int>> resultMatrix(rows, vector<int>(cols));
// Thuc hien phep nhan vo huong
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
resultMatrix[i][j] = matrix[i][j] * scalar;
}
}
return resultMatrix;
}
int main() {
vector<vector<int>> myMatrix = {
{1, 2, 3},
{4, 5, 6}
}; // Ma tran 2x3
int myScalar = 5;
cout << "**Ma tran ban dau:**" << endl;
printMatrix(myMatrix);
cout << "\n**So vo huong:** " << myScalar << endl;
vector<vector<int>> scaledMatrix = scalarMultiplyMatrix(myMatrix, myScalar);
cout << "\n***Ma tran sau khi nhan voi so vo huong:***" << endl;
printMatrix(scaledMatrix);
return 0;
}
Giải thích: Hàm scalarMultiplyMatrix
nhận vào ma trận và số vô hướng scalar
. Nó tạo một ma trận kết quả cùng kích thước, sau đó dùng hai vòng lặp để duyệt qua từng phần tử và nhân nó với scalar
, lưu kết quả vào ma trận mới.
Tìm Ma trận Chuyển vị (Transpose)
Ma trận chuyển vị của một ma trận $A$ (ký hiệu $A^T$) thu được bằng cách đổi hàng thành cột và cột thành hàng. Nếu ma trận $A$ có kích thước $m \times n$, thì ma trận chuyển vị $A^T$ sẽ có kích thước $n \times m$. Phần tử ở vị trí [i][j]
của ma trận gốc sẽ nằm ở vị trí [j][i]
trong ma trận chuyển vị.
#include <vector>
#include <iostream>
// (Include printMatrix function here or assume it's defined)
void printMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty()) { cout << "[]" << endl; return; }
for (const auto& row : matrix) {
for (int element : row) { cout << element << "\t"; }
cout << endl;
}
}
// Ham tim ma tran chuyen vi
vector<vector<int>> transposeMatrix(const vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty()) {
return {}; // Xu ly ma tran rong hoac ma tran co 0 cot
}
int rows = matrix.size();
int cols = matrix[0].size();
// Ma tran chuyen vi se co kich thuoc cols x rows
vector<vector<int>> transposedMatrix(cols, vector<int>(rows));
// Thuc hien chuyen vi
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
// Phan tu o [i][j] cua ma tran goc chuyen thanh [j][i] trong ma tran chuyen vi
transposedMatrix[j][i] = matrix[i][j];
}
}
return transposedMatrix;
}
int main() {
vector<vector<int>> myMatrix = {
{1, 2, 3},
{4, 5, 6}
}; // Ma tran 2x3
cout << "**Ma tran ban dau:**" << endl;
printMatrix(myMatrix);
vector<vector<int>> transMatrix = transposeMatrix(myMatrix);
cout << "\n***Ma tran chuyen vi:***" << endl; // Expected 3x2
printMatrix(transMatrix);
return 0;
}
Giải thích: Hàm transposeMatrix
đầu tiên xác định kích thước của ma trận gốc (rows
và cols
). Sau đó, nó tạo ma trận kết quả transposedMatrix
với kích thước đảo ngược (cols
hàng, rows
cột). Hai vòng lặp lồng nhau duyệt qua ma trận gốc. Tại mỗi vị trí [i][j]
của ma trận gốc, giá trị matrix[i][j]
được gán cho vị trí [j][i]
trong transposedMatrix
.
Những Lưu ý Khi Làm Việc Với Ma trận trong C++
- Chỉ số bắt đầu từ 0: Luôn nhớ rằng chỉ số hàng và cột trong C++ (dù là mảng hay vector) đều bắt đầu từ 0. Một ma trận $m \times n$ sẽ có chỉ số hàng từ 0 đến $m-1$ và chỉ số cột từ 0 đến $n-1$.
- Kích thước động vs. tĩnh: Sử dụng mảng hai chiều truyền thống nếu kích thước ma trận cố định và biết trước lúc biên dịch. Sử dụng
vector<vector<int>>
(hoặc các kiểu dữ liệu khác thay choint
) nếu bạn cần kích thước linh hoạt hoặc không biết trước.vector<vector<int>>
thường được khuyến khích trong lập trình C++ hiện đại. - Truyền ma trận vào hàm: Khi truyền ma trận (đặc biệt là
vector<vector<int>>
) vào hàm, hãy sử dụng tham chiếu (&
) hoặc hằng tham chiếu (const&
) để tránh sao chép toàn bộ ma trận, điều này có thể rất tốn kém về thời gian và bộ nhớ đối với ma trận lớn. Sử dụngconst&
khi hàm chỉ đọc dữ liệu mà không thay đổi ma trận gốc (như hàmprintMatrix
hayaddMatrices
). - Kiểm tra kích thước: Đối với các phép toán đòi hỏi kích thước tương thích (như cộng, trừ, nhân ma trận - phép nhân ma trận phức tạp hơn và không được trình bày ở đây), luôn kiểm tra kích thước của các ma trận đầu vào trước khi thực hiện phép toán để tránh lỗi hoặc kết quả không mong muốn.
Comments