0

Kiểu dữ liệu tệp - kiểu dữ liệu mảng trong C++

đã đăng vào 31, Tháng 7, 2026, 22:21

KIỂU DỮ LIỆU TỆP - KIỂU DỮ LIỆU MẢNG TRONG C++

Chào các bạn học sinh và lập trình viên! Khi giải quyết các bài toán lập trình quy mô lớn trên hệ thống Sen Code Online Judge! hoặc trong các kỳ thi Học sinh giỏi Tin học, dữ liệu đầu vào thường rất nhiều và phức tạp. Chúng ta không thể lưu trữ hàng trăm biến đơn lẻ hay nhập thủ công từ bàn phím mỗi lần chạy thử.

Để giải quyết vấn đề này, hai công cụ quan trọng bậc nhất trong C++ ra đời: Kiểu dữ liệu tệp (File I/O) để lưu trữ/đọc ghi dữ liệu tự động, và Kiểu dữ liệu mảng (Array / Vector) để quản lý danh sách dữ liệu có cùng kiểu trong bộ nhớ RAM. Bài viết này sẽ hướng dẫn chi tiết cú pháp và cách thao tác với hai kiểu dữ liệu này.


I. THAO TÁC VỚI TỆP DỮ LIỆU (FILE I/O)

1. Tại sao cần dùng tệp dữ liệu?

Trong lập trình thi đấu hoặc thi HSG Tin học, ban giám khảo sẽ chấm bài của bạn bằng các bộ dữ liệu thử nghiệm (Test cases) rất lớn (hàng triệu số). Việc đọc ghi qua tệp giúp:

  • Chương trình chạy tự động đọc dữ liệu mà không cần người dùng gõ từ bàn phím.
  • Kết quả đầu ra được ghi lại trực tiếp vào tệp văn bản để hệ thống chấm bài so khớp tự động.
2. Cách dùng lệnh freopen (Chuẩn mực thi HSG Việt Nam)

Cách đơn giản và phổ biến nhất trong các kỳ thi Tin học ở Việt Nam là sử dụng hàm freopen để chuyển hướng luồng nhập xuất chuẩn (cin, cout) sang đọc ghi tệp.

Cú pháp:
freopen("tên_tệp_vào.INP", "r", stdin);
freopen("tên_tệp_ra.OUT", "w", stdout);
  • "r" (Read): Chế độ đọc dữ liệu từ tệp đầu vào.
  • "w" (Write): Chế độ ghi dữ liệu ra tệp đầu ra.
  • stdin, stdout: Luồng nhập xuất chuẩn được chuyển hướng. Sau khi gọi hai lệnh này, các câu lệnh cincout sẽ tự động chuyển sang thao tác với tệp tương ứng thay vì bàn phím/màn hình.
Ví dụ minh họa:

Đọc hai số nguyên A và B từ tệp tinhtoan.inp, tính tổng của chúng và ghi kết quả ra tệp tinhtoan.out.

#include <bits/stdc++.h>

using namespace std;

int main() {
    // Chuyển hướng luồng nhập xuất sang tệp
    freopen("tinhtoan.inp", "r", stdin);
    freopen("tinhtoan.out", "w", stdout);

    // Tối ưu hóa nhập xuất
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int A, B;
    if (cin >> A >> B) {
        cout << A + B << "\n";
    }

    return 0;
}

II. KIỂU DỮ LIỆU MẢNG 1 CHIỀU (1D ARRAY)

1. Khái niệm mảng 1 chiều

Mảng một chiều là một dãy tuần tự các phần tử có cùng kiểu dữ liệu, được xếp liên tiếp nhau trong bộ nhớ RAM và chung một tên gọi. Mỗi phần tử trong mảng được truy cập thông qua chỉ số (Index) của nó.

QUY TẮC CỰC KỲ QUAN TRỌNG: Trong C++, chỉ số của mảng luôn bắt đầu từ 0 đến N - 1 (với N là số lượng phần tử của mảng).

2. Cú pháp khai báo mảng tĩnh:
kiểu_dữ_liệu tên_mảng[kích_thước_cố_định];

