Bài 9.4. Bài toán ma trận và các phép biến đổi

Chào mừng bạn quay trở lại với chuỗi bài viết về Cấu trúc dữ liệu và Giải thuật! Sau khi đã tìm hiểu về các cấu trúc dữ liệu tuyến tính và phi tuyến cơ bản, hôm nay chúng ta sẽ nhìn nhận một cấu trúc dữ liệu đặc biệt khác, thường xuất hiện trong nhiều bài toán khoa học máy tính, đồ họa máy tính, xử lý ảnh, và cả trong các thuật toán: đó chính là ma trận.

Ma trận không chỉ đơn thuần là một "bảng số" hình chữ nhật. Nó là một cấu trúc dữ liệu hai chiều mạnh mẽ cho phép biểu diễn các mối quan hệ phức tạp và thực hiện các phép biến đổi (transformations) trên dữ liệu một cách hiệu quả. Trong bài này, chúng ta sẽ khám phá cách biểu diễn ma trận trong lập trình C++ và tập trung vào các giải thuật thực hiện các phép biến đổi cơ bản trên ma trận.

Ma trận là gì?

Về bản chất, một ma trận là một mảng (array) hai chiều gồm các phần tử (thường là số) được sắp xếp theo hàng (rows) và cột (columns). Một ma trận kích thước m x nm hàng và n cột. Mỗi phần tử trong ma trận được xác định bằng chỉ số hàng và chỉ số cột của nó, ví dụ, A[i][j] là phần tử ở hàng thứ i và cột thứ j của ma trận A.

Tại sao ma trận quan trọng trong CTDL/GT?

  1. Biểu diễn dữ liệu: Ma trận có thể biểu diễn các mối quan hệ, ví dụ ma trận kề trong lý thuyết đồ thị biểu diễn kết nối giữa các đỉnh, ma trận điểm ảnh biểu diễn hình ảnh, ma trận trọng số trong các mô hình máy học.
  2. Biểu diễn phép biến đổi: Trong đồ họa máy tính, các phép biến đổi hình học như tịnh tiến (translation), quay (rotation), co giãn (scaling) được biểu diễn bằng các phép nhân ma trận.
  3. Giải hệ phương trình tuyến tính: Nhiều bài toán khoa học và kỹ thuật quy về việc giải hệ phương trình tuyến tính, và ma trận là công cụ cốt lõi để làm điều đó.
  4. Tối ưu hóa: Một số thuật toán tối ưu sử dụng các khái niệm và phép toán ma trận (ví dụ: gradient descent).

Biểu diễn ma trận trong C++

Có nhiều cách để biểu diễn ma trận trong C++, phổ biến nhất là sử dụng mảng hai chiều.

Sử dụng mảng tĩnh hai chiều
int matrix[3][4]; // Ma trận 3 hàng, 4 cột chứa số nguyên

Ưu điểm: Đơn giản, hiệu quả cho ma trận có kích thước cố định đã biết trước khi biên dịch. Nhược điểm: Kích thước phải cố định, không linh hoạt với ma trận có kích thước thay đổi trong quá trình chạy chương trình.

Sử dụng std::vector<std::vector<T>>

Đây là cách linh hoạt và phổ biến hơn trong C++ hiện đại, cho phép tạo ma trận với kích thước động.

#include <vector>

std::vector<std::vector<int>> matrix;

Bạn có thể thay đổi kích thước ma trận này một cách dễ dàng:

int rows = 3;
int cols = 4;
std::vector<std::vector<int>> dynamicMatrix(rows, std::vector<int>(cols));

// Truy cập phần tử:
dynamicMatrix[1][2] = 10; // Hàng 1, cột 2 (chỉ số bắt đầu từ 0)

Ưu điểm: Kích thước động, linh hoạt. Nhược điểm: Có thể kém hiệu quả hơn một chút so với mảng tĩnh về mặt bộ nhớ và tốc độ truy cập (do phân bổ động và có thể không liên tục trong bộ nhớ), nhưng sự khác biệt này thường không đáng kể với các bài toán thông thường và đáng đánh đổi lấy sự linh hoạt.

