सामग्री पर जाएं

STL एल्गोरिदम: <algorithm> की ताक़त

सबसे महत्वपूर्ण STL एल्गोरिदम में महारत हासिल करें — सर्चिंग और सॉर्टिंग से लेकर ट्रांसफ़ॉर्म और एक्यूमुलेट करने तक। सीखें कि एल्गोरिदम इटरेटर के साथ मिलकर अभिव्यंजक और कुशल कोड कैसे बनाते हैं।

<algorithm> हेडर

हेडर में 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.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.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';
}

सॉर्टिंग और ऑर्डरिंग एल्गोरिदम

STL अलग-अलग ट्रेड-ऑफ़ वाले कई सॉर्टिंग एल्गोरिदम देता है। std::sort सामान्य-उद्देश्य का मुख्य एल्गोरिदम है (औसतन O(n log n), introsort), जबकि जब आपको सिर्फ़ आंशिक ऑर्डरिंग चाहिए, तब partial_sort और nth_element ज़्यादा कुशल होते हैं।

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>)

हेडर रेंज पर गणितीय गणनाओं के लिए एल्गोरिदम देता है।

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 अब है: {1, 3, 4, ?, ?} और साइज़ अब भी 5 है
v.erase(new_end, v.end());  // अब साइज़ 3 है

C++20 में, std::erase(container, value) और std::erase_if(container, predicate) इस इडियम को एक ही कॉल में संभाल लेते हैं। उपलब्ध होने पर इन्हें प्राथमिकता दें।

सर्वोत्तम अभ्यास

एल्गोरिदम तब सबसे अच्छे लगते हैं जब उन्हें जोड़ा जाए। जटिल लूप की जगह एल्गोरिदम को एक-दूसरे से जोड़ें:

// सॉर्टेड यूनिक एलिमेंट पाएँ
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());

// सिर्फ़ सम संख्याओं को, वर्ग करके, आउटपुट में कॉपी करें
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; });

यह शैली स्पष्ट और सही है, और ऑप्टिमाइज़ेशन चालू होने पर आम तौर पर हाथ से लिखे लूप जितनी ही तेज़ होती है (कुछ मामलों में लाइब्रेरी इम्प्लीमेंटेशन के विशेष ऑप्टिमाइज़ेशन से बेहतर भी हो सकती है)। असली फ़ायदा यह है कि कंपाइलर और लाइब्रेरी को ऑप्टिमाइज़ करने के ज़्यादा मौक़े मिलते हैं; हमेशा मापकर देखें।

मुख्य बातें
  • हेडर 100+ जेनेरिक एल्गोरिदम देता है जो किसी भी इटरेटर रेंज पर काम करते हैं
  • std::sort O(n log n) का introsort है; std::stable_sort बराबर एलिमेंट का क्रम बनाए रखता है
  • std::transform फ़ंक्शनल "map" है — यह हर एलिमेंट पर एक फ़ंक्शन लागू करता है
  • std::remove एलिमेंट को erase नहीं करता — remove-erase इडियम का उपयोग करें, या C++20 का std::erase_if
  • std::accumulate ( से) सामान्य fold/reduce ऑपरेशन है
  • हाथ से लिखे लूप की जगह एल्गोरिदम को प्राथमिकता दें — वे ज़्यादा स्पष्ट, ज़्यादा सुरक्षित और अक्सर ज़्यादा तेज़ होते हैं

क्विज़ — अपना ज्ञान परखें

(20 XP)

1. `std::remove` क्या लौटाता है?

2. किसी रेंज के सभी एलिमेंट के प्रेडिकेट को संतुष्ट करने की जाँच के लिए आप कौन-सा एल्गोरिदम इस्तेमाल करेंगे?

3. `std::nth_element` की टाइम कॉम्प्लेक्सिटी क्या है?