الگوریتمهای STL: قدرت <algorithm>
مهمترین الگوریتمهای STL را فرا بگیرید — از جستجو و مرتبسازی تا تبدیل و تجمیع. بیاموزید چگونه الگوریتمها با تکرارگرها برای کد رسا و کارآمد ترکیب میشوند.
سرآیند <algorithm>
سرآیند بیش از ۱۰۰ الگوریتم عمومی شامل میشود که روی بازههای تکرارگر عمل میکنند. این الگوریتمها دههها علم کامپیوتر را تجسم میکنند — آزموده در میدان، بهینهسازی شده و صحیح. استفاده از آنها به جای حلقههای دستنوشته کد شما را کوتاهتر، رساتر و اغلب سریعتر میکند.
الگوریتمها در دستهبندیها سازماندهی شدهاند:
- غیرتغییردهنده: find، count، search، all_of/any_of/none_of، equal، mismatch
- تغییردهنده: copy، transform، fill، generate، replace، remove
- مرتبسازی و ترتیب: sort، stable_sort، partial_sort، nth_element
- جستجوی دودویی: lower_bound، upper_bound، binary_search، equal_range
- عددی (در ): accumulate، inner_product، partial_sum، iota
هر الگوریتم یک بازه تکرارگر [first, last) را به عنوان ورودی اصلی میگیرد.
الگوریتمهای غیرتغییردهنده
الگوریتمهای غیرتغییردهنده عناصر را بدون تغییر بررسی میکنند. آنها ابزارهای اصلی پرسوجو و اعتبارسنجی هستند.
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5};
// find — returns iterator to first match (or end)
auto it = std::find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
std::cout << "Found 5 at index " << std::distance(nums.begin(), it) << '\n';
}
// find_if — with a predicate
auto even = std::find_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
std::cout << "First even: " << *even << '\n'; // 4
// count / count_if
int fives = std::count(nums.begin(), nums.end(), 5);
int odds = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 != 0; });
std::cout << "Fives: " << fives << ", Odds: " << odds << '\n';
// all_of / any_of / none_of
bool all_positive = std::all_of(nums.begin(), nums.end(),
[](int x) { return x > 0; });
bool has_negative = std::any_of(nums.begin(), nums.end(),
[](int x) { return x < 0; });
std::cout << std::boolalpha;
std::cout << "All positive: " << all_positive << '\n'; // true
std::cout << "Has negative: " << has_negative << '\n'; // false
// equal — compare two ranges
std::vector<int> nums2 = {3, 1, 4, 1, 5, 9, 2, 6, 5};
bool same = std::equal(nums.begin(), nums.end(), nums2.begin());
std::cout << "Equal: " << same << '\n'; // true
}الگوریتمهای تغییردهنده
الگوریتمهای تغییردهنده مقادیر عناصر را تغییر میدهند یا عناصر را درون یک بازه بازآرایی میکنند.
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
// transform — apply a function to each element
std::vector<int> squared;
std::transform(src.begin(), src.end(), std::back_inserter(squared),
[](int x) { return x * x; });
// squared: {1, 4, 9, 16, 25}
// transform with two input ranges (binary transform)
std::vector<int> b = {10, 20, 30, 40, 50};
std::vector<int> sums;
std::transform(src.begin(), src.end(), b.begin(),
std::back_inserter(sums),
[](int a, int b) { return a + b; });
// sums: {11, 22, 33, 44, 55}
// fill and generate
std::vector<int> zeros(10);
std::fill(zeros.begin(), zeros.end(), 42); // all 42s
int counter = 0;
std::generate(zeros.begin(), zeros.end(),
[&counter]() { return counter++; });
// zeros: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// remove-erase idiom (pre-C++20)
std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};
auto new_end = std::remove(data.begin(), data.end(), 2);
data.erase(new_end, data.end()); // actually shrinks the vector
// data: {1, 3, 4, 5}
// C++20: simpler
std::erase(data, 3);
// data: {1, 4, 5}
// replace
std::replace(data.begin(), data.end(), 4, 99);
// data: {1, 99, 5}
for (int x : data) std::cout << x << ' ';
std::cout << '\n';
}الگوریتمهای مرتبسازی و ترتیب
STL چندین الگوریتم مرتبسازی با تعادلهای متفاوت ارائه میدهد. std::sort ابزار همهکاره عمومی (O(n log n) میانگین، introsort) است، در حالی که partial_sort و nth_element وقتی فقط به ترتیب جزئی نیاز دارید کارآمدتر هستند.
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
struct Student {
std::string name;
double gpa;
};
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9, 3, 7, 4, 6};
// sort — O(n log n), not stable
std::sort(nums.begin(), nums.end());
// nums: {1, 2, 3, 4, 5, 6, 7, 8, 9}
// sort descending with comparator
std::sort(nums.begin(), nums.end(), std::greater<int>{});
// nums: {9, 8, 7, 6, 5, 4, 3, 2, 1}
// stable_sort — preserves relative order of equal elements
std::vector<Student> students = {
{"Alice", 3.8}, {"Bob", 3.5}, {"Charlie", 3.8}, {"Diana", 3.5}
};
std::stable_sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.gpa > b.gpa; // descending GPA
});
// Alice before Charlie (both 3.8) — relative order preserved
// partial_sort — sort only the first k elements
std::vector<int> data = {5, 2, 8, 1, 9, 3};
std::partial_sort(data.begin(), data.begin() + 3, data.end());
// First 3 are sorted: {1, 2, 3, ...rest unspecified}
// nth_element — partition so the nth element is in its sorted position
std::vector<int> vals = {5, 2, 8, 1, 9, 3, 7};
std::nth_element(vals.begin(), vals.begin() + 3, vals.end());
// vals[3] == 5 (the median), left side <= 5, right side >= 5
// lower_bound / upper_bound — binary search on sorted data
std::vector<int> sorted = {1, 2, 4, 4, 4, 7, 9};
auto lo = std::lower_bound(sorted.begin(), sorted.end(), 4); // first 4
auto hi = std::upper_bound(sorted.begin(), sorted.end(), 4); // past last 4
std::cout << "Count of 4s: " << std::distance(lo, hi) << '\n'; // 3
}الگوریتمهای عددی (<numeric>)
سرآیند الگوریتمهایی برای محاسبات ریاضی روی بازهها فراهم میکند.
#include <numeric>
#include <vector>
#include <iostream>
#include <functional>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// accumulate — fold/reduce with initial value
int sum = std::accumulate(nums.begin(), nums.end(), 0);
std::cout << "Sum: " << sum << '\n'; // 15
// accumulate with custom operation (product)
int product = std::accumulate(nums.begin(), nums.end(), 1,
std::multiplies<int>{});
std::cout << "Product: " << product << '\n'; // 120
// inner_product — dot product
std::vector<int> weights = {2, 3, 1, 4, 2};
int dot = std::inner_product(nums.begin(), nums.end(),
weights.begin(), 0);
std::cout << "Dot product: " << dot << '\n'; // 1*2 + 2*3 + 3*1 + 4*4 + 5*2 = 37
// partial_sum — running total
std::vector<int> running;
std::partial_sum(nums.begin(), nums.end(), std::back_inserter(running));
// running: {1, 3, 6, 10, 15}
// iota — fill with incrementing values
std::vector<int> seq(10);
std::iota(seq.begin(), seq.end(), 1); // {1, 2, 3, ..., 10}
// C++17: reduce (parallel-friendly version of accumulate)
// int sum2 = std::reduce(nums.begin(), nums.end()); // same as accumulate for +
for (int x : running) std::cout << x << ' ';
std::cout << '\n';
}std::remove در واقع عناصر را از کانتینر حذف نمیکند. عناصر «نگهداشتهشده» را به جلو منتقل میکند و یک تکرارگر به انتهای منطقی جدید برمیگرداند. اندازه واقعی کانتینر تغییر نمیکند — باید erase() را فراخوانی کنید تا عناصر دنبالهای را فیزیکی حذف کنید:
std::vector<int> v = {1, 2, 3, 2, 4};
auto new_end = std::remove(v.begin(), v.end(), 2);
// v is now: {1, 3, 4, ?, ?} with size still 5
v.erase(new_end, v.end()); // NOW size is 3در C++20، std::erase(container, value) و std::erase_if(container, predicate) این الگو را در یک فراخوانی انجام میدهند. وقتی در دسترس هستند آنها را ترجیح دهید.
الگوریتمها وقتی ترکیب شوند میدرخشند. به جای حلقههای پیچیده، الگوریتمها را زنجیرهای کنید:
// Get sorted unique elements
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
// Copy only even numbers, squared, to output
std::vector<int> result;
std::copy_if(v.begin(), v.end(), std::back_inserter(result),
[](int x) { return x % 2 == 0; });
std::transform(result.begin(), result.end(), result.begin(),
[](int x) { return x * x; });این سبک واضح، صحیح و اغلب از حلقههای دستنوشته سریعتر است زیرا پیادهسازیهای کتابخانه از SIMD، کد بدون شاخه و سایر بهینهسازیهای ریز استفاده میکنند.
- سرآیند
بیش از ۱۰۰ الگوریتم عمومی فراهم میکند که روی هر بازه تکرارگر کار میکنند std::sortاز نوع O(n log n) introsort است؛std::stable_sortترتیب عناصر برابر را حفظ میکندstd::transformعملیات «نگاشت» تابعی است — یک تابع را روی هر عنصر اعمال میکندstd::removeحذف نمیکند — از الگوی remove-erase یاstd::erase_ifسیپلاسپلاس ۲۰ استفاده کنیدstd::accumulate(از) عملیات عمومی fold/reduce است- الگوریتمها را به حلقههای دستنوشته ترجیح دهید — آنها واضحتر، امنتر و اغلب سریعتر هستند
آزمون — دانش خود را بسنجید
(20 امتیاز)1. `std::remove` چه چیزی برمیگرداند؟
2. کدام الگوریتم را برای بررسی اینکه آیا تمام عناصر یک بازه یک محمول را برآورده میکنند استفاده میکنید؟
3. پیچیدگی زمانی `std::nth_element` چیست؟