Trong bài viết này, chúng ta sẽ sử dụng std::vector<std::vector<T>> để minh họa cho tính linh hoạt.

Các phép biến đổi (Giải thuật) cơ bản trên ma trận

Giờ chúng ta sẽ đi sâu vào các "giải thuật" thực hiện các phép biến đổi cơ bản trên ma trận. Đối với mỗi phép toán, chúng ta sẽ xem xét điều kiện thực hiện, quy tắc tính toán và minh họa bằng code C++.

Trước tiên, hãy viết một hàm tiện ích để in ma trận ra màn hình, giúp chúng ta dễ dàng kiểm tra kết quả các phép toán.

#include <iostream>
#include <vector>
#include <iomanip> // For std::setw

// Hàm in ma trận
void printMatrix(const std::vector<std::vector<int>>& matrix) {
    if (matrix.empty()) {
        std::cout << "Ma tran rong." << std::endl;
        return;
    }

    for (const auto& row : matrix) {
        for (int element : row) {
            std::cout << std::setw(5) << element << " "; // setw(5) de can le cho dep
        }
        std::cout << std::endl;
    }
    std::cout << std::endl; // Them dong trong cho de nhin
}

Giải thích code printMatrix:

  • Hàm nhận vào một ma trận (kiểu vector<vector<int>>) và in nó ra console.
  • const& được dùng để truyền tham chiếu không thay đổi, tránh sao chép toàn bộ ma trận tốn bộ nhớ.
  • Kiểm tra ma trận rỗng để tránh lỗi.
  • Sử dụng vòng lặp for (const auto& row : matrix) để duyệt qua từng hàng.
  • Sử dụng vòng lặp for (int element : row) để duyệt qua từng phần tử trong hàng đó.
  • std::setw(5) từ <iomanip> giúp định dạng đầu ra, đảm bảo mỗi số chiếm ít nhất 5 ký tự, làm cho ma trận in ra thẳng hàng và dễ đọc hơn.
1. Phép cộng ma trận

Phép cộng hai ma trận AB chỉ có thể thực hiện được nếu cả hai ma trận có cùng kích thước (cùng số hàng và cùng số cột). Kết quả là một ma trận C có cùng kích thước, trong đó mỗi phần tử C[i][j] là tổng của các phần tử tương ứng từ A và B: C[i][j] = A[i][j] + B[i][j].

Giải thuật:

  1. Kiểm tra xem hai ma trận có cùng số hàng và số cột hay không. Nếu không, trả về lỗi hoặc ma trận rỗng.
  2. Tạo một ma trận kết quả C có cùng kích thước.
  3. Duyệt qua từng phần tử của ma trận theo chỉ số hàng i và chỉ số cột j.
  4. Tại mỗi vị trí [i][j], tính C[i][j] = A[i][j] + B[i][j].
  5. Trả về ma trận kết quả C.

Code minh họa phép cộng:

// Hàm cộng hai ma trận
std::vector<std::vector<int>> addMatrices(const std::vector<std::vector<int>>& matrixA,
                                          const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien cong ma tran: cung kich thuoc
    if (matrixA.empty() || matrixB.empty() ||
        matrixA.size() != matrixB.size() ||
        matrixA[0].size() != matrixB[0].size()) {
        std::cerr << "Loi: Khong the cong hai ma tran khac kich thuoc!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rows = matrixA.size();
    int cols = matrixA[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep cong tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrixA[i][j] + matrixB[i][j];
        }
    }

    return result;
}

Giải thích code addMatrices:

  • Hàm nhận hai ma trận matrixAmatrixB làm tham số (truyền bằng const&).
  • Đầu tiên, kiểm tra kích thước: đảm bảo cả hai ma trận không rỗng, có cùng số hàng (size()) và cùng số cột (matrixA[0].size()). Nếu không thỏa mãn, in thông báo lỗi ra cerr và trả về một ma trận rỗng {}.
  • Lấy số hàng và số cột từ matrixA.
  • Khởi tạo ma trận result với kích thước rows x cols.
  • Sử dụng hai vòng lặp lồng nhau: vòng ngoài duyệt qua các hàng (i từ 0 đến rows-1), vòng trong duyệt qua các cột (j từ 0 đến cols-1).
  • Bên trong vòng lặp, thực hiện phép cộng matrixA[i][j] + matrixB[i][j] và lưu kết quả vào result[i][j].
  • Cuối cùng, trả về ma trận result.
