Bài 11.2: Duyệt mảng 1 chiều bằng chỉ số trong C++

Chào mừng trở lại với loạt bài viết về C++! Sau khi đã làm quen với khái niệm mảng một chiều, bước tiếp theo và vô cùng quan trọng là học cách truy cậplàm việc với từng phần tử bên trong mảng đó. Quá trình này được gọi là duyệt mảng (array traversal).

Trong C++, có nhiều cách để duyệt mảng, nhưng phương pháp sử dụng chỉ số (index)cốt lõi, cơ bản và mang lại cho chúng ta sức mạnh kiểm soát tuyệt đối trên từng vị trí trong mảng. Hãy cùng khám phá phương pháp này nhé!

Chỉ số (Index) trong Mảng: Người bạn đồng hành

Trước hết, hãy nhắc lại một chút về chỉ số. Trong C++ (và hầu hết các ngôn ngữ lập trình khác), các phần tử trong mảng một chiều được đánh số thứ tự bắt đầu từ 0.

  • Phần tử đầu tiên có chỉ số là 0.
  • Phần tử thứ hai có chỉ số là 1.
  • ...
  • Phần tử cuối cùng của một mảng có kích thướcN sẽ có chỉ số là N-1.

Khi duyệt mảng bằng chỉ số, chúng ta sẽ sử dụng một biến (thường là kiểu số nguyên) để đại diện cho chỉ số hiện tại mà chúng ta đang truy cập. Biến này sẽ thay đổi giá trị, đi từ chỉ số đầu tiên (0) đến chỉ số cuối cùng (N-1).

Vòng lặp for - Công cụ đắc lực

Để tự động hóa việc thay đổi chỉ số và thực hiện một hành động nào đó với từng phần tử, chúng ta sử dụng các cấu trúc lặp. Vòng lặp for là lựa chọn phổ biến và phù hợp nhất khi duyệt mảng bằng chỉ số, bởi vì chúng ta biết chính xác số lần lặp cần thiết (bằng với số lượng phần tử của mảng).

Cấu trúc chung của vòng lặp for để duyệt mảng kích thước kich_thuoc sẽ là:

for (int i = 0; i < kich_thuoc; ++i) {
    // Làm gì đó với phan tu tai chi so 'i',
    // truy cap bang: ten_mang[i]
}
  • int i = 0: Khởi tạo biến chỉ số i bắt đầu từ 0.
  • i < kich_thuoc: Điều kiện lặp. Vòng lặp tiếp tục chừng nào i còn nhỏ hơn kích thước của mảng. Điều này đảm bảo i sẽ chạy từ 0 đến kich_thuoc - 1, bao trọn tất cả các chỉ số hợp lệ.
  • ++i: Tăng giá trị của i sau mỗi lần lặp, để chuyển sang chỉ số kế tiếp.
  • ten_mang[i]: Đây là cách chúng ta truy cập giá trị của phần tử tại chỉ số i hiện tại trong mảng.

Bây giờ, hãy cùng xem một vài ví dụ minh họa nhé!

Ví dụ 1: In tất cả các phần tử của mảng

Đây là tác vụ cơ bản nhất khi duyệt mảng: chỉ đơn giản là xem bên trong nó có gì.

#include <iostream>
#include <vector> // Sử dụng vector cho dễ quản lý kích thước

int main() {
    // Khai báo và khởi tạo một mảng (sử dụng vector)
    vector<int> diemSo = {85, 90, 78, 92, 88, 95};

    // Lấy kích thước của mảng
    // Đối với vector, sử dụng phương thức size()
    int soLuongPhanTu = diemSo.size();

    cout << "Danh sach diem so:" << endl;

    // Duyệt mảng bằng chỉ số
    for (int i = 0; i < soLuongPhanTu; ++i) {
        // Truy cap va in gia tri tai chi so i
        cout << "Diem tai chi so " << i << " la: " << diemSo[i] << endl;
    }

    return 0;
}

