Bài 25.1: Khái niệm và sử dụng mảng 2 chiều trong C++

Chào mừng trở lại với chuỗi bài viết về C++! Hôm nay, chúng ta sẽ nâng cấp kiến thức về mảng của mình bằng cách khám phá một cấu trúc dữ liệu mạnh mẽ và cực kỳ hữu ích: mảng 2 chiều (2D Array). Nếu mảng 1 chiều giúp bạn lưu trữ một danh sách các giá trị, thì mảng 2 chiều cho phép bạn biểu diễn dữ liệu dưới dạng bảng hoặc lưới - một thứ cực kỳ phổ biến trong thế giới thực!

Hãy cùng đi sâu vào thế giới của các bảng số trong C++!

Mảng 2 chiều là gì?

Hãy tưởng tượng mảng 1 chiều là một danh sách các ngăn kéo được xếp thẳng hàng. Mỗi ngăn kéo chứa một giá trị.

[ Ngăn 0 ] [ Ngăn 1 ] [ Ngăn 2 ] [ Ngăn 3 ] ...

Còn mảng 2 chiều? Nó giống như một bảng các ngăn kéo được sắp xếp thành nhiều hàng và nhiều cột. Mỗi ngăn kéo vẫn chứa một giá trị, nhưng bây giờ bạn cần biết nó nằm ở hàng nàocột nào để truy cập.

      Cột 0   Cột 1   Cột 2   Cột 3 ...
Hàng 0 [     ] [     ] [     ] [     ]
Hàng 1 [     ] [     ] [     ] [     ]
Hàng 2 [     ] [     ] [     ] [     ]
...

Về bản chất, mảng 2 chiều trong C++ có thể được coi là một mảng của các mảng 1 chiều. Mỗi "phần tử" của mảng chính là một mảng 1 chiều (một hàng).

Cấu trúc này cực kỳ hữu ích khi bạn cần làm việc với:

  • Ma trận trong toán học.
  • Bảng tính hoặc dữ liệu dạng bảng.
  • Bản đồ hoặc lưới trò chơi.
  • Hình ảnh (lưới điểm ảnh).
  • Và nhiều ứng dụng khác cần tổ chức dữ liệu theo cấu trúc hàng và cột.

Khai báo mảng 2 chiều

Để khai báo một mảng 2 chiều trong C++, bạn cần chỉ định kiểu dữ liệu của các phần tử, tên mảng, số lượng hàng và số lượng cột. Cú pháp như sau:

kieu_du_lieu ten_mang[so_hang][so_cot];

Lưu ý quan trọng: Đối với mảng C-style (mảng tĩnh) được khai báo theo cách này, so_hangso_cot phải là các giá trị hằng số nguyên dương (hoặc biểu thức có thể tính toán tại thời điểm biên dịch).

Ví dụ:

#include <iostream>

int main() {
    using namespace std;
    int maTran[3][4];
    char luoi[5][10];

    const int HANG = 2;
    const int COT = 5;
    double duLieu[HANG][COT];

    cout << "Da khai bao cac mang 2 chieu." << endl;

    return 0;
}
Da khai bao cac mang 2 chieu.

Khởi tạo mảng 2 chiều