2. Phép trừ ma trận

Phép trừ ma trận A cho ma trận B cũng yêu cầu cả hai ma trận có cùng kích thước. Kết quả là ma trận C cùng kích thước, với C[i][j] = A[i][j] - B[i][j].

Giải thuật: Tương tự như phép cộng, chỉ thay phép cộng bằng phép trừ.

Code minh họa phép trừ:

// Hàm trừ hai ma trận
std::vector<std::vector<int>> subtractMatrices(const std::vector<std::vector<int>>& matrixA,
                                               const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien tru ma tran: cung kich thuoc
     if (matrixA.empty() || matrixB.empty() ||
        matrixA.size() != matrixB.size() ||
        matrixA[0].size() != matrixB[0].size()) {
        std::cerr << "Loi: Khong the tru hai ma tran khac kich thuoc!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rows = matrixA.size();
    int cols = matrixA[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep tru tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrixA[i][j] - matrixB[i][j];
        }
    }

    return result;
}

Giải thích code subtractMatrices:

  • Cấu trúc hoàn toàn tương tự addMatrices.
  • Điểm khác biệt duy nhất là phép tính bên trong vòng lặp lồng nhau: result[i][j] = matrixA[i][j] - matrixB[i][j];.
  • Phần kiểm tra điều kiện và khởi tạo ma trận kết quả là giống nhau.
3. Phép nhân ma trận với một số vô hướng (Scalar Multiplication)

Phép nhân một ma trận A với một số vô hướng k tạo ra một ma trận mới C có cùng kích thước với A, trong đó mỗi phần tử C[i][j] là tích của k và phần tử tương ứng của A: C[i][j] = k * A[i][j].

Giải thuật:

  1. Tạo một ma trận kết quả C có cùng kích thước với A.
  2. Duyệt qua từng phần tử của ma trận A theo chỉ số hàng i và chỉ số cột j.
  3. Tại mỗi vị trí [i][j], tính C[i][j] = k * A[i][j].
  4. Trả về ma trận kết quả C.

Code minh họa phép nhân vô hướng:

// Hàm nhân ma trận voi mot so vo huong
std::vector<std::vector<int>> scalarMultiply(const std::vector<std::vector<int>>& matrix, int scalar) {
    if (matrix.empty()) {
        return {}; // Tra ve ma tran rong neu ma tran dau vao rong
    }

    int rows = matrix.size();
    int cols = matrix[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep nhan vo huong tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrix[i][j] * scalar;
        }
    }

    return result;
}

Giải thích code scalarMultiply:

  • Hàm nhận ma trận matrix và số vô hướng scalar.
  • Kiểm tra ma trận đầu vào có rỗng không.
  • Lấy kích thước của ma trận đầu vào và tạo ma trận result cùng kích thước.
  • Sử dụng hai vòng lặp lồng nhau để duyệt qua từng phần tử.
  • Thực hiện phép nhân matrix[i][j] * scalar và lưu vào result[i][j].
  • Trả về ma trận result.
4. Phép nhân hai ma trận

Phép nhân hai ma trận A (kích thước m x n) và B (kích thước p x q) chỉ có thể thực hiện được nếu số cột của ma trận A bằng số hàng của ma trận B (tức là n == p). Kết quả là một ma trận C có kích thước m x q.

Mỗi phần tử C[i][j] của ma trận kết quả được tính bằng tổng của tích các phần tử tương ứng trên hàng i của ma trận A và cột j của ma trận B. Công thức: C[i][j] = Sum(A[i][k] * B[k][j]) với k chạy từ 0 đến n-1 (hoặc p-1).