Giải thích:

  • Chúng ta khai báo một vector tên là diemSo chứa các điểm số. Việc sử dụng vector thay vì mảng C-style (int diemSo[] = ...;) là thực hành tốt trong C++ hiện đại vì nó quản lý bộ nhớ linh hoạt hơn và cung cấp phương thức .size() đáng tin cậy để lấy kích thước.
  • soLuongPhanTu = diemSo.size(); lấy kích thước của vector. Đối với một mảng C-style int arr[10];, kích thước là cố định 10. Nếu khai báo int arr[] = {1, 2, 3};, kích thước sẽ là 3. Lưu ý rằng cách tính kích thước mảng C-style khi mảng được truyền vào hàm có thể phức tạp hơn (sizeof(arr)/sizeof(arr[0]) chỉ hoạt động khi mảng không bị phân rã thành con trỏ). Với vector, .size() luôn chính xác.
  • Vòng lặp for (int i = 0; i < soLuongPhanTu; ++i) bắt đầu với i=0, chạy cho đến khi i bằng soLuongPhanTu (không bao gồm soLuongPhanTu), và tăng i sau mỗi lần lặp.
  • Trong mỗi lần lặp, diemSo[i] truy cập đến phần tử tại chỉ số i hiện tại.
  • Chúng ta in ra cả chỉ số (i) và giá trị (diemSo[i]) để thấy rõ mối liên hệ.
Ví dụ 2: Tính tổng các phần tử trong mảng

Duyệt mảng không chỉ để in ra, mà còn để thực hiện tính toán trên các phần tử.

#include <iostream>
#include <vector>
#include <numeric> // Thư viện hữu ích cho các phép toán số học

int main() {
    vector<double> doanhThuNgay = {150.5, 210.0, 185.75, 250.0, 199.99};
    double tongDoanhThu = 0.0;

    // Duyệt mảng để cộng dồn giá trị
    for (int i = 0; i < doanhThuNgay.size(); ++i) {
        tongDoanhThu += doanhThuNgay[i]; // Cong gia tri tai chi so i vao tong
    }

    cout << "Tong doanh thu trong " << doanhThuNgay.size() << " ngay la: " << tongDoanhThu << endl;

    // Minh hoa su dung ham trong <numeric> - cach hien dai hon cho tong don gian
    // double tongDoanhThuModern = accumulate(doanhThuNgay.begin(), doanhThuNgay.end(), 0.0);
    // cout << "(Su dung accumulate) Tong doanh thu: " << tongDoanhThuModern << endl;

    return 0;
}

Giải thích:

  • Chúng ta sử dụng vòng lặp for để đi qua từng chỉ số từ 0 đến kích thước mảng trừ 1.
  • Biến tongDoanhThu được khởi tạo bằng 0 trước vòng lặp.
  • Bên trong vòng lặp, tongDoanhThu += doanhThuNgay[i]; lấy giá trị của phần tử tại chỉ số i (doanhThuNgay[i]) và cộng nó vào tongDoanhThu.
  • Sau khi vòng lặp kết thúc, tongDoanhThu chứa tổng của tất cả các phần tử trong mảng.
  • (Đoạn code bị chú thích) Minh họa rằng có những cách hiện đại hơn (accumulate) cho các tác vụ phổ biến như tính tổng, nhưng vòng lặp index vẫn là cơ sở để hiểu và thực hiện các thao tác phức tạp hơn.
Ví dụ 3: Tìm kiếm một phần tử và vị trí của nó

Nếu bạn cần biết một giá trị cụ thể có tồn tại trong mảng hay không, và nếu có thì nó nằm ở vị trí (chỉ số) nào, duyệt bằng chỉ số là cách tự nhiên để làm điều đó.

#include <iostream>
#include <vector>
#include <string>

int main() {
    vector<string> danhSachHangHoa = {"Ao Thun", "Quan Bo", "Ao Khoac", "Quan Short", "Ao Thun"};
    string hangHoaCanTim = "Ao Khoac";
    int viTriTimThay = -1; // Sử dụng -1 để biểu thị không tìm thấy

    cout << "Tim kiem '" << hangHoaCanTim << "' trong danh sach..." << endl;

    // Duyệt mảng để tìm kiếm
    for (int i = 0; i < danhSachHangHoa.size(); ++i) {
        if (danhSachHangHoa[i] == hangHoaCanTim) {
            viTriTimThay = i; // Lưu lại chỉ số khi tìm thấy
            break; // Tim thay roi thi thoat vong lap luon (hieu qua hon)
        }
    }

    // Kiem tra ket qua
    if (viTriTimThay != -1) {
        cout << "Tim thay '" << hangHoaCanTim << "' tai chi so: " << viTriTimThay << endl;
    } else {
        cout << "'" << hangHoaCanTim << "' khong co trong danh sach." << endl;
    }

    return 0;
}