Bạn có thể gán giá trị cho các phần tử của mảng 2 chiều ngay khi khai báo. Có một vài cách phổ biến:

  1. Khởi tạo đầy đủ bằng danh sách lồng nhau: Sử dụng dấu ngoặc nhọn {} lồng nhau. Mỗi cặp {} bên trong biểu diễn một hàng.

    #include <iostream>
    #include <iomanip>
    
    int main() {
        using namespace std;
        int mt[2][3] = {
            {1, 2, 3},
            {4, 5, 6}
        };
    
        cout << "Mang simpleMatrix da khoi tao:" << endl;
        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 3; ++j) {
                cout << setw(4) << mt[i][j];
            }
            cout << endl;
        }
    
        return 0;
    }
    
    Mang simpleMatrix da khoi tao:
       1   2   3
       4   5   6
  2. Khởi tạo bằng danh sách phẳng: Bạn có thể cung cấp tất cả các giá trị trong một danh sách duy nhất. Trình biên dịch sẽ tự động điền vào mảng theo thứ tự từng hàng một.

    #include <iostream>
    #include <iomanip>
    
    int main() {
        using namespace std;
        int mt[3][2] = {10, 20, 30, 40, 50, 60};
    
        cout << "Mang flatMatrix da khoi tao:" << endl;
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 2; ++j) {
                cout << setw(4) << mt[i][j];
            }
            cout << endl;
        }
    
        return 0;
    }
    
    Mang flatMatrix da khoi tao:
      10  20
      30  40
      50  60
  3. Khởi tạo một phần: Nếu bạn cung cấp ít giá trị hơn tổng số phần tử, các phần tử còn lại sẽ được khởi tạo về giá trị 0 (đối với kiểu số) hoặc giá trị mặc định (đối với các kiểu dữ liệu khác).

    #include <iostream>
    #include <iomanip>
    
    int main() {
        using namespace std;
        int mt[3][3] = {
            {1, 1},
            {2},
            {0, 0, 3}
        };
    
        cout << "Mang partialMatrix da khoi tao mot phan:" << endl;
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                cout << setw(4) << mt[i][j];
            }
            cout << endl;
        }
    
        return 0;
    }
    
    Mang partialMatrix da khoi tao mot phan:
       1   1   0
       2   0   0
       0   0   3
  4. Bỏ qua số hàng khi khởi tạo đầy đủ: Nếu bạn cung cấp danh sách khởi tạo lồng nhau đầy đủ, bạn có thể bỏ qua việc chỉ định số hàng. Trình biên dịch sẽ tự động tính toán số hàng dựa trên danh sách khởi tạo. Tuy nhiên, bạn vẫn PHẢI chỉ định số cột.

    #include <iostream>
    #include <iomanip>
    
    int main() {
        using namespace std;
        int mt[][4] = {
            {100, 200, 300, 400},
            {500, 600, 700, 800}
        };
    
        const int HANG = sizeof(mt) / sizeof(mt[0]);
        const int COT = sizeof(mt[0]) / sizeof(mt[0][0]);
    
        cout << "Mang autoRowsMatrix (tu dong tinh so hang) da khoi tao:" << endl;
        cout << "So hang thuc te: " << HANG << endl;
        cout << "So cot: " << COT << endl;
    
        for (int i = 0; i < HANG; ++i) {
            for (int j = 0; j < COT; ++j) {
                cout << setw(6) << mt[i][j];
            }
            cout << endl;
        }
    
        return 0;
    }
    
    Mang autoRowsMatrix (tu dong tinh so hang) da khoi tao:
    So hang thuc te: 2
    So cot: 4
       100   200   300   400
       500   600   700   800

Truy cập các phần tử

Để truy cập (đọc hoặc ghi) một phần tử cụ thể trong mảng 2 chiều, bạn sử dụng tên mảng theo sau là hai cặp ngoặc vuông [], một cho chỉ số hàng và một cho chỉ số cột:

ten_mang[chi_so_hang][chi_so_cot]

Giống như mảng 1 chiều, các chỉ số (indices) bắt đầu từ 0.

  • chi_so_hang đi từ 0 đến so_hang - 1.
  • chi_so_cot đi từ 0 đến so_cot - 1.

Ví dụ:

#include <iostream>

int main() {
    using namespace std;
    int b[4][4] = {
        {0, 0, 0, 0},
        {0, 1, 0, 0},
        {0, 0, 1, 0},
        {0, 0, 0, 0}
    };

    cout << "Phan tu o hang 1, cot 1 la: " << b[1][1] << endl;

    b[3][0] = 9;
    cout << "Phan tu o hang 3, cot 0 sau khi thay doi: " << b[3][0] << endl;

    cout << "Phan tu o hang 0, cot 3 la: " << b[0][3] << endl;

    return 0;
}
Phan tu o hang 1, cot 1 la: 1
Phan tu o hang 3, cot 0 sau khi thay doi: 9
Phan tu o hang 0, cot 3 la: 0

Duyệt (lặp qua) mảng 2 chiều

Cách phổ biến nhất để xử lý tất cả các phần tử trong mảng 2 chiều là sử dụng hai vòng lặp for lồng nhau: một vòng lặp bên ngoài để duyệt qua các hàng và một vòng lặp bên trong để duyệt qua các cột trong mỗi hàng.

#include <iostream>
#include <iomanip>

int main() {
    using namespace std;
    const int HANG = 3;
    const int COT = 5;
    int mt[HANG][COT];

    for (int i = 0; i < HANG; ++i) {
        for (int j = 0; j < COT; ++j) {
            mt[i][j] = i * 10 + j;
        }
    }

    cout << "Noi dung mang 2 chieu:" << endl;
    for (int i = 0; i < HANG; ++i) {
        for (int j = 0; j < COT; ++j) {
            cout << setw(4) << mt[i][j];
        }
        cout << endl;
    }

    return 0;
}
Noi dung mang 2 chieu:
   0   1   2   3   4
  10  11  12  13  14
  20  21  22  23  24

Mảng 2 chiều và hàm