Giải thuật:

  1. Kiểm tra điều kiện nhân ma trận: số cột của A có bằng số hàng của B không (A.cols == B.rows)? Nếu không, trả về lỗi hoặc ma trận rỗng.
  2. Xác định kích thước của ma trận kết quả C: số hàng bằng số hàng của A (A.rows), số cột bằng số cột của B (B.cols).
  3. Tạo ma trận kết quả C với kích thước A.rows x B.cols.
  4. Sử dụng ba vòng lặp lồng nhau:
    • Vòng ngoài cùng duyệt qua các hàng i của ma trận kết quả (từ 0 đến A.rows - 1).
    • Vòng giữa duyệt qua các cột j của ma trận kết quả (từ 0 đến B.cols - 1).
    • Vòng trong cùng duyệt qua chỉ số k để tính tổng (từ 0 đến A.cols - 1 hoặc B.rows - 1).
  5. Bên trong vòng lặp thứ ba, tính C[i][j] += A[i][k] * B[k][j]. Khởi tạo C[i][j] bằng 0 trước khi bắt đầu vòng lặp thứ ba.
  6. Trả về ma trận kết quả C.

Code minh họa phép nhân ma trận: Đây là giải thuật cơ bản và có độ phức tạp $O(m \times n \times q)$ với ma trận A m x n và ma trận B n x q.