Giải thích:

  • Chúng ta khởi tạo viTriTimThay bằng -1.
  • Vòng lặp for đi qua từng chỉ số i.
  • Trong mỗi lần lặp, câu lệnh if (danhSachHangHoa[i] == hangHoaCanTim) so sánh phần tử tại chỉ số i với giá trị cần tìm.
  • Nếu tìm thấy, chúng ta cập nhật viTriTimThay bằng chỉ số i hiện tại và sử dụng break; để thoát khỏi vòng lặp ngay lập tức. Điều này giúp tối ưu hóa hiệu suất, vì không cần kiểm tra các phần tử còn lại nếu đã tìm thấy.
  • Sau vòng lặp, chúng ta kiểm tra giá trị của viTriTimThay để biết kết quả tìm kiếm.
Ví dụ 4: Duyệt mảng theo chiều ngược lại

Đôi khi, bạn cần xử lý các phần tử từ cuối mảng trở về đầu. Duyệt bằng chỉ số cho phép bạn dễ dàng làm điều này.

#include <iostream>
#include <vector>

int main() {
    vector<char> kyTu = {'A', 'B', 'C', 'D', 'E'};

    cout << "Duyet mang theo chieu dao nguoc:" << endl;

    // Duyệt mảng từ chi so cuoi cung ve 0
    for (int i = kyTu.size() - 1; i >= 0; --i) {
        cout << kyTu[i] << " "; // In phan tu tai chi so i
    }
    cout << endl; // Xuong dong sau khi in xong

    return 0;
}

Giải thích:

  • Thay vì bắt đầu từ 0 và tăng dần, chúng ta bắt đầu vòng lặp từ chỉ số cuối cùng của mảng, là kyTu.size() - 1.
  • Điều kiện lặp là i >= 0. Vòng lặp tiếp tục chừng nào i còn lớn hơn hoặc bằng 0 (bao gồm cả chỉ số 0).
  • Bước nhảy là --i, giảm giá trị của i sau mỗi lần lặp.
  • Kết quả là vòng lặp sẽ truy cập các phần tử theo thứ tự E, D, C, B, A.
Sức mạnh và lưu ý khi dùng chỉ số

Tại sao lại dùng chỉ số?

  • Truy cập trực tiếp: Chỉ số cho phép bạn truy cập bất kỳ phần tử nào trong mảng ngay lập tức nếu bạn biết chỉ số của nó (ten_mang[i]).
  • Kiểm soát linh hoạt: Bạn có thể duyệt từ đầu đến cuối, từ cuối về đầu, hoặc thậm chí bỏ qua các phần tử (ví dụ: tăng i lên 2 mỗi lần lặp để chỉ xử lý các phần tử ở chỉ số chẵn).
  • Thao tác tại chỗ: Chỉ số là cần thiết khi bạn muốn thay đổi giá trị của một phần tử cụ thể trong mảng (ví dụ: ten_mang[i] = gia_tri_moi;).

Lưu ý quan trọng:

  • Lỗi ngoài phạm vi (Out of Bounds Error): Đây là lỗi phổ biến và nguy hiểm nhất khi làm việc với chỉ số. Nếu bạn cố gắng truy cập ten_mang[i]i nhỏ hơn 0 hoặc lớn hơn hoặc bằng kích thước mảng (i >= kich_thuoc), chương trình của bạn có thể gặp lỗi nghiêm trọng (ví dụ: crash) hoặc cho kết quả sai không lường trước.
  • Luôn đảm bảo điều kiện vòng lặp của bạn chính xác để chỉ số không bao giờ vượt ra ngoài phạm vi [0, kich_thuoc - 1].
  • Với vector, bạn có thể sử dụng phương thức at() thay vì toán tử []. Ví dụ: diemSo.at(i). Ưu điểm của at() là nó kiểm tra phạm vi. Nếu i nằm ngoài phạm vi hợp lệ, at() sẽ ném ra một ngoại lệ (out_of_range), giúp bạn phát hiện lỗi sớm hơn trong quá trình phát triển, thay vì gặp hành vi không xác định với []. Tuy nhiên, at() có thể chậm hơn một chút do chi phí kiểm tra.

Bài tập ví dụ: C++ Bài 7.A2: Số lượng số chẵn

Lập trình nhập vào một số nguyên dương \(N\) và dãy số nguyên \(A\) gồm \(N\) phần tử. Đếm và thông báo số lượng số chẵn có mặt trong dãy \(A\). .

INPUT FORMAT

