This programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.
Thư viện chuẩn STL (Standard Template Library) không chỉ cung cấp các container mạnh mẽ mà còn cung
cấp một bộ sưu tập khlosit các thuật toán được tối ưu hóa để làm việc với những container này. Thay vì
viết các vòng lặp thủ công, bạn có thể dùng <algorithm> header để truy cập hàng
chục thuật toán tiêu chuẩn như std::sort, std::find,
std::transform, và nhiều hơn nữa. Bài học này sẽ dạy bạn cách sử dụng những thuật toán
này một cách hiệu quả, kết hợp với Lambda Expressions, Function Objects, và Custom Comparators để viết
code C++ sạch, mạnh mẽ và tối ưu.
1. Giới thiệu STL Algorithms
STL Algorithms hoạt động trên Iterators (không phải trực tiếp trên containers), điều này cho phép chúng hoạt động với bất kỳ container nào miễn là container đó cung cấp các iterator thích hợp. Paradigm cốt lõi là: Algorithms + Iterators + Function Objects/Lambdas.
Tại sao dùng STL Algorithms?
- Hiệu năng: Các thuật toán này được tối ưu hóa bởi các chuyên gia, thường nhanh hơn code thủ công.
- Tính chính xác: Đã được kiểm thử kỹ lưỡng, giảm lỗi logic.
- Code tinh gọn: Thay thế vòng lặp dài bằng một dòng code có ý nghĩa.
- Khả năng tái sử dụng: Hoạt động với mọi container hỗ trợ iterators thích hợp.
- Functional paradigm: Hỗ trợ lập trình hàm (functional programming) với lambda và function objects.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9, 3};
// Cách cũ: Vòng lặp thủ công
for (size_t i = 0; i < nums.size(); ++i) {
for (size_t j = i + 1; j < nums.size(); ++j) {
if (nums[i] > nums[j]) {
std::swap(nums[i], nums[j]);
}
}
}
// Cách hiện đại: STL Algorithm
std::sort(nums.begin(), nums.end());
return 0;
}
2. Searching & Sorting Algorithms
Nhóm thuật toán này giúp tìm kiếm phần tử hoặc sắp xếp dữ liệu một cách hiệu quả. Độ phức tạp thời gian rất quan trọng khi xử lý dữ liệu lớn.
a) std::find & std::find_if — Tìm kiếm tuyến tính
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {10, 20, 30, 40, 50};
// std::find: Tìm giá trị chính xác
auto it = std::find(nums.begin(), nums.end(), 30);
if (it != nums.end()) {
std::cout << "Found: " << *it << std::endl; // Found: 30
}
// std::find_if: Tìm phần tử thỏa điều kiện (predicate)
auto it2 = std::find_if(nums.begin(), nums.end(),
[](int x) { return x > 25; });
if (it2 != nums.end()) {
std::cout << "First > 25: " << *it2 << std::endl; // First > 25: 30
}
// Đếm số phần tử thỏa điều kiện
int count = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 10 == 0; });
std::cout << "Count divisible by 10: " << count << std::endl; // 5
return 0;
}
b) std::sort, std::stable_sort & std::nth_element
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9, 3, 7};
// std::sort: Sắp xếp nhanh (quick sort)
std::vector<int> nums1 = nums;
std::sort(nums1.begin(), nums1.end()); // {1, 2, 3, 5, 7, 8, 9}
// std::sort với custom comparator
std::vector<int> nums2 = nums;
std::sort(nums2.begin(), nums2.end(), std::greater<int>()); // Sắp xếp giảm dần
// std::stable_sort: Sắp xếp nhưng giữ thứ tự tương đối của các phần tử bằng nhau
struct Person { std::string name; int age; };
std::vector<Person> people = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}};
std::stable_sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
// Người 30 tuổi giữ thứ tự gốc: Alice trước Charlie
// std::nth_element: Tìm phần tử thứ n (k-th largest/smallest)
std::vector<int> nums3 = nums;
auto it = nums3.begin() + 3; // Muốn phần tử thứ 3 (index 3)
std::nth_element(nums3.begin(), it, nums3.end());
std::cout << "3rd smallest: " << *it << std::endl; // O(n) thay vì O(n log n)
return 0;
}
Bảng so sánh độ phức tạp Searching & Sorting:
| Thuật toán | Độ phức tạp Avg | Worst Case | Ghi chú |
|---|---|---|---|
| std::find | O(n) | O(n) | Tuyến tính, không cần sắp xếp trước |
| std::binary_search | O(log n) | O(log n) | Cần mảng sắp xếp trước |
| std::sort | O(n log n) | O(n log n) | Hybrid (introsort), không bảo toàn thứ tự |
| std::stable_sort | O(n log n) | O(n log n) | Bảo toàn thứ tự các phần tử bằng nhau |
| std::nth_element | O(n) | O(n²) | Tìm phần tử thứ k nhanh hơn sort |
3. Transformation Algorithms
Nhóm thuật toán này biến đổi hoặc sao chép dữ liệu từ một container sang container khác hoặc thay đổi các phần tử hiện tại.
a) std::transform — Áp dụng hàm cho mỗi phần tử
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> result;
// Transform: Nhân mỗi phần tử với 2
std::transform(nums.begin(), nums.end(),
std::back_inserter(result),
[](int x) { return x * 2; });
// result = {2, 4, 6, 8, 10}
// Transform hai container: Cộng phần tử tương ứng
std::vector<int> nums1 = {1, 2, 3};
std::vector<int> nums2 = {10, 20, 30};
std::vector<int> sums;
std::transform(nums1.begin(), nums1.end(),
nums2.begin(),
std::back_inserter(sums),
[](int a, int b) { return a + b; });
// sums = {11, 22, 33}
return 0;
}
b) Erase-Remove Idiom: std::remove & std::remove_if
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Xóa tất cả số chẵn: Remove-Erase Idiom
// remove() di chuyển phần tử không cần xóa lên trước, trả về new end
// erase() thực sự xóa các phần tử ở cuối
nums.erase(
std::remove_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; }),
nums.end()
);
// nums = {1, 3, 5, 7, 9}
// std::unique: Xóa các phần tử trùng nhau liên tiếp
std::vector<int> nums2 = {1, 1, 2, 2, 2, 3, 3, 4};
nums2.erase(
std::unique(nums2.begin(), nums2.end()),
nums2.end()
);
// nums2 = {1, 2, 3, 4}
return 0;
}
c) std::reverse & std::shuffle
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// Đảo ngược thứ tự
std::reverse(nums.begin(), nums.end());
// nums = {5, 4, 3, 2, 1}
// Xáo trộn ngẫu nhiên
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(nums.begin(), nums.end(), gen);
// nums = {4, 2, 5, 1, 3} (ngẫu nhiên)
return 0;
}
4. Numeric Algorithms
Nhóm thuật toán này thực hiện các phép toán số học trên các dãy phần tử, bao gồm tính tổng, tích, hiệu phân, v.v.
a) std::accumulate & std::inner_product
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// std::accumulate: Tính tổng (hoặc phép toán tùy chỉnh)
int sum = std::accumulate(nums.begin(), nums.end(), 0);
std::cout << "Sum: " << sum << std::endl; // 15
// Tính tích
int product = std::accumulate(nums.begin(), nums.end(), 1,
[](int a, int b) { return a * b; });
std::cout << "Product: " << product << std::endl; // 120
// std::inner_product: Tích vô hướng (dot product)
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
int dotProduct = std::inner_product(a.begin(), a.end(),
b.begin(), 0);
std::cout << "Dot product: " << dotProduct << std::endl; // 1*4 + 2*5 + 3*6 = 32
return 0;
}
b) std::iota & std::partial_sum
#include <iostream>
#include <vector>
#include <numeric>
int main() {
// std::iota: Điền các giá trị liên tiếp
std::vector<int> range(5);
std::iota(range.begin(), range.end(), 10);
// range = {10, 11, 12, 13, 14}
// std::partial_sum: Tính tổng tích lũy (running sum)
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> sums;
std::partial_sum(nums.begin(), nums.end(),
std::back_inserter(sums));
// sums = {1, 3, 6, 10, 15}
return 0;
}
5. Partition & Set Operations
Các thuật toán này chia container thành các phần hoặc thực hiện các phép hợp, giao, hiệu của hai tập hợp sắp xếp.
a) std::partition & std::is_partitioned
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {5, 1, 9, 3, 7, 2, 8, 4};
// Partition: Chia thành 2 phần (< 5 và >= 5)
auto it = std::partition(nums.begin(), nums.end(),
[](int x) { return x < 5; });
// Phần trước it: tất cả < 5 (thứ tự không đảm bảo)
// Phần sau it: tất cả >= 5
// std::stable_partition: Giữ thứ tự tương đối
std::stable_partition(nums.begin(), nums.end(),
[](int x) { return x < 5; });
// std::is_partitioned: Kiểm tra xem đã partition chưa
bool isPartitioned = std::is_partitioned(nums.begin(), nums.end(),
[](int x) { return x < 5; });
std::cout << (isPartitioned ? "Partitioned" : "Not partitioned") << std::endl;
return 0;
}
b) Set Operations: Merge, Union, Intersection, Difference
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 3, 5, 7, 9};
std::vector<int> b = {2, 3, 5, 8};
// std::set_union: A ∪ B (toàn bộ phần tử từ A và B, không trùng)
std::vector<int> unionSet;
std::set_union(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(unionSet));
// unionSet = {1, 2, 3, 5, 7, 8, 9}
// std::set_intersection: A ∩ B (chỉ phần tử chung)
std::vector<int> intersectionSet;
std::set_intersection(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(intersectionSet));
// intersectionSet = {3, 5}
// std::set_difference: A - B (chỉ phần tử trong A không trong B)
std::vector<int> diffSet;
std::set_difference(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(diffSet));
// diffSet = {1, 7, 9}
// std::merge: Gộp 2 mảng sắp xếp thành 1
std::vector<int> merged;
std::merge(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(merged));
// merged = {1, 2, 3, 3, 5, 5, 7, 8, 9}
return 0;
}
6. Functional Programming với Algorithms
Lambda expressions và function objects cho phép viết code tuyệt vời bằng functional paradigm, nơi bạn xây dựng các đường ống xử lý dữ liệu phức tạp từ các hàm nhỏ.
Function Objects vs Lambda vs std::function
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
// Function Object (Functor)
class Multiplier {
int factor;
public:
Multiplier(int f) : factor(f) {}
int operator()(int x) const { return x * factor; }
};
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 1. Lambda (Inline, dễ nhất)
std::transform(nums.begin(), nums.end(),
nums.begin(),
[](int x) { return x * 10; });
// nums = {10, 20, 30, 40, 50}
// 2. Function Object (Stateful, tái sử dụng)
Multiplier mult(5);
std::transform(nums.begin(), nums.end(),
nums.begin(),
mult);
// nums = {50, 100, 150, 200, 250}
// 3. std::function (Type-erased, chậm hơn nhưng linh hoạt)
std::function<int(int)> operation = [](int x) { return x + 1; };
std::transform(nums.begin(), nums.end(),
nums.begin(),
operation);
return 0;
}
Composing Algorithms: Multi-stage Data Processing Pipeline
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> result;
// Pipeline: Filter -> Transform -> Collect
// Bước 1: Lọc số chẵn
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(evens),
[](int x) { return x % 2 == 0; });
// evens = {2, 4, 6, 8, 10}
// Bước 2: Transform (nhân 2)
std::vector<int> doubled;
std::transform(evens.begin(), evens.end(),
std::back_inserter(doubled),
[](int x) { return x * 2; });
// doubled = {4, 8, 12, 16, 20}
// Bước 3: Filter > 10
std::copy_if(doubled.begin(), doubled.end(),
std::back_inserter(result),
[](int x) { return x > 10; });
// result = {12, 16, 20}
for (int x : result) {
std::cout << x << " "; // In: 12 16 20
}
return 0;
}
7. Custom Comparators & Predicates
Comparators định nghĩa cách so sánh hai phần tử, còn predicates là các hàm trả về bool dùng trong các thuật toán lọc.
a) std::less, std::greater & Custom Comparators
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Person {
std::string name;
int age;
};
// Custom comparator function
bool compareByAge(const Person& a, const Person& b) {
return a.age < b.age;
}
// Custom comparator using lambda (preferred)
auto compareByName = [](const Person& a, const Person& b) {
return a.name < b.name;
};
int main() {
std::vector<Person> people = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
// Sort bằng comparator tùy chỉnh
std::sort(people.begin(), people.end(), compareByAge);
// Sorted by age: Bob(25), Alice(30), Charlie(35)
// Sort theo tên
std::sort(people.begin(), people.end(), compareByName);
// Sorted by name: Alice, Bob, Charlie
// Dùng std::greater: Giảm dần
std::vector<int> nums = {5, 2, 8, 1, 9};
std::sort(nums.begin(), nums.end(), std::greater<int>());
// nums = {9, 8, 5, 2, 1}
return 0;
}
b) Predicates với sort, find_if, count_if
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// count_if: Đếm phần tử thỏa predicate
int evenCount = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
std::cout << "Even numbers: " << evenCount << std::endl; // 5
// find_if: Tìm phần tử đầu tiên thỏa predicate
auto it = std::find_if(nums.begin(), nums.end(),
[](int x) { return x > 6; });
if (it != nums.end()) {
std::cout << "First > 6: " << *it << std::endl; // 7
}
// all_of: Kiểm tra tất cả phần tử có thỏa điều kiện
bool allEven = std::all_of(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
std::cout << (allEven ? "All even" : "Not all even") << std::endl; // Not all even
// any_of: Kiểm tra có ít nhất một phần tử thỏa điều kiện
bool hasOdd = std::any_of(nums.begin(), nums.end(),
[](int x) { return x % 2 != 0; });
std::cout << (hasOdd ? "Has odd" : "No odd") << std::endl; // Has odd
return 0;
}
8. Performance Tips & Best Practices
Dù STL Algorithms hiệu năng cao, nhưng vẫn cần chú ý một số điểm để tối ưu hóa tối đa:
a) Cache Locality & In-place Operations
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// BAD: Copy-based approach (allocates memory)
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(evens),
[](int x) { return x % 2 == 0; });
// GOOD: In-place approach (no allocation)
nums.erase(
std::remove_if(nums.begin(), nums.end(),
[](int x) { return x % 2 != 0; }),
nums.end()
);
// Tip: std::vector is cache-friendly (contiguous memory)
// std::list is not (scattered pointers across memory)
// Prefer std::vector for algorithms when possible
return 0;
}
b) Choosing the Right Algorithm & Container
QUYẾT ĐỊNH CHỌN THUẬT TOÁN:
1. Bạn muốn tìm phần tử?
- std::find (bất kỳ), O(n)
- std::binary_search (mảng sắp xếp), O(log n)
- std::unordered_map/set.find(), O(1) avg
2. Bạn muốn sắp xếp?
- std::sort (nhanh nhất), O(n log n)
- std::stable_sort (giữ thứ tự), O(n log n)
- std::partial_sort (top-k), O(n log k)
3. Bạn muốn biến đổi dữ liệu?
- std::transform (map), O(n)
- std::copy_if (filter), O(n)
- std::partition (split), O(n)
4. Bạn có 2 tập hợp sắp xếp?
- std::set_union (∪)
- std::set_intersection (∩)
- std::set_difference (-)
- std::merge (nối 2 mảng sắp xếp)
5. Bạn muốn tính toán?
- std::accumulate (sum/product)
- std::inner_product (dot product)
- std::partial_sum (running sum)
Quiz: Algorithm Complexity
Câu hỏi: Tìm thuật toán có độ phức tạp O(n) trong danh sách chưa sắp xếp
- std::binary_search — Sai (O(log n) nhưng cần mảng sắp xếp)
- std::find — Đúng (O(n))
- std::find_if — Đúng (O(n))
- std::nth_element — Đúng (O(n) trung bình)
9. Complete Example: Data Processing Pipeline
Dưới đây là ví dụ thực tế xử lý dữ liệu người dùng:
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
struct User {
std::string name;
int age;
int purchases; // Số lần mua hàng
double spent; // Tổng tiền chi tiêu
};
int main() {
std::vector<User> users = {
{"Alice", 25, 10, 500.0},
{"Bob", 30, 5, 200.0},
{"Charlie", 35, 15, 800.0},
{"Diana", 28, 8, 350.0}
};
// 1. Sắp xếp theo số tiền chi tiêu (giảm dần)
std::sort(users.begin(), users.end(),
[](const User& a, const User& b) {
return a.spent > b.spent;
});
std::cout << "Top spenders:\n";
for (const auto& user : users) {
std::cout << user.name << ": $" << user.spent << "\n";
}
// 2. Tính tổng tiền từ tất cả người dùng
double totalSpent = std::accumulate(
users.begin(), users.end(), 0.0,
[](double sum, const User& user) { return sum + user.spent; }
);
std::cout << "\nTotal spent: $" << totalSpent << "\n";
// 3. Đếm người dùng >= 30 tuổi
int seniorCount = std::count_if(
users.begin(), users.end(),
[](const User& user) { return user.age >= 30; }
);
std::cout << "Users >= 30 years: " << seniorCount << "\n";
// 4. Tìm người dùng chi tiêu nhiều nhất
auto topSpender = std::max_element(
users.begin(), users.end(),
[](const User& a, const User& b) { return a.spent < b.spent; }
);
if (topSpender != users.end()) {
std::cout << "Top spender: " << topSpender->name << "\n";
}
return 0;
}
Tải mã nguồn mẫu bài học
Bạn có thể tải file mã nguồn C++ mẫu hoàn chỉnh của bài học này để tiến hành thực hành trực tiếp trên máy tính cá nhân.
Tải stl_algorithms_demo.cpp
Comments
Bình luận