// Hàm nhân hai ma trận
std::vector<std::vector<int>> multiplyMatrices(const std::vector<std::vector<int>>& matrixA,
                                               const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien nhan ma tran: so cot A == so hang B
    if (matrixA.empty() || matrixB.empty() || matrixA[0].size() != matrixB.size()) {
        std::cerr << "Loi: Khong the nhan hai ma tran voi kich thuoc khong hop le!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rowsA = matrixA.size();
    int colsA = matrixA[0].size(); // Dong thoi la so hang B
    int colsB = matrixB[0].size();

    // Kich thuoc ma tran ket qua la rowsA x colsB
    std::vector<std::vector<int>> result(rowsA, std::vector<int>(colsB, 0)); // Khoi tao bang 0

    // Thuc hien phep nhan
    for (int i = 0; i < rowsA; ++i) { // Duyet qua cac hang cua A (va ket qua C)
        for (int j = 0; j < colsB; ++j) { // Duyet qua cac cot cua B (va ket qua C)
            for (int k = 0; k < colsA; ++k) { // Duyet qua cac phan tu de tinh tong
                result[i][j] += matrixA[i][k] * matrixB[k][j];
            }
        }
    }

    return result;
}

Giải thích code multiplyMatrices:

  • Hàm nhận hai ma trận matrixAmatrixB.
  • Kiểm tra điều kiện: matrixA[0].size() (số cột của A) có bằng matrixB.size() (số hàng của B) không? Nếu không, in lỗi và trả về ma trận rỗng.
  • Xác định kích thước của ma trận kết quả: rowsA là số hàng của A, colsB là số cột của B. Số cột của A (colsA) chính là số hàng của B, dùng làm giới hạn cho vòng lặp trong cùng.
  • Khởi tạo ma trận result kích thước rowsA x colsB, với tất cả các phần tử ban đầu bằng 0 (quan trọng cho phép tính tổng tích lũy).
  • Có ba vòng lặp lồng nhau:
    • i: Lặp qua các hàng của ma trận kết quả C (và ma trận A).
    • j: Lặp qua các cột của ma trận kết quả C (và ma trận B).
    • k: Lặp qua các phần tử trung gian để tính tổng tích (từ 0 đến colsA-1).
  • Bên trong vòng lặp thứ ba, công thức result[i][j] += matrixA[i][k] * matrixB[k][j]; thực hiện việc lấy phần tử A[i][k] (hàng i của A, cột k của A) nhân với B[k][j] (hàng k của B, cột j của B) và cộng dồn vào result[i][j].
  • Trả về ma trận result sau khi đã tính toán xong tất cả các phần tử.
5. Phép chuyển vị ma trận (Transpose)

Phép chuyển vị của một ma trận A (kích thước m x n) là một ma trận mới A^T (đọc là A chuyển vị) có kích thước n x m. Các hàng của A^T là các cột của A, và các cột của A^T là các hàng của A. Nói cách khác, phần tử ở vị trí [i][j] của A^T là phần tử ở vị trí [j][i] của A: A^T[i][j] = A[j][i].

Giải thuật:

  1. Xác định kích thước của ma trận kết quả A^T: số hàng bằng số cột của A (A.cols), số cột bằng số hàng của A (A.rows).
  2. Tạo ma trận kết quả result với kích thước mới.
  3. Duyệt qua từng phần tử của ma trận A theo chỉ số hàng i và chỉ số cột j.
  4. Tại mỗi vị trí [i][j] của A, gán giá trị đó cho vị trí [j][i] của ma trận kết quả: result[j][i] = matrix[i][j].
  5. Trả về ma trận kết quả result.

Code minh họa phép chuyển vị:

// Hàm chuyển vị ma trận
std::vector<std::vector<int>> transposeMatrix(const std::vector<std::vector<int>>& matrix) {
    if (matrix.empty()) {
        return {}; // Tra ve ma tran rong neu ma tran dau vao rong
    }

    int rows = matrix.size();
    int cols = matrix[0].size();

    // Kich thuoc ma tran chuyen vi la cols x rows
    std::vector<std::vector<int>> result(cols, std::vector<int>(rows));

    // Thuc hien phep chuyen vi
    for (int i = 0; i < rows; ++i) { // Duyet qua cac hang cua ma tran goc
        for (int j = 0; j < cols; ++j) { // Duyet qua cac cot cua ma tran goc
            result[j][i] = matrix[i][j]; // Gan phan tu A[i][j] vao A^T[j][i]
        }
    }

    return result;
}

Giải thích code transposeMatrix:

  • Hàm nhận ma trận matrix.
  • Kiểm tra ma trận đầu vào có rỗng không.
  • Lấy số hàng (rows) và số cột (cols) của ma trận đầu vào.
  • Quan trọng: Khởi tạo ma trận result với kích thước đảo ngược: cols hàng và rows cột.
  • Sử dụng hai vòng lặp lồng nhau để duyệt qua ma trận gốc.
  • Bên trong vòng lặp, thay vì gán result[i][j] = matrix[i][j], chúng ta thực hiện gán result[j][i] = matrix[i][j];. Điều này hoán đổi chỉ số hàng và cột, thực hiện phép chuyển vị.
  • Trả về ma trận result.

Ví dụ tổng hợp

Hãy cùng xem một ví dụ sử dụng tất cả các hàm chúng ta vừa xây dựng trong hàm main.

#include <iostream>
#include <vector>
#include <iomanip> // For std::setw
#include <stdexcept> // For exception handling (optional, but good practice)

// --- CAC HAM DA VIET O TREN ---
// printMatrix
// addMatrices
// subtractMatrices
// scalarMultiply
// multiplyMatrices
// transposeMatrix
// ----------------------------

// Copy/paste the functions printMatrix, addMatrices, subtractMatrices, scalarMultiply, multiplyMatrices, transposeMatrix here

// Hàm in ma trận
void printMatrix(const std::vector<std::vector<int>>& matrix) {
    if (matrix.empty()) {
        std::cout << "Ma tran rong." << std::endl;
        return;
    }

    // Kiem tra ma tran co rong cot khong (truong hop dac biet)
    if (matrix[0].empty()){
         std::cout << "Ma tran rong." << std::endl;
        return;
    }

    for (const auto& row : matrix) {
        for (int element : row) {
            std::cout << std::setw(5) << element << " "; // setw(5) de can le cho dep
        }
        std::cout << std::endl;
    }
    std::cout << std::endl; // Them dong trong cho de nhin
}

// Hàm cộng hai ma trận
std::vector<std::vector<int>> addMatrices(const std::vector<std::vector<int>>& matrixA,
                                          const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien cong ma tran: cung kich thuoc
    if (matrixA.empty() || matrixB.empty() ||
        matrixA.size() != matrixB.size() ||
        matrixA[0].size() != matrixB[0].size()) {
        std::cerr << "Loi cong: Khong the cong hai ma tran khac kich thuoc!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rows = matrixA.size();
    int cols = matrixA[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep cong tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrixA[i][j] + matrixB[i][j];
        }
    }

    return result;
}

// Hàm trừ hai ma trận
std::vector<std::vector<int>> subtractMatrices(const std::vector<std::vector<int>>& matrixA,
                                               const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien tru ma tran: cung kich thuoc
     if (matrixA.empty() || matrixB.empty() ||
        matrixA.size() != matrixB.size() ||
        matrixA[0].size() != matrixB[0].size()) {
        std::cerr << "Loi tru: Khong the tru hai ma tran khac kich thuoc!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rows = matrixA.size();
    int cols = matrixA[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep tru tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrixA[i][j] - matrixB[i][j];
        }
    }

    return result;
}

// Hàm nhân ma trận voi mot so vo huong
std::vector<std::vector<int>> scalarMultiply(const std::vector<std::vector<int>>& matrix, int scalar) {
    if (matrix.empty()) {
        return {}; // Tra ve ma tran rong neu ma tran dau vao rong
    }

    int rows = matrix.size();
    int cols = matrix[0].size();
    std::vector<std::vector<int>> result(rows, std::vector<int>(cols));

    // Thuc hien phep nhan vo huong tung phan tu
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            result[i][j] = matrix[i][j] * scalar;
        }
    }

    return result;
}