Ví dụ: int A[100]; khai báo mảng A chứa tối đa 100 số nguyên. Chỉ số từ A[0] đến A[99].


3. Các hàm thư viện chuẩn cực mạnh dùng cho mảng 1 chiều

Thay vì phải tự viết các vòng lặp dài dòng để tìm kiếm hay sắp xếp, C++ cung cấp sẵn các hàm tối ưu hóa tốc độ chạy trong thư viện <algorithm><numeric> (đã tích hợp trong thư viện gộp <bits/stdc++.h>):

a. Hàm tìm phần tử lớn nhất: *max_element
  • Cú pháp: *max_element(A, A + N)
  • Ý nghĩa: Tìm giá trị lớn nhất trong mảng A gồm N phần tử. (Chú ý có dấu sao * ở trước vì hàm gốc trả về con trỏ chỉ vị trí phần tử).
b. Hàm tìm phần tử nhỏ nhất: *min_element
  • Cú pháp: *min_element(A, A + N)
  • Ý nghĩa: Tìm giá trị nhỏ nhất trong mảng A gồm N phần tử.
c. Hàm sắp xếp mảng: sort
  • Cú pháp sắp xếp tăng dần: sort(A, A + N)
  • Cú pháp sắp xếp giảm dần: sort(A, A + N, greater<kiểu_dữ_liệu>())
  • Ý nghĩa: Sắp xếp lại vị trí các phần tử của mảng A theo thứ tự mong muốn với độ phức tạp cực kỳ tối ưu là O(N log N).
d. Hàm tính tổng mảng: accumulate
  • Cú pháp: accumulate(A, A + N, giá_trị_khởi_đầu)
  • Ý nghĩa: Tính tổng toàn bộ phần tử trong mảng từ vị trí bắt đầu đến hết.
  • Lưu ý: Nên truyền 0LL (kiểu long long) làm giá trị khởi đầu để tránh lỗi tràn số nguyên khi tính tổng mảng lớn: accumulate(A, A + N, 0LL).

4. Ví dụ minh họa sử dụng các hàm chuẩn:

Nhập một dãy số gồm N phần tử. Sử dụng các hàm thư viện để in ra phần tử lớn nhất, nhỏ nhất, tổng mảng và sắp xếp mảng theo thứ tự tăng dần.

#include <bits/stdc++.h>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N;
    cin >> N;

    int A[N]; // Khai báo mảng tĩnh gồm N phần tử

    for (int i = 0; i < N; ++i) {
        cin >> A[i];
    }

    // Sử dụng hàm thư viện để tìm Max, Min
    int giaTriMax = *max_element(A, A + N);
    int giaTriMin = *min_element(A, A + N);

    // Sử dụng hàm thư viện để tính tổng mảng
    long long tongMang = accumulate(A, A + N, 0LL);

    // Sử dụng hàm thư viện để sắp xếp mảng tăng dần
    sort(A, A + N);

    cout << "Max = " << giaTriMax << "\n";
    cout << "Min = " << giaTriMin << "\n";
    cout << "Tong mang = " << tongMang << "\n";

    cout << "Mang sau khi sap xep tang dan: ";
    for (int i = 0; i < N; ++i) {
        cout << A[i] << (i == N - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}

III. KIỂU DỮ LIỆU MẢNG 2 CHIỀU (MATRIX / 2D ARRAY)

1. Khái niệm mảng 2 chiều

Mảng hai chiều có thể hình dung như một bảng số gồm các dòng và các cột (ma trận). Bản chất nó là một mảng lớn mà mỗi phần tử của nó lại là một mảng một chiều khác.

2. Cú pháp khai báo:
kiểu_dữ_liệu tên_mảng[số_dòng][số_cột];

Để truy cập phần tử ở dòng thứ i và cột thứ j, ta viết: tên_mảng[i][j]. (Chỉ số dòng và cột đều bắt đầu từ 0).

3. Ví dụ minh họa:

Nhập vào một ma trận vuông kích thước N x N số nguyên. Tính tổng các phần tử nằm trên đường chéo chính của ma trận này (các phần tử có chỉ số dòng bằng chỉ số cột, tức là A[i][i]).

#include <bits/stdc++.h>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N;
    cin >> N;

    int A[N][N]; // Khai báo ma trận kích thước N x N

    // Vòng lặp lồng nhau để nhập dữ liệu cho ma trận
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) {
            cin >> A[i][j];
        }
    }

    long long tongCheoChinh = 0;
    // Chỉ cần duy nhất 1 vòng lặp để duyệt đường chéo chính
    for (int i = 0; i < N; ++i) {
        tongCheoChinh += A[i][i];
    }

    cout << tongCheoChinh << "\n";
    return 0;
}

