انتقل إلى المحتوى

خوارزميات STL: قوة <الخوارزمية>

أتقن أهم خوارزميات STL - بدءًا من البحث والفرز ووصولاً إلى التحويل والتراكم. تعرف على كيفية تكوين الخوارزميات مع التكرارات للحصول على تعليمات برمجية معبرة وفعالة.

The <algorithm> Header

يحتوي رأسعلى أكثر من 100 خوارزمية عامة تعمل على نطاقات التكرار. تجسد هذه الخوارزميات عقودًا من علوم الكمبيوتر - تم اختبارها وتحسينها وصحيحة في المعركة. إن استخدامها بدلاً من الحلقات المكتوبة بخط اليد يجعل التعليمات البرمجية الخاصة بك أقصر وأكثر تعبيراً وأسرع في كثير من الأحيان.

يتم تنظيم الخوارزميات إلى فئات:

- غير قابل للتعديل: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)كمدخل أساسي لها.

Non-Modifying Algorithms

Non-modifying algorithms inspect elements without changing them. They are the workhorses of querying and validation.

non_modifying.cpp
#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
}

Modifying Algorithms

Modifying algorithms change element values or rearrange elements within a range.

modifying_algorithms.cpp
#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';
}

Sorting and Ordering Algorithms

The STL provides several sorting algorithms with different trade-offs.std::sortis the general-purpose workhorse (O(n log n) average, introsort), whilepartial_sortandnth_elementare more efficient when you only need partial ordering.

sorting.cpp
#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_algorithms.cpp
#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

In C++20, std::erase(container, value) and 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، والتعليمات البرمجية الخالية من الفروع، والتحسينات الدقيقة الأخرى.

النقاط الرئيسية
  • يوفر رأسأكثر من 100 خوارزمية عامة تعمل على أي نطاق مكرر
  • std::sortهو مقدمة O(n log n)؛ يحافظstd::stable_sortعلى ترتيب العناصر المتساوية
  • std::transformهي "الخريطة" الوظيفية - التي تطبق وظيفة على كل عنصر
  • std::removeلا يمسح — استخدم مصطلح إزالة المسح، أو C++20std::erase_if
  • std::accumulate(من) هي عملية الطي/التصغير العامة
  • تفضل الخوارزميات على الحلقات المكتوبة بخط اليد، فهي أكثر وضوحًا وأمانًا وأسرع في كثير من الأحيان

اختبار — اختبر معرفتك

(20 نقطة خبرة)

1. ماذا يعود`std::remove`؟

2. ما الخوارزمية التي ستستخدمها للتحقق مما إذا كانت جميع العناصر الموجودة في النطاق تلبي المسند؟

3. What is the time complexity of`std::nth_element`?