// Hàm nhân hai ma trận
std::vector<std::vector<int>> multiplyMatrices(const std::vector<std::vector<int>>& matrixA,
                                               const std::vector<std::vector<int>>& matrixB) {
    // Kiem tra dieu kien nhan ma tran: so cot A == so hang B
    if (matrixA.empty() || matrixB.empty() || matrixA[0].size() != matrixB.size()) {
        std::cerr << "Loi nhan: Khong the nhan hai ma tran voi kich thuoc khong hop le!" << std::endl;
        return {}; // Tra ve ma tran rong neu loi
    }

    int rowsA = matrixA.size();
    int colsA = matrixA[0].size(); // Dong thoi la so hang B
    int colsB = matrixB[0].size();

    // Kich thuoc ma tran ket qua la rowsA x colsB
    std::vector<std::vector<int>> result(rowsA, std::vector<int>(colsB, 0)); // Khoi tao bang 0

    // Thuc hien phep nhan
    for (int i = 0; i < rowsA; ++i) { // Duyet qua cac hang cua A (va ket qua C)
        for (int j = 0; j < colsB; ++j) { // Duyet qua cac cot cua B (va ket qua C)
            for (int k = 0; k < colsA; ++k) { // Duyet qua cac phan tu de tinh tong
                result[i][j] += matrixA[i][k] * matrixB[k][j];
            }
        }
    }

    return result;
}

// Hàm chuyển vị ma trận
std::vector<std::vector<int>> transposeMatrix(const std::vector<std::vector<int>>& matrix) {
    if (matrix.empty() || matrix[0].empty()) { // Kiem tra ca ma tran va hang dau tien co rong khong
        std::cout << "Ma tran chuyen vi rong." << std::endl; // In ra thong bao thay vi cerr
        return {}; // Tra ve ma tran rong neu ma tran dau vao rong
    }

    int rows = matrix.size();
    int cols = matrix[0].size();

    // Kich thuoc ma tran chuyen vi la cols x rows
    std::vector<std::vector<int>> result(cols, std::vector<int>(rows));

    // Thuc hien phep chuyen vi
    for (int i = 0; i < rows; ++i) { // Duyet qua cac hang cua ma tran goc
        for (int j = 0; j < cols; ++j) { // Duyet qua cac cot cua ma tran goc
            result[j][i] = matrix[i][j]; // Gan phan tu A[i][j] vao A^T[j][i]
        }
    }

    return result;
}