Khi truyền một mảng 2 chiều (kiểu C-style tĩnh) vào một hàm, bạn gặp một ràng buộc đặc biệt: Bạn phải chỉ định kích thước của tất cả các chiều trừ chiều đầu tiên (số hàng). Điều này là do cách C++ quản lý bộ nhớ cho mảng. Để trình biên dịch tính toán được vị trí của một phần tử [i][j], nó cần biết có bao nhiêu cột trong mỗi hàng.

Cú pháp truyền mảng 2 chiều vào hàm:

void ten_ham(kieu_du_lieu ten_bien_mang[][so_cot], int so_hang);
// Hoặc nếu bạn biết cả 2 kích thước cố định
void ten_ham(kieu_du_lieu ten_bien_mang[so_hang_co_dinh][so_cot_co_dinh]);

Ví dụ:

#include <iostream>
#include <iomanip>

void inMT(int a[][4], int h) {
    using namespace std;
    const int c = 4;

    cout << "--- Trong ham inMT ---" << endl;
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < c; ++j) {
            cout << setw(4) << a[i][j];
        }
        cout << endl;
    }
    cout << "-----------------------------" << endl;
}

void xuLyMTCoDinh(double b[2][3]) {
    using namespace std;
    cout << "--- Trong ham xuLyMTCoDinh (2x3) ---" << endl;
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            cout << setprecision(1) << fixed << setw(5) << b[i][j];
        }
        cout << endl;
    }
    cout << "------------------------------------------" << endl;
}


