Bài 12.4: Bài tập thực hành mảng 1 chiều nâng cao trong C++

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
void print_vector(const vector<int>& a) {
cout << "[";
for (size_t i = 0; i < a.size(); ++i) {
cout << a[i] << (i == a.size() - 1 ? "" : ", ");
}
cout << "]" << endl;
}
int main() {
vector<int> d1 = {1, 5, 3, 8, 2, 5, 9, 5, 4, 6};
vector<int> d2 = {-2, 7, 0, -5, 10, 3, -8};
vector<int> d3 = {10, 20, 30, 40, 50};
vector<int> d4 = {5, 5, 5, 5, 5};
vector<int> rong;
cout << "--- Bat dau cac bai tap thuc hanh ---" << endl;
// --- Cac bai tap ---
return 0;
}
Bài tập 1: Tìm tất cả các vị trí xuất hiện của một phần tử
Bài toán: Cho một mảng và một giá trị x
, tìm tất cả các chỉ số (index) trong mảng mà phần tử tại đó có giá trị bằng x
.
Đây là một biến thể của bài toán tìm kiếm cơ bản. Thay vì chỉ tìm vị trí đầu tiên, chúng ta cần tìm tất cả các vị trí.
cout << "\n## Bai tap 1: Tim tat ca cac vi tri xuat hien" << endl;
int x = 5;
vector<int> vi_tri;
cout << "Vector: ";
print_vector(d1);
cout << "Tim cac vi tri cua gia tri: " << x << endl;
for (size_t i = 0; i < d1.size(); ++i) {
if (d1[i] == x) {
vi_tri.push_back(i);
}
}
cout << "Cac vi tri tim thay: ";
print_vector(vi_tri);
x = 100;
vi_tri.clear();
cout << "\nVector: ";
print_vector(d1);
cout << "Tim cac vi tri cua gia tri: " << x << endl;
for (size_t i = 0; i < d1.size(); ++i) {
if (d1[i] == x) {
vi_tri.push_back(i);
}
}
cout << "Cac vi tri tim thay: ";
print_vector(vi_tri);
cout << "--------------------" << endl;
## Bai tap 1: Tim tat ca cac vi tri xuat hien
Vector: [1, 5, 3, 8, 2, 5, 9, 5, 4, 6]
Tim cac vi tri cua gia tri: 5
Cac vi tri tim thay: [1, 5, 7]
Vector: [1, 5, 3, 8, 2, 5, 9, 5, 4, 6]
Tim cac vi tri cua gia tri: 100
Cac vi tri tim thay: []
--------------------
Giải thích: Chúng ta duyệt qua từng phần tử của vector
data1
bằng một vòng lặp for
thông thường, sử dụng chỉ số i
. Tại mỗi chỉ số, chúng ta kiểm tra xem data1[i]
có bằng target_value
hay không. Nếu có, chúng ta thêm chỉ số i
vào vector
indices
bằng phương thức push_back()
. Cuối cùng, chúng ta in nội dung của indices
để biết các vị trí đã tìm thấy. Nếu không tìm thấy giá trị nào, vector indices
sẽ vẫn rỗng.
Bài tập 2: Tìm phần tử lớn nhất, nhỏ nhất và vị trí của chúng
Bài toán: Tìm giá trị lớn nhất và nhỏ nhất trong mảng, cùng với vị trí (chỉ số) đầu tiên mà chúng xuất hiện.
Bài toán này là bài toán kinh điển khi làm việc với mảng. Chúng ta có thể làm điều này một cách thủ công hoặc sử dụng các hàm sẵn có trong C++. Với mục tiêu "nâng cao", chúng ta sẽ xem xét cách sử dụng thư viện chuẩn.
cout << "\n## Bai tap 2: Tim phan tu lon nhat, nho nhat va vi tri" << endl;
if (!d2.empty()) {
cout << "Vector: ";
print_vector(d2);
auto max_it = max_element(d2.begin(), d2.end());
int max_gia_tri = *max_it;
int max_vi_tri = distance(d2.begin(), max_it);
cout << "Phan tu lon nhat: " << max_gia_tri << " tai vi tri (dau tien): " << max_vi_tri << endl;
auto min_it = min_element(d2.begin(), d2.end());
int min_gia_tri = *min_it;
int min_vi_tri = distance(d2.begin(), min_it);
cout << "Phan tu nho nhat: " << min_gia_tri << " tai vi tri (dau tien): " << min_vi_tri << endl;
} else {
cout << "Vector rong, khong tim duoc min/max." << endl;
}
cout << "--------------------" << endl;
## Bai tap 2: Tim phan tu lon nhat, nho nhat va vi tri
Vector: [-2, 7, 0, -5, 10, 3, -8]
Phan tu lon nhat: 10 tai vi tri (dau tien): 4
Phan tu nho nhat: -8 tai vi tri (dau tien): 6
--------------------
Giải thích: Thay vì viết vòng lặp thủ công để theo dõi min/max, chúng ta sử dụng hai hàm tiện ích từ thư viện <algorithm>
: max_element()
và min_element()
. Các hàm này nhận vào hai iterator (ở đây là data2.begin()
và data2.end()
đại diện cho toàn bộ vector) và trả về một iterator trỏ đến phần tử lớn nhất hoặc nhỏ nhất đầu tiên tìm được trong phạm vi đó.
*max_it
(hoặc*min_it
) dùng để lấy giá trị thực sự tại vị trí mà iterator trỏ tới.- Hàm
distance(data2.begin(), max_it)
(hoặcmin_it
) từ thư viện<iterator>
(thường được include sẵn khi dùng<vector>
hoặc<algorithm>
) dùng để tính khoảng cách từ iterator bắt đầu (data2.begin()
) đến iterator màmax_element
(hoặcmin_element
) trả về. Khoảng cách này chính là chỉ số của phần tử đó trong vector. - Chúng ta cũng thêm kiểm tra
!data2.empty()
để đảm bảo vector không rỗng trước khi thực hiện tìm kiếm, tránh lỗi.
Bài tập 3: Tính tổng các phần tử thỏa mãn một điều kiện
Bài toán: Tính tổng các phần tử trong mảng mà giá trị của chúng thỏa mãn một điều kiện nhất định (ví dụ: tổng các số dương, tổng các số chẵn, tổng các số lớn hơn 10, v.v.).
Bài toán này giúp rèn luyện khả năng kết hợp duyệt mảng với kiểm tra điều kiện. Chúng ta có thể làm điều này bằng vòng lặp for
, hoặc sử dụng các hàm tổng hợp mạnh mẽ hơn.
cout << "\n## Bai tap 3: Tinh tong cac phan tu thoa man dieu kien" << endl;
cout << "Vector: ";
print_vector(d2);
long long tong_duong = 0;
for (int v : d2) {
if (v > 0) {
tong_duong += v;
}
}
cout << "Tong cac so duong trong vector: " << tong_duong << endl;
long long tong_chan = accumulate(d2.begin(), d2.end(), 0LL,
[](long long t, int v){
if (v % 2 == 0) {
return t + v;
} else {
return t;
}
});
cout << "Tong cac so chan trong vector: " << tong_chan << endl;
cout << "--------------------" << endl;
## Bai tap 3: Tinh tong cac phan tu thoa man dieu kien
Vector: [-2, 7, 0, -5, 10, 3, -8]
Tong cac so duong trong vector: 20
Tong cac so chan trong vector: 0
--------------------
Giải thích:
- Ví dụ 1 (Vòng lặp
for
): Chúng ta sử dụng vòng lặprange-based for
(for (int val : data2)
) để duyệt qua từng phần tửval
trongdata2
một cách trực tiếp mà không cần dùng chỉ số. Nếuval
lớn hơn 0, chúng ta cộng nó vàosum_positives
. - Ví dụ 2 (
accumulate
): Đây là cách tiếp cận nâng cao hơn sử dụng hàmaccumulate()
từ thư viện<numeric>
. Hàm này dùng để tính tổng (hoặc một phép toán kết hợp khác) trên một dãy các phần tử.data2.begin(), data2.end()
: Phạm vi các phần tử cần xử lý.0LL
: Giá trị khởi tạo ban đầu cho tổng (kiểulong long
).[](long long current_sum, int val){ ... }
: Đây là một lambda function (hàm ẩn danh). Nó được gọi cho mỗi phần tử trong phạm vi. Nó nhận vàocurrent_sum
(tổng tính được cho đến thời điểm hiện tại) vàval
(phần tử hiện tại). Logic bên trong lambda kiểm tra nếuval
là số chẵn (val % 2 == 0
), nó trả vềcurrent_sum + val
. Nếu không, nó chỉ trả vềcurrent_sum
(không cộngval
vào).accumulate
sẽ lặp lại quá trình này cho đến hết vector và trả về giá trị cuối cùng củacurrent_sum
.
Cách sử dụng accumulate
với lambda thể hiện sự mạnh mẽ và linh hoạt của thư viện chuẩn C++ trong việc xử lý mảng/vector.
Bài tập 4: Đếm số lần xuất hiện của một phần tử
Bài toán: Đếm xem một giá trị x
xuất hiện bao nhiêu lần trong mảng.
Bài toán này tương tự bài tìm vị trí, nhưng thay vì lưu các vị trí, chúng ta chỉ cần một bộ đếm.
cout << "\n## Bai tap 4: Dem so lan xuat hien cua mot phan tu" << endl;
cout << "Vector: ";
print_vector(d1);
int x = 5;
int dem_vong_lap = 0;
for (int v : d1) {
if (v == x) {
dem_vong_lap++;
}
}
cout << "So lan xuat hien cua " << x << " (dung loop): " << dem_vong_lap << endl;
int dem_stl = count(d1.begin(), d1.end(), x);
cout << "So lan xuat hien cua " << x << " (dung count): " << dem_stl << endl;
cout << "--------------------" << endl;
## Bai tap 4: Dem so lan xuat hien cua mot phan tu
Vector: [1, 5, 3, 8, 2, 5, 9, 5, 4, 6]
So lan xuat hien cua 5 (dung loop): 3
So lan xuat hien cua 5 (dung count): 3
--------------------
Giải thích:
- Cách 1 (Vòng lặp): Đây là cách làm trực tiếp, duyệt qua từng phần tử và tăng biến đếm nếu phần tử đó bằng giá trị cần đếm.
- Cách 2 (
count
): Thư viện<algorithm>
cung cấp hàmcount()
. Hàm này nhận vào phạm vi (data1.begin()
,data1.end()
) và giá trị cần đếm (value_to_count
). Nó sẽ tự động duyệt qua phạm vi và trả về số lần giá trị đó xuất hiện. Cách này ngắn gọn và thể hiện việc tận dụng thư viện chuẩn, rất được khuyến khích trong C++.
Bài tập 5: Đảo ngược mảng
Bài toán: Đảo ngược thứ tự các phần tử trong mảng (phần tử đầu thành cuối, cuối thành đầu, v.v.).
Bài toán này yêu cầu thay đổi vị trí của các phần tử. Có thể làm bằng cách tạo mảng mới hoặc đảo chỗ trực tiếp trong mảng gốc.
cout << "\n## Bai tap 5: Dao nguoc mang" << endl;
vector<int> goc = {1, 2, 3, 4, 5, 6};
cout << "Vector goc: ";
print_vector(goc);
vector<int> moi;
for (int i = goc.size() - 1; i >= 0; --i) {
moi.push_back(goc[i]);
}
cout << "Vector dao nguoc (tao moi): ";
print_vector(moi);
vector<int> dao_tai_cho = goc;
reverse(dao_tai_cho.begin(), dao_tai_cho.end());
cout << "Vector dao nguoc (tai cho dung reverse): ";
print_vector(dao_tai_cho);
cout << "--------------------" << endl;
## Bai tap 5: Dao nguoc mang
Vector goc: [1, 2, 3, 4, 5, 6]
Vector dao nguoc (tao moi): [6, 5, 4, 3, 2, 1]
Vector dao nguoc (tai cho dung reverse): [6, 5, 4, 3, 2, 1]
--------------------
Giải thích:
- Cách 1 (Tạo vector mới): Chúng ta tạo một vector
reversed_data_new
rỗng. Sau đó, duyệt quaoriginal_data
từ cuối về đầu (sử dụng vòng lặpfor
với chỉ số giảm dần từsize() - 1
về 0). Mỗi phần tử duyệt được từoriginal_data[i]
, chúng ta thêm vào cuối củareversed_data_new
. - Cách 2 (
reverse
): Thư viện<algorithm>
cung cấp hàmreverse()
. Hàm này nhận vào phạm vi (data_to_reverse.begin()
,data_to_reverse.end()
) và thực hiện đảo ngược thứ tự các phần tử trong chính phạm vi đó (hay còn gọi là đảo ngược "tại chỗ" - in-place), không tạo ra vector mới. Đây là cách ngắn gọn và hiệu quả hơn cho bài toán này.
Comments