int main() {
    // Khai bao va khoi tao cac ma tran mau
    std::vector<std::vector<int>> matrixA = {
        {1, 2, 3},
        {4, 5, 6}
    }; // Kich thuoc 2x3

    std::vector<std::vector<int>> matrixB = {
        {7, 8, 9},
        {10, 11, 12}
    }; // Kich thuoc 2x3

    std::vector<std::vector<int>> matrixC = {
        {1, 2},
        {3, 4},
        {5, 6}
    }; // Kich thuoc 3x2

    std::vector<std::vector<int>> matrixD = {
        {2, 0, 1},
        {3, 1, 0},
        {0, 0, 4}
    }; // Kich thuoc 3x3

    std::vector<std::vector<int>> matrixE = {
        {5, 2},
        {1, 3}
    }; // Kich thuoc 2x2


    std::cout << "--- Ma tran A (2x3) ---" << std::endl;
    printMatrix(matrixA);

    std::cout << "--- Ma tran B (2x3) ---" << std::endl;
    printMatrix(matrixB);

    std::cout << "--- Ma tran C (3x2) ---" << std::endl;
    printMatrix(matrixC);

     std::cout << "--- Ma tran D (3x3) ---" << std::endl;
    printMatrix(matrixD);

    std::cout << "--- Ma tran E (2x2) ---" << std::endl;
    printMatrix(matrixE);


    // --- Minh hoa phep cong ---
    std::cout << "--- Phep cong: A + B ---" << std::endl;
    std::vector<std::vector<int>> sumAB = addMatrices(matrixA, matrixB);
    printMatrix(sumAB);

    std::cout << "--- Phep cong: A + C (loi kich thuoc) ---" << std::endl;
    std::vector<std::vector<int>> sumAC = addMatrices(matrixA, matrixC); // Se in ra thong bao loi
    printMatrix(sumAC); // Se in ma tran rong


    // --- Minh hoa phep tru ---
    std::cout << "--- Phep tru: B - A ---" << std::endl;
    std::vector<std::vector<int>> diffBA = subtractMatrices(matrixB, matrixA);
    printMatrix(diffBA);

     std::cout << "--- Phep tru: D - A (loi kich thuoc) ---" << std::endl;
    std::vector<std::vector<int>> diffDA = subtractMatrices(matrixD, matrixA); // Se in ra thong bao loi
    printMatrix(diffDA); // Se in ma tran rong


    // --- Minh hoa phep nhan vo huong ---
    int scalar = 5;
    std::cout << "--- Phep nhan vo huong: A * " << scalar << " ---" << std::endl;
    std::vector<std::vector<int>> scaledA = scalarMultiply(matrixA, scalar);
    printMatrix(scaledA);


    // --- Minh hoa phep nhan ma tran ---
    // A (2x3) * C (3x2) = Result (2x2)
    std::cout << "--- Phep nhan ma tran: A (2x3) * C (3x2) ---" << std::endl;
    std::vector<std::vector<int>> prodAC = multiplyMatrices(matrixA, matrixC);
    printMatrix(prodAC);

    // D (3x3) * D (3x3) = Result (3x3)
    std::cout << "--- Phep nhan ma tran: D (3x3) * D (3x3) ---" << std::endl;
    std::vector<std::vector<int>> prodDD = multiplyMatrices(matrixD, matrixD);
    printMatrix(prodDD);

    // A (2x3) * D (3x3) = Result (2x3)
    std::cout << "--- Phep nhan ma tran: A (2x3) * D (3x3) ---" << std::endl;
    std::vector<std::vector<int>> prodAD = multiplyMatrices(matrixA, matrixD);
    printMatrix(prodAD);

    // A (2x3) * E (2x2) (loi kich thuoc)
    std::cout << "--- Phep nhan ma tran: A (2x3) * E (2x2) (loi kich thuoc) ---" << std::endl;
    std::vector<std::vector<int>> prodAE = multiplyMatrices(matrixA, matrixE); // Se in ra thong bao loi
    printMatrix(prodAE); // Se in ma tran rong


    // --- Minh hoa phep chuyen vi ---
    // Chuyen vi A (2x3) -> A^T (3x2)
    std::cout << "--- Phep chuyen vi: A^T (tu A 2x3) ---" << std::endl;
    std::vector<std::vector<int>> transposeA = transposeMatrix(matrixA);
    printMatrix(transposeA);

     // Chuyen vi D (3x3) -> D^T (3x3)
    std::cout << "--- Phep chuyen vi: D^T (tu D 3x3) ---" << std::endl;
    std::vector<std::vector<int>> transposeD = transposeMatrix(matrixD);
    printMatrix(transposeD);


    return 0;
}