int main() {
    using namespace std;
    int mt1[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    double mt2[2][3] = {
        {1.1, 2.2, 3.3},
        {4.4, 5.5, 6.6}
    };

    inMT(mt1, 3);
    xuLyMTCoDinh(mt2);

    return 0;
}
--- Trong ham inMT ---
   1   2   3   4
   5   6   7   8
   9  10  11  12
-----------------------------
--- Trong ham xuLyMTCoDinh (2x3) ---
  1.1  2.2  3.3
  4.4  5.5  6.6
------------------------------------------

Một số ví dụ ứng dụng cơ bản

Hãy xem xét một vài ví dụ đơn giản về cách sử dụng mảng 2 chiều để thực hiện các tác vụ cơ bản.

Ví dụ 1: Tính tổng tất cả các phần tử
#include <iostream>

int main() {
    using namespace std;
    const int HANG = 3;
    const int COT = 3;
    int mt[HANG][COT] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    long long tong = 0;

    for (int i = 0; i < HANG; ++i) {
        for (int j = 0; j < COT; ++j) {
            tong += mt[i][j];
        }
    }

    cout << "Tong tat ca cac phan tu trong mang la: " << tong << endl;

    return 0;
}
Tong tat ca cac phan tu trong mang la: 45
Ví dụ 2: Tìm phần tử lớn nhất
#include <iostream>
#include <limits>

int main() {
    using namespace std;
    const int HANG = 4;
    const int COT = 5;
    int dl[HANG][COT] = {
        {10, -5, 20, 8, 15},
        {3, 12, 2, 18, 7},
        {-1, 9, 25, 6, 11},
        {14, 4, 19, 1, 22}
    };

    int maxV = numeric_limits<int>::min();

    for (int i = 0; i < HANG; ++i) {
        for (int j = 0; j < COT; ++j) {
            if (dl[i][j] > maxV) {
                maxV = dl[i][j];
            }
        }
    }

    cout << "Phan tu lon nhat trong mang la: " << maxV << endl;

    return 0;
}
Phan tu lon nhat trong mang la: 25
Ví dụ 3: Tính tổng của một hàng cụ thể
#include <iostream>

int main() {
    using namespace std;
    const int HANG = 3;
    const int COT = 4;
    int dsBan[HANG][COT] = {
        {100, 150, 120, 200},
        {250, 180, 300, 220},
        {110, 160, 140, 190}
    };

    int hMucTieu = 1;
    int tongHang = 0;

    if (hMucTieu >= 0 && hMucTieu < HANG) {
        for (int j = 0; j < COT; ++j) {
            tongHang += dsBan[hMucTieu][j];
        }
        cout << "Tong doanh so cua chi nhanh " << hMucTieu + 1 << " (Hang " << hMucTieu << ") la: " << tongHang << endl;
    } else {
        cout << "Chi so hang khong hop le!" << endl;
    }

    return 0;
}
Tong doanh so cua chi nhanh 2 (Hang 1) la: 950
Ví dụ 4: Nhập dữ liệu từ người dùng

Bạn cũng có thể sử dụng vòng lặp lồng nhau để đọc dữ liệu nhập từ người dùng và lưu vào mảng 2 chiều.

#include <iostream>

int main() {
    using namespace std;
    const int HANG = 2;
    const int COT = 3;
    int mtNguoiDung[HANG][COT];

    cout << "Nhap " << HANG * COT << " so nguyen cho mang " << HANG << "x" << COT << ":" << endl;

    for (int i = 0; i < HANG; ++i) {
        cout << "Nhap cac phan tu cho hang " << i + 1 << ": ";
        for (int j = 0; j < COT; ++j) {
            cin >> mtNguoiDung[i][j];
        }
    }

    cout << "\nMang ban vua nhap la:" << endl;
    for (int i = 0; i < HANG; ++i) {
        for (int j = 0; j < COT; ++j) {
            cout << mtNguoiDung[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

Input mẫu:

1 2 3
4 5 6

Output tương ứng:

Nhap 6 so nguyen cho mang 2x3:
Nhap cac phan tu cho hang 1: 1 2 3
Nhap cac phan tu cho hang 2: 4 5 6

Mang ban vua nhap la:
1 2 3 
4 5 6

Bài tập ví dụ: C++ Bài 11.A1: Ma trận chia hết

Viết chương trình nhập vào ma trận số nguyên gồm \(n\) dòng \(m\) cột. Đếm số lượng các số chia hết cho \(3\) có trong ma trận đó.

INPUT FORMAT

Dòng đầu là hai số nguyên dương \(n, m\) \((1 \leq n, m \leq 1000)\)

Các dòng tiếp theo là ma trận số nguyên \(A\).

OUTPUT FORMAT

In ra số lượng đếm được

Ví dụ:

Input
3 4
1 3 4 6
8 4 2 9
4 2 8 6
Output
4
Giải thích ví dụ mẫu
Đếm số lượng phần tử chia hết cho 3 trong ma trận.
  1. Đọc kích thước ma trận: Đầu tiên, bạn cần đọc hai số nguyên nm từ đầu vào. Đây là số dòng và số cột của ma trận.

  2. Khởi tạo bộ đếm: Tạo một biến kiểu số nguyên (ví dụ: count) và khởi tạo nó bằng 0. Biến này sẽ dùng để lưu số lượng các phần tử chia hết cho 3 mà bạn tìm thấy.

  3. Duyệt qua từng phần tử của ma trận: Vì bạn cần kiểm tra tất cả các phần tử trong ma trận, cách thông thường và hiệu quả nhất là sử dụng hai vòng lặp lồng nhau:

    • Vòng lặp ngoài chạy từ 0 đến n-1 (hoặc 1 đến n tùy cách bạn quy ước, nhưng 0 đến n-1 phổ biến trong C++). Vòng lặp này đại diện cho các dòng.
    • Vòng lặp trong chạy từ 0 đến m-1 (hoặc 1 đến m). Vòng lặp này đại diện cho các cột trong mỗi dòng.
  4. Đọc và kiểm tra từng phần tử: Bên trong vòng lặp trong cùng (sau khi đã chọn được dòng và cột hiện tại), bạn sẽ:

    • Đọc một số nguyên từ đầu vào. Đây là giá trị của phần tử ma trận tại vị trí hiện tại. Bạn có thể đọc nó vào một biến tạm thời (ví dụ: element).
    • Kiểm tra xem số nguyên vừa đọc có chia hết cho 3 hay không. Phép toán % (modulo) rất hữu ích cho việc này: một số nguyên x chia hết cho 3 nếu x % 3 == 0.
    • Nếu số đó chia hết cho 3, tăng giá trị của biến count lên 1.
  5. Kết thúc và in kết quả: Sau khi cả hai vòng lặp hoàn thành (tức là bạn đã đọc và kiểm tra tất cả n * m phần tử của ma trận), biến count sẽ chứa tổng số các phần tử chia hết cho 3. Bạn chỉ cần in giá trị của biến count ra màn hình.

Lưu ý quan trọng:

  • Với kích thước ma trận tối đa 1000x1000, bạn không nhất thiết phải lưu toàn bộ ma trận vào bộ nhớ (ví dụ dùng vector<vector<int>>). Bạn có thể đọc từng phần tử, kiểm tra và cập nhật bộ đếm ngay lập tức, sau đó bỏ qua phần tử đó. Cách này giúp tiết kiệm bộ nhớ đáng kể.
  • Hãy sử dụng các hàm nhập/xuất chuẩn trong thư viện iostream của C++ (cin, cout). Để chương trình chạy nhanh hơn với dữ liệu lớn, bạn có thể thêm dòng ios_base::sync_with_stdio(false); cin.tie(NULL); ở đầu hàm main.

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

Comments

There are no comments at the moment.