Dòng đầu là số nguyên dương \(N\) \((1 \leq N \leq 10 ^6)\).

Dòng thứ hai là dãy số nguyên \(A\) \((1 \leq |A_i| \leq 10^9)\).

OUTPUT FORMAT

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

Ví dụ:

Input
5
1 12 4 -3 8
Output
3

Giải thích ví dụ mẫu:

  • Ví dụ: 5 1 12 4 -3 8
  • Giải thích: Có 3 số chẵn trong dãy: 12, 4, và 8. <br>

1. Hiểu yêu cầu: Bạn cần đọc một số nguyên dương N, sau đó đọc N số nguyên tiếp theo và đếm xem có bao nhiêu số trong dãy đó là số chẵn.

2. Cách xác định số chẵn: Một số nguyên x là số chẵn nếu nó chia hết cho 2. Trong C++, bạn có thể kiểm tra điều này bằng toán tử modulo: x % 2 == 0.

3. Sử dụng thư viện chuẩn C++:

  • Để đọc và ghi dữ liệu, bạn sẽ cần thư viện iostream.
  • Để lưu trữ dãy N số, vector<int> là lựa chọn phù hợp và linh hoạt hơn mảng truyền thống.
  • Để đếm số lượng phần tử thỏa mãn một điều kiện, thư viện algorithm cung cấp hàm count_if rất tiện lợi.

4. Hướng giải chi tiết (sử dụng vectorcount_if):

  • Bước 1: Chuẩn bị:

    • Bao gồm các header cần thiết: iostream để nhập xuất, vector để dùng vector, và algorithm để dùng count_if.
    • Có thể thêm using namespace std;.
    • Để tăng tốc nhập xuất cho N lớn, bạn có thể thêm các dòng sau ở đầu hàm main:
      ios_base::sync_with_stdio(false);
      cin.tie(NULL);
      
  • Bước 2: Đọc N:

    • Khai báo một biến kiểu int (hoặc long long nếu N có thể rất lớn, nhưng đề bài cho 10^6 nên int đủ) để lưu giá trị N.
    • Đọc giá trị N từ đầu vào chuẩn (cin).
  • Bước 3: Đọc dãy số và lưu vào vector:

    • Khai báo một đối tượng vector<int>. Kích thước của vector sẽ là N.
    • Sử dụng một vòng lặp (ví dụ: for) chạy N lần.
    • Trong mỗi lần lặp, đọc một số nguyên từ đầu vào chuẩn (cin) và thêm số đó vào cuối vector (ví dụ: dùng phương thức push_back).
  • Bước 4: Đếm số chẵn:

    • Sử dụng hàm count_if. Hàm này cần 3 đối số:
      • Iterator bắt đầu của dãy (vector.begin()).
      • Iterator kết thúc của dãy (vector.end()).
      • Một predicate (một hàm hoặc lambda) nhận một phần tử của vector và trả về true nếu phần tử đó thỏa mãn điều kiện (là số chẵn), false nếu ngược lại. Predicate này có thể là một lambda expression như [](int x){ return x % 2 == 0; }.
    • Kết quả trả về của count_if chính là số lượng số chẵn. Lưu kết quả này vào một biến kiểu int.
  • Bước 5: In kết quả:

    • In giá trị của biến đếm được ở Bước 4 ra đầu ra chuẩn (cout), sau đó là ký tự xuống dòng (endl).

5. Lưu ý về bộ nhớ (N lớn): Với N lên đến 10^6, việc lưu trữ toàn bộ dãy số vào vector là chấp nhận được về mặt bộ nhớ (khoảng vài MB).

6. Hướng giải thay thế (tiết kiệm bộ nhớ hơn): Nếu N lớn hơn nữa và bộ nhớ là vấn đề, bạn có thể không cần lưu toàn bộ dãy số. Thay vào đó:

  • Khai báo biến đếm (count) khởi tạo bằng 0.
  • Sử dụng một vòng lặp chạy N lần.
  • Trong mỗi lần lặp, chỉ cần đọc một số từ đầu vào, kiểm tra xem nó có phải là số chẵn không.
  • Nếu là số chẵn, tăng biến đếm lên 1.
  • Không cần lưu số vừa đọc vào vector hay mảng.
  • Sau khi vòng lặp kết thúc, in biến đếm. Tuy nhiên, với giới hạn N <= 10^6 của bài này, cách dùng vector + count_if là đủ hiệu quả và mang tính "std" cao hơn.

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

Comments

There are no comments at the moment.