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>

int main() {
    vector<int> a = {85, 90, 78, 92, 88, 95};
    int n = a.size();

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

    for (int i = 0; i < n; ++i) {
        cout << "Diem tai chi so " << i << " la: " << a[i] << endl;
    }

    return 0;
}

Output:

Danh sach diem so:
Diem tai chi so 0 la: 85
Diem tai chi so 1 la: 90
Diem tai chi so 2 la: 78
Diem tai chi so 3 la: 92
Diem tai chi so 4 la: 88
Diem tai chi so 5 la: 95
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>

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

    for (int i = 0; i < a.size(); ++i) {
        tong += a[i];
    }

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

    return 0;
}

Output:

Tong doanh thu trong 5 ngay la: 996.24
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> ds = {"Ao Thun", "Quan Bo", "Ao Khoac", "Quan Short", "Ao Thun"};
    string tim = "Ao Khoac";
    int vt = -1;

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

    for (int i = 0; i < ds.size(); ++i) {
        if (ds[i] == tim) {
            vt = i;
            break;
        }
    }

    if (vt != -1) {
        cout << "Tim thay '" << tim << "' tai chi so: " << vt << endl;
    } else {
        cout << "'" << tim << "' khong co trong danh sach." << endl;
    }

    return 0;
}

Output:

Tim kiem 'Ao Khoac' trong danh sach...
Tim thay 'Ao Khoac' tai chi so: 2
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 đó.

#include <iostream>
#include <vector>

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

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

    for (int i = a.size() - 1; i >= 0; --i) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

Output:

Duyet mang theo chieu dao nguoc:
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.

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.
#include <iostream>
#include <vector>
#include <algorithm> // For count_if

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

    int n;
    cin >> n;

    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    int dem = count_if(a.begin(), a.end(), [](int x) {
        return x % 2 == 0;
    });

    cout << dem << endl;

    return 0;
}

Output cho ví dụ:

3

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

Comments

There are no comments at the moment.