IV. MẢNG ĐỘNG std::vector (HIỆN ĐẠI & KHUYÊN DÙNG)

1. Tại sao nên dùng std::vector thay cho mảng tĩnh?

Mảng tĩnh bắt buộc phải khai báo kích thước cố định từ trước. Việc này dẫn đến hai nhược điểm:

  1. Gây lãng phí bộ nhớ nếu khai báo quá lớn mà thực tế dùng ít.
  2. Bị lỗi tràn bộ nhớ hoặc lỗi truy cập ngoài phạm vi (Runtime Error) nếu dữ liệu thực tế vượt quá kích thước mảng tĩnh.

std::vector thuộc thư viện chuẩn C++ là mảng động. Nok có khả năng tự động co giãn kích thước linh hoạt trong lúc chạy và cung cấp nhiều hàm tiện ích cực kỳ mạnh mẽ.

2. Cú pháp khai báo vector:
vector<kiểu_dữ_liệu> tên_vector; // Vector rỗng kích thước bằng 0
vector<kiểu_dữ_liệu> tên_vector(kích_thước); // Khởi tạo vector có sẵn kích thước ban đầu
3. Các hàm/phương thức thông dụng của std::vector:
  • v.size(): Trả về số lượng phần tử hiện tại trong vector v.
  • v.push_back(giá_trị): Thêm một phần tử mới có giá_trị vào cuối vector, kích thước tự động tăng thêm 1.
  • v.pop_back(): Xóa phần tử ở cuối cùng của vector, kích thước giảm đi 1.
  • v.resize(kích_thước_mới): Thay đổi kích thước vector theo mong muốn.
  • v.clear(): Xóa sạch toàn bộ phần tử trong vector, đưa kích thước về 0.
  • Sắp xếp mảng động: Ta có thể sắp xếp các phần tử của vector tăng dần cực kỳ nhanh bằng hàm sort: sort(v.begin(), v.end());
  • Tìm Max, Min với vector: Cú pháp tương tự mảng tĩnh nhưng truyền iterator:
    • *max_element(v.begin(), v.end())
    • *min_element(v.begin(), v.end())
  • Tính tổng mảng với vector:
    • accumulate(v.begin(), v.end(), 0LL)
Ví dụ minh họa:

Nhập vào N số nguyên. Sắp xếp dãy số đó theo thứ tự tăng dần và in ra màn hình.