Giải thích code main:

  • Bao gồm các header cần thiết (iostream, vector, iomanip).
  • Khai báo và khởi tạo một vài ma trận mẫu với các kích thước khác nhau (matrixA, matrixB, matrixC, matrixD, matrixE).
  • Gọi hàm printMatrix để hiển thị các ma trận gốc.
  • Thực hiện lần lượt các phép toán: cộng, trừ, nhân vô hướng, nhân ma trận, chuyển vị.
  • Đối với mỗi phép toán, gọi hàm tương ứng (addMatrices, subtractMatrices, v.v.) với các ma trận mẫu đã tạo.
  • In ra ma trận kết quả bằng printMatrix.
  • Đặc biệt, minh họa cả các trường hợp cố tình vi phạm điều kiện của phép toán (ví dụ: cộng hai ma trận khác kích thước, nhân hai ma trận có số cột/hàng không khớp) để cho thấy cách các hàm xử lý lỗi và trả về ma trận rỗng.

Bài tập ví dụ:

Liệt kê các số nguyên tố trong mảng 2 chiều.

Cho ma trận cỡ NxM gồm N hàng, mỗi hàng M cột. Hãy liệt kê các số nguyên tố theo từng hàng trong ma trận.

Input Format

Dòng đầu tiên là 2 số N và M. N dòng tiếp theo mỗi dòng có M số. (1≤n,m≤500; Các phần tử trong ma trận là số dương không quá 10^9)

Constraints

.

Output Format

In ra các số nguyên tố trong ma trận theo từng dòng.

Ví dụ:

Dữ liệu vào
3 4
1 3 55 31
4 5 7 11
19 6 88 70
Dữ liệu ra
3 31
5 7 11
19

Chào bạn, đây là hướng dẫn giải bài "Liệt kê các số nguyên tố trong mảng 2 chiều" bằng C++ một cách ngắn gọn:

  1. Đọc dữ liệu: Đọc hai số N và M đầu tiên.
  2. Hàm kiểm tra số nguyên tố: Xây dựng một hàm bool isPrime(int num) để kiểm tra xem một số có phải là số nguyên tố hay không.
    • Trong hàm isPrime:
      • Kiểm tra các trường hợp đặc biệt: num <= 1 không phải, num == 2 là.
      • Nếu num > 2 và là số chẵn, không phải là số nguyên tố.
      • Đối với các số lẻ lớn hơn 2, duyệt các ước số từ 3 đến căn bậc hai của num, chỉ xét các số lẻ (i += 2). Nếu tìm thấy ước số nào (num % i == 0), trả về false.
      • Nếu vòng lặp kết thúc mà không tìm thấy ước số, trả về true.
      • Lưu ý: Khi tính bình phương i * i trong điều kiện vòng lặp, hãy sử dụng kiểu dữ liệu long long để tránh tràn số nếu num lớn.
  3. Duyệt ma trận và in kết quả:
    • Sử dụng hai vòng lặp lồng nhau để duyệt qua từng hàng (từ 0 đến N-1) và từng cột (từ 0 đến M-1).
    • Trong vòng lặp duyệt cột của mỗi hàng:
      • Đọc giá trị của phần tử hiện tại.
      • Gọi hàm isPrime để kiểm tra xem số đó có phải là số nguyên tố không.
      • Nếu là số nguyên tố, in số đó ra. Cần xử lý để các số nguyên tố trên cùng một hàng cách nhau bởi khoảng trắng. Bạn có thể dùng một biến cờ hoặc biến đếm để biết khi nào cần in khoảng trắng trước số (chỉ in khoảng trắng trước các số nguyên tố tiếp theo trong cùng một hàng).
    • Sau khi kết thúc vòng lặp duyệt hết M cột của một hàng, in ký tự xuống dòng (\n) để xuống dòng cho hàng tiếp theo.
  4. Thư viện cần dùng: iostream cho nhập/xuất và cmath cho hàm sqrt.

Làm thêm nhiều bài tập miễn phí tại đây

Comments

There are no comments at the moment.