#include <bits/stdc++.h>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N;
    cin >> N;

    vector<int> V; // Khai báo vector rỗng

    for (int i = 0; i < N; ++i) {
        int x;
        cin >> x;
        V.push_back(x); // Nạp x vào cuối mảng động
    }

    // Sắp xếp tăng dần toàn bộ vector
    sort(V.begin(), V.end());

    // In kết quả
    for (int i = 0; i < V.size(); ++i) {
        cout << V[i] << (i == V.size() - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}

V. ĐỀ XUẤT 10 BÀI TẬP THỰC HÀNH THỰC TẾ (MẢNG VÀ TỆP)

Để chuẩn bị tốt kỹ năng cho các cuộc thi Lập trình thi đấu, học sinh hãy thực hiện đọc/ghi dữ liệu bằng tệp thông qua freopen đối với 10 bài tập thực tế sau:

Bài 1: Thống kê nhiệt độ đo đạc
  • Ngữ cảnh: Một trạm khí tượng thu thập dãy gồm N số đo nhiệt độ của N ngày liên tiếp. Trạm cần biết nhiệt độ cao nhất, thấp nhất và nhiệt độ trung bình của chuỗi ngày này.
  • Yêu cầu: Đọc dữ liệu từ tệp nhietdo.inp, tìm Max, Min và tính Điểm trung bình của dãy số rồi ghi ra tệp nhietdo.out.
  • Đầu vào (Input - từ tệp nhietdo.inp):
    • Dòng 1 chứa số nguyên dương N (1 <= N <= 105).
    • Dòng 2 chứa N số thực cách nhau bởi khoảng trắng đại diện cho nhiệt độ từng ngày.
  • Đầu ra (Output - ra tệp nhietdo.out): Một dòng duy nhất chứa 3 số thực lần lượt là Nhiệt độ lớn nhất, Nhỏ nhất và Trung bình (lấy chính xác 2 chữ số thập phân).
  • Ví dụ:

    • Tệp nhietdo.inp:

      4
      30.5 32.0 28.5 29.0
      
    • Tệp nhietdo.out:

      32.00 28.50 30.00
      
Bài 2: Tìm kiếm số báo danh trúng giải
  • Ngữ cảnh: Ban tổ chức cuộc thi trên hệ thống lưu danh sách gồm N số báo danh của các thí sinh đạt giải khuyến khích. Một thí sinh có mã số báo danh X muốn truy vấn nhanh xem mình có nằm trong danh sách trúng giải hay không.
  • Yêu cầu: Đọc từ tệp lucky.inp, kiểm tra số báo danh X có tồn tại trong danh sách không rồi ghi kết quả ra tệp lucky.out.
  • Đầu vào (Input - tệp lucky.inp):
    • Dòng 1 chứa hai số nguyên dương N và X (1 <= N <= 105, X <= 109).
    • Dòng 2 chứa N số nguyên đại diện cho danh sách trúng giải.
  • Đầu ra (Output - tệp lucky.out): In ra YES nếu X trúng giải, ngược lại in ra NO.
  • Ví dụ:

    • Input:

      5 1005
      1001 1003 1005 1008 1010
      
    • Output: YES

Bài 3: Thống kê số lượng người hiến máu nhóm O
  • Ngữ cảnh: Trong danh sách đăng ký hiến máu tình nguyện gồm N người, mỗi người khai báo nhóm máu của mình dưới dạng ký hiệu chuỗi (nhận các giá trị "A", "B", "AB", "O"). Do bệnh viện đang thiếu khẩn cấp nhóm máu O, bác sĩ cần đếm xem có bao nhiêu người thuộc nhóm máu này trong danh sách.
  • Yêu cầu: Đọc từ tệp blood.inp và ghi số lượng nhóm máu O ra tệp blood.out.
  • Đầu vào (Input - tệp blood.inp):
    • Dòng 1 chứa số nguyên dương N (1 <= N <= 104).
    • Dòng 2 chứa N chuỗi ký tự nhóm máu cách nhau bởi khoảng trắng.
  • Đầu ra (Output - tệp blood.out): Một số nguyên duy nhất là số người thuộc nhóm máu O.
  • Ví dụ:

    • Input:

      6
      A O AB B O O
      
    • Output: 3

Bài 4: Bảng xếp hạng điểm số học sinh
  • Ngữ cảnh: Một lớp học có N học sinh vừa hoàn thành bài kiểm tra trực tuyến. Giáo viên muốn sắp xếp điểm số của các em theo thứ tự giảm dần từ cao xuống thấp để lập bảng xếp hạng vinh danh.
  • Yêu cầu: Đọc điểm số từ tệp leaderboard.inp, sắp xếp giảm dần và ghi kết quả ra tệp leaderboard.out.
  • Đầu vào (Input - tệp leaderboard.inp):
    • Dòng 1 chứa số nguyên N (1 <= N <= 105).
    • Dòng 2 chứa N số thực điểm số của học sinh.
  • Đầu ra (Output - tệp leaderboard.out): Dãy điểm số sau khi đã xếp giảm dần cách nhau bởi khoảng trắng (lấy 1 chữ số thập phân).
  • Ví dụ:

    • Input:

      4
      8.5 9.0 7.5 9.5
      
    • Output: 9.5 9.0 8.5 7.5

Bài 5: Tìm kiếm điểm số cao thứ nhì
  • Ngữ cảnh: Trong một kỳ thi chọn đội tuyển HSG Tin học, giáo viên cần tìm ra điểm số cao thứ nhì của kỳ thi để trao giải thưởng phụ. Chú ý điểm cao thứ nhì phải thực sự nhỏ hơn điểm cao nhất (tức là nếu có nhiều học sinh cùng đồng thủ khoa điểm cao nhất, ta phải tìm điểm lớn tiếp theo đứng sau điểm thủ khoa đó).
  • Yêu cầu: Đọc từ tệp second.inp và ghi kết quả ra tệp second.out.
  • Đầu vào (Input - tệp second.inp):
    • Dòng 1 chứa số nguyên N (2 <= N <= 105).
    • Dòng 2 chứa N số nguyên điểm số của thí sinh.
  • Đầu ra (Output - tệp second.out): In ra điểm số lớn thứ nhì. Nếu tất cả học sinh đều bằng điểm nhau (không tồn tại điểm lớn thứ nhì), in ra -1.
  • Ví dụ:

    • Input:

      5
      10 10 9 8 9
      
    • Output: 9

Bài 6: Phép chuyển vị ảnh ma trận (Xoay ảnh)
  • Ngữ cảnh: Một camera của robot thu thập hình ảnh dạng ma trận số kích thước M x N. Bộ phận xử lý hình ảnh cần xoay ảnh bằng cách thực hiện phép chuyển vị ma trận (các dòng của ma trận ban đầu sẽ biến thành các cột tương ứng của ma trận kết quả và ngược lại).
  • Yêu cầu: Đọc ma trận từ tệp matrix.inp và ghi ma trận chuyển vị kích thước N x M ra tệp matrix.out.
  • Đầu vào (Input - tệp matrix.inp):
    • Dòng 1 chứa hai số nguyên M và N (1 <= M, N <= 100).
    • M dòng tiếp theo, mỗi dòng chứa N số nguyên biểu diễn ma trận.
  • Đầu ra (Output - tệp matrix.out): Ma trận chuyển vị gồm N dòng, mỗi dòng chứa M số nguyên.
  • Ví dụ:

    • Input:

      2 3
      1 2 3
      4 5 6
      
    • Output:

      1 4
      2 5
      3 6
      
Bài 7: Lọc số nguyên tố trong dãy số
  • Ngữ cảnh: Cho một dãy gồm N số nguyên dương. Nhiệm vụ của bạn là lọc ra tất cả các số nguyên tố xuất hiện trong dãy đó và in chúng ra theo đúng thứ tự xuất hiện ban đầu.
  • Yêu cầu: Đọc dãy số từ tệp primefilter.inp và ghi các số nguyên tố tìm thấy ra tệp primefilter.out.
  • Đầu vào (Input - tệp primefilter.inp):
    • Dòng 1 chứa số nguyên N (1 <= N <= 104).
    • Dòng 2 chứa N số nguyên dương cách nhau bởi dấu cách (các số nguyên không quá 109).
  • Đầu ra (Output - tệp primefilter.out): Dãy các số nguyên tố lọc được cách nhau bởi khoảng trắng. Nếu không có số nguyên tố nào, ghi ra một dòng trống.
  • Ví dụ:

    • Input:

      5
      4 7 12 11 9
      
    • Output: 7 11

Bài 8: Kiểm tra mạng lưới đối xứng (Ma trận đối xứng)
  • Ngữ cảnh: Sơ đồ kết nối các trạm phát sóng radar trong khu vực được biểu diễn dưới dạng ma trận liên kết vuông A kích thước N x N. Mạng lưới được coi là đối xứng hoàn toàn nếu khoảng cách kết nối từ trạm i tới trạm j bằng khoảng cách từ trạm j tới trạm i (tức là A[i][j] = A[j][i] với mọi chỉ số dòng i và chỉ số cột j).
  • Yêu cầu: Đọc ma trận kề từ tệp symmetric.inp và ghi kết luận ra tệp symmetric.out.
  • Đầu vào (Input - tệp symmetric.inp):
    • Dòng 1 chứa số nguyên vuông N (1 <= N <= 100).
    • N dòng tiếp theo, mỗi dòng chứa N số nguyên mô tả ma trận A.
  • Đầu ra (Output - tệp symmetric.out): In ra YES nếu ma trận đối xứng, ngược lại in ra NO.
  • Ví dụ:

    • Input:

      3
      0 5 9
      5 0 2
      9 2 0
      
    • Output: YES

Bài 9: Sản phẩm bình chọn nhiều nhất
  • Ngữ cảnh: Trong một cuộc bình chọn trực tuyến chất lượng sản phẩm, ban tổ chức nhận được N lượt bầu chọn. Mỗi lượt bầu chọn chứa mã sản phẩm là một số nguyên dương từ 1 đến 1000. Hãy thống kê và chỉ ra sản phẩm nhận được số lượt bầu chọn nhiều nhất để trao giải "Sản phẩm được yêu thích nhất".
  • Yêu cầu: Đọc dữ liệu bầu chọn từ tệp vote.inp và ghi mã sản phẩm chiến thắng ra tệp vote.out (nếu có nhiều sản phẩm cùng số phiếu bầu cao nhất, in ra sản phẩm có mã số nhỏ nhất).
  • Đầu vào (Input - tệp vote.inp):
    • Dòng 1 chứa số nguyên dương N (1 <= N <= 105).
    • Dòng 2 chứa N số nguyên là mã của các sản phẩm được bình chọn (mỗi mã từ 1 đến 1000).
  • Đầu ra (Output - tệp vote.out): Một số nguyên duy nhất là mã sản phẩm nhận nhiều phiếu bầu nhất.
  • Ví dụ:

    • Input:

      7
      12 5 12 9 5 12 7
      
    • Output: 12 (Sản phẩm mã 12 nhận được 3 phiếu bầu, nhiều nhất)

Bài 10: Quản lý đặt ghế ngồi rạp chiếu phim
  • Ngữ cảnh: Sơ đồ phòng chiếu phim được mô tả dưới dạng ma trận nhị phân kích thước M x N, trong đó số 0 đại diện cho ghế trống và số 1 đại diện cho ghế đã được khách đặt mua. Một đoàn khách lớn muốn tìm một hàng ghế còn trống nhiều chỗ để có thể ngồi gần nhau.
  • Yêu cầu: Đọc từ tệp cinema.inp, đếm tổng số ghế trống còn lại trong phòng và tìm ra chỉ số hàng ghế (dòng ma trận, đánh số từ 0) có số lượng ghế trống nhiều nhất. Ghi kết quả ra tệp cinema.out.
  • Đầu vào (Input - tệp cinema.inp):
    • Dòng 1 chứa hai số nguyên M và N là số hàng và số ghế mỗi hàng (1 <= M, N <= 100).
    • M dòng tiếp theo, mỗi dòng chứa N số nguyên nhị phân (0 hoặc 1).
  • Đầu ra (Output - tệp cinema.out): Một dòng chứa hai số nguyên cách nhau bởi dấu cách: Tổng số ghế trống của phòng chiếu và chỉ số hàng ghế (dòng ma trận, tính từ 0) có nhiều ghế trống nhất (nếu có nhiều hàng cùng số ghế trống tối đa giống nhau, in ra chỉ số hàng nhỏ nhất).
  • Ví dụ:

    • Input:

      3 4
      0 1 0 1
      1 1 1 1
      0 0 0 1
      
    • Output: 5 2 (Tổng số ghế trống (số 0) là 5. Hàng 0 có 2 ghế trống, hàng 1 có 0 ghế trống, hàng 2 có 3 ghế trống -> Hàng 2 có nhiều ghế trống nhất)


Lời kết

Thành thạo kỹ năng thao tác với Tệp dữ liệu và các kiểu Mảng một chiều, hai chiều, vector là nền tảng cốt lõi giúp các bạn bắt đầu giải quyết các thuật toán phức tạp trên các hệ thống chấm bài trực tuyến. Hãy viết code thật cẩn thận để tránh lỗi tràn mảng (Out of bounds) nhé!

Chúc các bạn rèn luyện thật tốt và đạt điểm số tối đa cho các bài tập này trên Sen Code Online Judge!! Chúc các bạn thành công!


Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.