आधुनिक C ++ में क्लासिक सॉर्टिंग एल्गोरिदम को कैसे लागू किया जाए?


331

std::sortएल्गोरिथ्म (और उसके चचेरे भाई std::partial_sortऔर std::nth_elementसी ++ स्टैंडर्ड लाइब्रेरी से) अधिकांश प्रयोगों में है और अधिक प्राथमिक छँटाई एल्गोरिदम के एक जटिल और संकर समामेलन इस तरह के चयन प्रकार, प्रविष्टि प्रकार, त्वरित तरह, मर्ज क्रमबद्ध या ढेर प्रकार के रूप में,।

यहाँ और बहन साइटों पर कई सवाल हैं जैसे https://codereview.stackexchange.com/ बग्स, जटिलता और इन क्लासिक सॉर्टिंग एल्गोरिदम के कार्यान्वयन के अन्य पहलुओं से संबंधित हैं। अधिकांश प्रस्तावित कार्यान्वयन में कच्चे लूप शामिल हैं, सूचकांक हेरफेर और कंक्रीट प्रकार का उपयोग करते हैं, और शुद्धता और दक्षता के संदर्भ में विश्लेषण करने के लिए आमतौर पर गैर-तुच्छ होते हैं।

प्रश्न : आधुनिक C ++ का उपयोग करके उपरोक्त उल्लिखित क्लासिक सॉर्टिंग एल्गोरिदम को कैसे लागू किया जा सकता है?

  • कोई कच्चा लूप नहीं , लेकिन स्टैण्डर्ड लाइब्रेरी के एल्गोरिथम बिल्डिंग ब्लॉक्स से मिला कर<algorithm>
  • सूचकांक जोड़-तोड़ और ठोस प्रकारों के बजाय टेम्पलेट इंटरफ़ेस और टेम्पलेट्स का उपयोग
  • सी ++ 14 शैली , पूर्ण मानक पुस्तकालय, साथ ही साथ सिंटैक्टिक शोर रिड्यूसर जैसे कि auto, टेम्पलेट एलियास, पारदर्शी तुलनित्र और बहुरूपी लैम्ब्डा।

नोट :

  • छँटाई एल्गोरिदम के कार्यान्वयन पर आगे के संदर्भ के लिए विकिपीडिया , रोसेटा कोड या http://www.sorting-algorithms.com/ देखें
  • के अनुसार शॉन अभिभावक की परंपराओं (स्लाइड 39), एक कच्चे पाश एक है for-loop एक ऑपरेटर के साथ दो कार्यों की रचना से अधिक समय। तो f(g(x));या f(x); g(x);या f(x) + g(x);कच्चे छोरों नहीं हैं, और न में लूप बने हैं selection_sortऔर insertion_sortनीचे।
  • मैं वर्तमान सी ++ 1y को C ++ 14 के रूप में दर्शाने के लिए स्कॉट मेयर्स की शब्दावली का पालन करता हूं, और C ++ 98 के रूप में C ++ 98 और C ++ 03 दोनों को निरूपित करता हूं, इसलिए मुझे उसके लिए लौ न दें।
  • जैसा कि @ मेहरदाद द्वारा टिप्पणियों में सुझाव दिया गया है, मैं उत्तर के अंत में लाइव उदाहरण के रूप में चार कार्यान्वयन प्रदान करता हूं: सी ++ 14, सी ++ 11, सी ++ 98 और बूस्ट और सी ++ 98।
  • उत्तर केवल C ++ 14 के संदर्भ में प्रस्तुत किया गया है। जहाँ प्रासंगिक है, मैं वाक्यविन्यास और पुस्तकालय के अंतर को निरूपित करता हूँ जहाँ विभिन्न भाषा संस्करण भिन्न होते हैं।

8
प्रश्न में C ++ फ़ेक टैग को जोड़ना बहुत अच्छा होगा, हालांकि इसे दूसरों में से कम से कम खोना होगा। मैं संस्करणों को हटाने का सुझाव दूंगा (क्योंकि यह एक सामान्य सी ++ प्रश्न है, अधिकांश संस्करणों में कुछ अनुकूलन के साथ कार्यान्वयन उपलब्ध है)।
मैथ्यू एम।

@TemplateRex ठीक है, तकनीकी रूप से, अगर यह सामान्य प्रश्न नहीं है, तो यह प्रश्न बहुत व्यापक है (अनुमान लगाया जा रहा है - मैंने नीचे नहीं देखा)। Btw। अच्छी नौकरी, बहुत सारी उपयोगी जानकारी, धन्यवाद :)
बार्टोज़केपी

जवाबों:


388

एल्गोरिदमिक बिल्डिंग ब्लॉक

हम मानक लाइब्रेरी से एल्गोरिथम बिल्डिंग ब्लॉक्स को असेंबल करके शुरू करते हैं:

#include <algorithm>    // min_element, iter_swap, 
                        // upper_bound, rotate, 
                        // partition, 
                        // inplace_merge,
                        // make_heap, sort_heap, push_heap, pop_heap,
                        // is_heap, is_sorted
#include <cassert>      // assert 
#include <functional>   // less
#include <iterator>     // distance, begin, end, next
  • नॉन-मेंबर std::begin()/ std::end()साथ ही साथ इट्रेटर टूल std::next()केवल C ++ 11 और उसके बाद के संस्करण के रूप में उपलब्ध हैं। C ++ 98 के लिए, इन्हें स्वयं लिखने की आवश्यकता है। Boost.Range से boost::begin()/ में boost::end(), और Boost.Utility में से विकल्प हैं boost::next()
  • std::is_sortedएल्गोरिथ्म सी ++ के लिए 11 और उससे आगे ही उपलब्ध है। C ++ 98 के लिए, इसे std::adjacent_findऔर हाथ से लिखी गई फ़ंक्शन ऑब्जेक्ट के संदर्भ में लागू किया जा सकता है । Boost.Algorithm boost::algorithm::is_sortedएक विकल्प के रूप में भी प्रदान करता है ।
  • std::is_heapएल्गोरिथ्म सी ++ के लिए 11 और उससे आगे ही उपलब्ध है।

सिंथेटिक नेकियां

सी ++ 14 फॉर्म के पारदर्शी तुलनात्मक रूप प्रदान करता है std::less<>जो उनके तर्कों पर बहुरूपिए रूप से कार्य करते हैं। यह इट्रेटर के प्रकार प्रदान करने से बचा जाता है। इसका उपयोग C ++ 11 के डिफ़ॉल्ट फ़ंक्शन टेम्प्लेट तर्कों के साथ संयोजन के लिए एक अधिभार बनाने के लिए किया जा सकता है जो <तुलनात्मक रूप से लेने वाले एल्गोरिदम के लिए है और जिनके पास उपयोगकर्ता-परिभाषित तुलना फ़ंक्शन फ़ंक्शन है।

template<class It, class Compare = std::less<>>
void xxx_sort(It first, It last, Compare cmp = Compare{});

C ++ 11 में, कोई पुन: प्रयोज्य टेम्प्लेट उर्फ को इट्रेटर के मान प्रकार को परिभाषित करने के लिए परिभाषित कर सकता है जो सॉर्ट एल्गोरिदम के हस्ताक्षर में मामूली अव्यवस्था जोड़ता है:

template<class It>
using value_type_t = typename std::iterator_traits<It>::value_type;

template<class It, class Compare = std::less<value_type_t<It>>>
void xxx_sort(It first, It last, Compare cmp = Compare{});

C ++ 98 में, एक को दो अधिभार लिखने और क्रिया typename xxx<yyy>::typeसिंटैक्स का उपयोग करने की आवश्यकता है

template<class It, class Compare>
void xxx_sort(It first, It last, Compare cmp); // general implementation

template<class It>
void xxx_sort(It first, It last)
{
    xxx_sort(first, last, std::less<typename std::iterator_traits<It>::value_type>());
}
  • एक और वाक्यविन्यास अच्छाई यह है कि C ++ 14 पॉलीमॉर्फिक लैम्ब्डा के माध्यम से उपयोगकर्ता-परिभाषित तुलनित्रों को लपेटने की सुविधा देता है ( autoफ़ंक्शन टेम्पलेट तर्कों जैसे मापदंडों के साथ कटौती की जाती है)।
  • C ++ 11 में केवल मोनोमोर्फिक लैम्ब्डा है, जिसे उपरोक्त टेम्पलेट उर्फ ​​के उपयोग की आवश्यकता है value_type_t
  • C ++ 98 में, किसी को या तो स्टैंडअलोन फ़ंक्शन ऑब्जेक्ट लिखने या वर्बोस std::bind1st/ std::bind2nd/ std::not1प्रकार के सिंटैक्स का सहारा लेने की आवश्यकता होती है।
  • Boost.Bind इसे boost::bindऔर _1/ _2प्लेसहोल्डर सिंटैक्स के साथ सुधारता है ।
  • सी ++ 11 और उससे आगे भी है std::find_if_not, जबकि सी ++ 98 जरूरत है, std::find_ifएक साथ std::not1एक समारोह वस्तु के आसपास।

सी ++ शैली

आम तौर पर स्वीकार्य C ++ 14 शैली अभी तक नहीं है। बेहतर या बदतर के लिए, मैं स्कॉट मेयर्स के प्रभावी मॉडर्न सी ++ और हर्ब सटर के संशोधित गॉटडब्ल्यू का बारीकी से पालन करता हूं । मैं निम्नलिखित शैली सिफारिशों का उपयोग करता हूं:

चयन छांटना

चयन प्रकार किसी भी तरह से डेटा के अनुकूल नहीं होता है, इसलिए इसका रनटाइम हमेशा होता हैO(N²)। हालांकि, चयन प्रकार में स्वैप की संख्या को कम करने की संपत्ति है। उन अनुप्रयोगों में जहां स्वैपिंग आइटम की लागत अधिक है, चयन प्रकार बहुत अच्छी तरह से पसंद का एल्गोरिदम हो सकता है।

मानक लाइब्रेरी का उपयोग करके इसे लागू std::min_elementकरने के लिए, शेष न्यूनतम तत्व को खोजने के लिए बार-बार उपयोग करें, और iter_swapइसे जगह में स्वैप करें:

template<class FwdIt, class Compare = std::less<>>
void selection_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last; ++it) {
        auto const selection = std::min_element(it, last, cmp);
        std::iter_swap(selection, it); 
        assert(std::is_sorted(first, std::next(it), cmp));
    }
}

ध्यान दें कि selection_sortपहले से ही संसाधित सीमा [first, it)इसकी लूप इनवेरिएंट के रूप में क्रमबद्ध है। न्यूनतम आवश्यकताएं पुनरावृत्तियों के लिए हैं , std::sortयादृच्छिक अभिगमकर्ताओं की तुलना में ।

विवरण छोड़ा गया :

  • चयन प्रकार को प्रारंभिक परीक्षा if (std::distance(first, last) <= 1) return;(या आगे / द्विदिश पुनरावृत्तियों के लिए if (first == last || std::next(first) == last) return;) के साथ अनुकूलित किया जा सकता है ।
  • के लिए द्विदिश iterators , ऊपर परीक्षण अंतराल पर एक पाश के साथ जोड़ा जा सकता है [first, std::prev(last))क्योंकि पिछले तत्व न्यूनतम शेष तत्व होने की गारंटी है और एक स्वैप की आवश्यकता नहीं है,।

सम्मिलन सॉर्ट

यद्यपि यह O(N²)सबसे खराब समय के साथ प्राथमिक सॉर्टिंग एल्गोरिदम में से एक है , सम्मिलन सॉर्ट पसंद का एल्गोरिथ्म है जब डेटा लगभग सॉर्ट किया जाता है (क्योंकि यह अनुकूली है ) या जब समस्या का आकार छोटा होता है (क्योंकि इसमें कम ओवरहेड होता है)। इन कारणों के लिए, और क्योंकि यह भी स्थिर है , सम्मिलन प्रकार का उपयोग अक्सर उच्च ओवरहेड डिवाइड और-विजयी सॉर्टिंग एल्गोरिदम जैसे मर्ज सॉर्ट या त्वरित सॉर्ट के लिए पुनरावर्ती बेस केस (जब समस्या का आकार छोटा होता है) के रूप में किया जाता है।

insertion_sortमानक लाइब्रेरी के साथ कार्यान्वित करने के लिए, बार-बार std::upper_boundउस स्थान को खोजने के लिए उपयोग करें जहां वर्तमान तत्व को जाने की आवश्यकता है, और std::rotateशेष तत्वों को इनपुट रेंज में ऊपर की ओर स्थानांतरित करने के लिए उपयोग करें:

template<class FwdIt, class Compare = std::less<>>
void insertion_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last; ++it) {
        auto const insertion = std::upper_bound(first, it, *it, cmp);
        std::rotate(insertion, it, std::next(it)); 
        assert(std::is_sorted(first, std::next(it), cmp));
    }
}

ध्यान दें कि insertion_sortपहले से ही संसाधित सीमा [first, it)इसकी लूप इनवेरिएंट के रूप में क्रमबद्ध है। सम्मिलन क्रम आगे चलने वाले के साथ भी काम करता है।

विवरण छोड़ा गया :

  • सम्मिलन सॉर्ट को प्रारंभिक परीक्षण if (std::distance(first, last) <= 1) return;(या फॉरवर्ड / बिडायरेक्शनल पुनरावृत्तियों:) if (first == last || std::next(first) == last) return;और अंतराल पर एक लूप के साथ अनुकूलित किया जा सकता है [std::next(first), last), क्योंकि पहला तत्व जगह में रहने की गारंटी देता है और इसे घुमाने की आवश्यकता नहीं होती है।
  • के लिए द्विदिश iterators , द्विआधारी खोज सम्मिलन बिंदु को खोजने के लिए एक साथ बदला जा सकता रिवर्स रैखिक खोज स्टैंडर्ड लाइब्रेरी का उपयोग कर std::find_if_notएल्गोरिथ्म।

नीचे दिए गए टुकड़े के लिए चार लाइव उदाहरण ( C ++ 14 , C ++ 11 , C ++ 98 और Boost , C ++ 98 ):

using RevIt = std::reverse_iterator<BiDirIt>;
auto const insertion = std::find_if_not(RevIt(it), RevIt(first), 
    [=](auto const& elem){ return cmp(*it, elem); }
).base();
  • यादृच्छिक आदानों के लिए यह O(N²)तुलना देता है , लेकिन यह O(N)लगभग छांटे गए इनपुट के लिए तुलना में सुधार करता है । बाइनरी सर्च हमेशा O(N log N)तुलना का उपयोग करता है ।
  • छोटी इनपुट श्रेणियों के लिए, रैखिक खोज की बेहतर मेमोरी लोकलिटी (कैश, प्रीफेटिंग) एक द्विआधारी खोज पर भी हावी हो सकती है (किसी को भी इसका परीक्षण करना चाहिए)।

जल्दी से सुलझाएं

जब ध्यान से लागू किया जाता है, तो त्वरित सॉर्ट मजबूत होता है और इससे O(N log N)जटिलता की उम्मीद होती है, लेकिन O(N²)सबसे खराब स्थिति जटिलता के साथ प्रतिकूल रूप से चुने गए इनपुट डेटा के साथ शुरू हो सकती है। जब एक स्थिर प्रकार की आवश्यकता नहीं होती है, तो त्वरित सॉर्ट एक उत्कृष्ट सामान्य-उद्देश्य सॉर्ट है।

यहां तक ​​कि सबसे सरल संस्करणों के लिए, अन्य क्लासिक सॉर्टिंग एल्गोरिदम की तुलना में स्टैंडर्ड लाइब्रेरी का उपयोग करने के लिए त्वरित सॉर्ट काफी जटिल है। का उपयोग करता है कुछ इटरेटर उपयोगिताओं नीचे दृष्टिकोण का पता लगाने का मध्य तत्व इनपुट रेंज के [first, last)धुरी के रूप में, तो दो के लिए कॉल का उपयोग std::partition(जो कर रहे हैं O(N)तत्वों है कि तुलना में छोटे होते के क्षेत्रों में इनपुट रेंज तीन तरह विभाजन के लिए), के बराबर, और क्रमशः चयनित धुरी से बड़ा है। अंत में धुरी से छोटे और बड़े तत्वों वाले दो बाहरी खंडों को पुन: क्रमबद्ध किया जाता है:

template<class FwdIt, class Compare = std::less<>>
void quick_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    auto const N = std::distance(first, last);
    if (N <= 1) return;
    auto const pivot = *std::next(first, N / 2);
    auto const middle1 = std::partition(first, last, [=](auto const& elem){ 
        return cmp(elem, pivot); 
    });
    auto const middle2 = std::partition(middle1, last, [=](auto const& elem){ 
        return !cmp(pivot, elem);
    });
    quick_sort(first, middle1, cmp); // assert(std::is_sorted(first, middle1, cmp));
    quick_sort(middle2, last, cmp);  // assert(std::is_sorted(middle2, last, cmp));
}

हालांकि, त्वरित सॉर्ट सही और कुशल पाने के लिए मुश्किल है, क्योंकि उपरोक्त प्रत्येक चरण को सावधानीपूर्वक जांचना और उत्पादन स्तर कोड के लिए अनुकूलित करना है। विशेष रूप से, O(N log N)जटिलता के लिए, धुरी को इनपुट डेटा के संतुलित विभाजन में परिणाम करना पड़ता है, जिसे सामान्य रूप से O(1)धुरी के लिए गारंटी नहीं दी जा सकती है , लेकिन जिसे गारंटी दी जा सकती है यदि कोई धुरी O(N)इनपुट रेंज के मध्य के रूप में सेट करता है ।

विवरण छोड़ा गया :

  • उपरोक्त कार्यान्वयन विशेष रूप से विशेष इनपुट के लिए कमजोर है, उदाहरण O(N^2)के लिए " ऑर्गन पाइप " इनपुट के लिए इसकी जटिलता है 1, 2, 3, ..., N/2, ... 3, 2, 1(क्योंकि मध्य हमेशा अन्य सभी तत्वों से बड़ा होता है)।
  • लगभग 3 तरह के इनपुट के खिलाफ इनपुट रेंज गार्डसे बेतरतीब ढंग से चुने गए तत्वों से माध्यिका -3 की धुरी चयनजिसके लिए जटिलता अन्यथा बिगड़ जाएगीO(N^2)
  • 3- कॉल पार्टीटिंग (तत्वों को छोटे से अलग, धुरी के बराबर और बड़े से अलग) जैसा कि दो कॉल द्वारा दिखाया गया है,इस परिणाम को प्राप्त करने के लिएstd::partitionसबसे कुशलO(N)एल्गोरिथ्मनहीं है।
  • के लिए रैंडम एक्सेस iterators , एक गारंटीकृत O(N log N)जटिलता के माध्यम से प्राप्त किया जा सकता मंझला धुरी चयन का उपयोग कर std::nth_element(first, middle, last), करने के लिए पुनरावर्ती कॉल के बाद quick_sort(first, middle, cmp)और quick_sort(middle, last, cmp)
  • यह गारंटी, हालांकि, लागत पर आती है, क्योंकि O(N)कॉम्प्लेक्स की निरंतरता का कारक एक मंझला -३ पिवट std::nth_elementकी O(1)जटिलता की तुलना में अधिक महंगा हो सकता है, जिसके बाद O(N)कॉल के लिए कॉल किया जा सकता है std::partition(जो कि कैश-फ्रेंडली सिंगल फॉरवर्ड पास है आँकड़े)।

मर्ज़ सॉर्ट

यदि O(N)अतिरिक्त स्थान का उपयोग करना कोई चिंता का विषय नहीं है, तो मर्ज सॉर्ट एक उत्कृष्ट विकल्प है: यह एकमात्र स्थिर O(N log N) सॉर्टिंग एल्गोरिदम है।

मानक एल्गोरिदम का उपयोग करके इसे लागू करना सरल है: इनपुट रेंज के मध्य का पता लगाने [first, last)और दो पुनरावर्ती सॉर्ट किए गए सेगमेंट को एक के साथ संयोजित करने के लिए कुछ इटरेटर उपयोगिताओं का उपयोग करें std::inplace_merge:

template<class BiDirIt, class Compare = std::less<>>
void merge_sort(BiDirIt first, BiDirIt last, Compare cmp = Compare{})
{
    auto const N = std::distance(first, last);
    if (N <= 1) return;                   
    auto const middle = std::next(first, N / 2);
    merge_sort(first, middle, cmp); // assert(std::is_sorted(first, middle, cmp));
    merge_sort(middle, last, cmp);  // assert(std::is_sorted(middle, last, cmp));
    std::inplace_merge(first, middle, last, cmp); // assert(std::is_sorted(first, last, cmp));
}

मर्ज सॉर्ट के लिए द्विदिश चलने वाले, टोंटी होने की आवश्यकता होती है std::inplace_merge। ध्यान दें कि लिंक की गई सूचियों को सॉर्ट करते समय, मर्ज सॉर्ट के लिए केवल O(log N)अतिरिक्त स्थान (पुनरावर्तन के लिए) की आवश्यकता होती है । बाद के एल्गोरिथ्म को std::list<T>::sortमानक पुस्तकालय में लागू किया गया है।

ढेर बनाएं और छांटें

हीप सॉर्ट लागू करने के लिए सरल है,O(N log N)इन-प्लेस सॉर्ट करता है, लेकिन स्थिर नहीं है।

पहला लूप, O(N)"हीपाइफाई" चरण, सरणी को ढेर क्रम में रखता है। दूसरा लूप, O(N log N) "सॉर्टडाउन" चरण, बार-बार अधिकतम और पुनर्स्थापना ढेर आदेश निकालता है। स्टैंडर्ड लाइब्रेरी इसे बेहद सरल बनाता है:

template<class RandomIt, class Compare = std::less<>>
void heap_sort(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    lib::make_heap(first, last, cmp); // assert(std::is_heap(first, last, cmp));
    lib::sort_heap(first, last, cmp); // assert(std::is_sorted(first, last, cmp));
}

यदि आप उपयोग करने के लिए इसे "धोखा" मानते हैं std::make_heapऔर std::sort_heap, आप क्रमशः एक स्तर पर जा सकते हैं और उन कार्यों को क्रमशः std::push_heapऔर के संदर्भ में लिख सकते हैं std::pop_heap:

namespace lib {

// NOTE: is O(N log N), not O(N) as std::make_heap
template<class RandomIt, class Compare = std::less<>>
void make_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last;) {
        std::push_heap(first, ++it, cmp); 
        assert(std::is_heap(first, it, cmp));           
    }
}

template<class RandomIt, class Compare = std::less<>>
void sort_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    for (auto it = last; it != first;) {
        std::pop_heap(first, it--, cmp);
        assert(std::is_heap(first, it, cmp));           
    } 
}

}   // namespace lib

मानक पुस्तकालय दोनों push_heapऔर pop_heapजटिलता के रूप में निर्दिष्ट करता है O(log N)। नोट हालांकि सीमा पर बाहरी पाश है कि [first, last)में परिणाम O(N log N)के लिए जटिलता make_heapहै, जबकि std::make_heapकेवल O(N)जटिलता। समग्र O(N log N)जटिलता के लिए heap_sortयह कोई फर्क नहीं पड़ता।

विवरण छोड़ा गया : का O(N)कार्यान्वयनmake_heap

परिक्षण

यहां चार लाइव उदाहरण ( C ++ 14 , C ++ 11 , C ++ 98 और Boost , C ++ 98 ) सभी पांच एल्गोरिदम का परीक्षण विभिन्न प्रकार के इनपुटों पर किया गया है (जिसका अर्थ संपूर्ण या कठोर नहीं है)। LOC में भारी अंतर पर ध्यान दें: C ++ 11 / C ++ 14 को लगभग 130 LOC, C ++ 98 और Boost 190 (+ 50%) और C ++ 98 270 (+ 100%) से अधिक की आवश्यकता है।


13
जबकि मैं आपकेauto (और कई लोग मुझसे असहमत हैं) आपके उपयोग से असहमत हैं, मुझे मानक लाइब्रेरी एल्गोरिदम को अच्छी तरह से इस्तेमाल करते हुए देखकर बहुत अच्छा लगा। मैं सीन पेरेंट की बात को देखने के बाद इस तरह के कोड के कुछ उदाहरण देखना चाहता हूं। इसके अलावा, मेरे पास कोई विचार नहीं std::iter_swapथा, हालांकि यह मुझे अजीब लगता है कि यह अंदर है <algorithm>
जोसेफ मैन्सफील्ड

32
@sbabbi संपूर्ण मानक पुस्तकालय इस सिद्धांत पर आधारित है कि पुनरावृत्तियाँ कॉपी करने के लिए सस्ते हैं; उदाहरण के लिए, यह उन्हें मूल्य से गुजरता है। यदि एक पुनरावृत्ति की नकल करना सस्ता नहीं है, तो आप हर जगह प्रदर्शन समस्याओं का सामना करने जा रहे हैं।
जेम्स कंज

2
महान पद। [Std ::] make_heap के धोखाधड़ी वाले हिस्से के बारे में। अगर std :: make_heap को धोखा माना जाता है, तो std :: push_heap। यानी धोखा देना = ढेर संरचना के लिए परिभाषित वास्तविक व्यवहार को लागू नहीं करना। मुझे लगता है कि यह शिक्षाप्रद है push_heap के रूप में अच्छी तरह से शामिल होंगे।
कप्तान जिराफ

3
@gnzlbg आप निश्चित रूप से टिप्पणी कर सकते हैं। प्रारंभिक परीक्षा को रैंडम एक्सेस के लिए वर्तमान संस्करण के साथ, प्रति इटैलर श्रेणी में टैग-प्रेषण किया जा सकता है, और if (first == last || std::next(first) == last)। मैं बाद में इसे अपडेट कर सकता हूं। "लोप किए गए विवरण" अनुभागों में सामान को लागू करना सवाल के दायरे से बाहर है, आईएमओ, क्योंकि वे पूरे क्यू एंड खुद के लिए लिंक रखते हैं। वास्तविक-शब्द सॉर्टिंग दिनचर्या को लागू करना कठिन है!
टेम्प्लेक्स

3
महान पद। हालाँकि, आपने nth_elementमेरी राय का उपयोग करके अपने क्विकॉर्ट के साथ धोखा किया है । nth_elementपहले से ही आधा एस्कॉर्ट करता है (विभाजन के चरण और आधे पर एक पुनरावृत्ति जिसमें एन-वें तत्व शामिल हैं जिसमें आप रुचि रखते हैं)।
बिक्री

14

एक और छोटा और बल्कि सुरुचिपूर्ण एक मूल रूप से कोड की समीक्षा पर पाया गया । मुझे लगा कि यह साझा करने लायक है।

गिनती की तरह

हालांकि यह विशेष रूप से विशिष्ट है, गिनती सॉर्टिंग एक सरल पूर्णांक छँटाई एल्गोरिथ्म है और अक्सर वास्तव में तेजी से हो सकता है बशर्ते कि पूर्णांक के मानों को अलग करने के लिए बहुत दूर नहीं है। यह शायद आदर्श है यदि किसी को उदाहरण के लिए 0 और 100 के बीच ज्ञात एक मिलियन पूर्णांकों के संग्रह को क्रमबद्ध करने की आवश्यकता है।

एक बहुत ही सरल गणना प्रकार को लागू करने के लिए जो हस्ताक्षरित और अहस्ताक्षरित पूर्णांक दोनों के साथ काम करता है, किसी को क्रमबद्ध करने के लिए सबसे छोटे और महानतम तत्वों को खोजने की आवश्यकता होती है; उनके अंतर को आवंटित करने के लिए मायने रखने वाले सरणी का आकार बताएगा। फिर, संग्रह के माध्यम से एक दूसरा पास हर तत्व की घटनाओं की संख्या की गणना करने के लिए किया जाता है। अंत में, हम हर पूर्णांक की आवश्यक संख्या को वापस मूल संग्रह में लिखते हैं।

template<typename ForwardIterator>
void counting_sort(ForwardIterator first, ForwardIterator last)
{
    if (first == last || std::next(first) == last) return;

    auto minmax = std::minmax_element(first, last);  // avoid if possible.
    auto min = *minmax.first;
    auto max = *minmax.second;
    if (min == max) return;

    using difference_type = typename std::iterator_traits<ForwardIterator>::difference_type;
    std::vector<difference_type> counts(max - min + 1, 0);

    for (auto it = first ; it != last ; ++it) {
        ++counts[*it - min];
    }

    for (auto count: counts) {
        first = std::fill_n(first, count, min++);
    }
}

जबकि यह केवल तभी उपयोगी होता है जब पूर्णांकों की श्रेणी को छोटा करने के लिए जाना जाता है (आमतौर पर सॉर्ट करने के लिए संग्रह के आकार से बड़ा नहीं होता है), गिनती को अधिक सामान्य बनाने से यह अपने सर्वोत्तम मामलों के लिए धीमी हो जाएगी। यदि सीमा छोटा होना ज्ञात नहीं है, तो एक अन्य एल्गोरिदम जैसे मूलांक , स्कैससोर्ट या स्प्रेडोर्ट का उपयोग किया जा सकता है।

विवरण छोड़ा गया :

  • हम एल्गोरिथ्म द्वारा स्वीकृत मूल्यों की सीमा की सीमा को पूरी तरह std::minmax_elementसे संग्रह के माध्यम से पहले पास से छुटकारा पाने के लिए पारित कर सकते थे। यह एल्गोरिथ्म को तब और तेज कर देगा जब एक उपयोगी-छोटी श्रेणी की सीमा को अन्य साधनों से जाना जाता है। (यह सटीक होना करने के लिए नहीं है, 0 से 100 एक निरंतर गुजर अब भी है बहुत एक लाख से अधिक तत्वों एक अतिरिक्त पास की तुलना में बेहतर पता लगाने के लिए यह सच सीमा 1 95 करने के लिए यहां तक कि 0 से 1000 कर रहे हैं लायक हो सकता है; अतिरिक्त तत्वों को एक बार शून्य के साथ लिखा जाता है और एक बार पढ़ा जाता है)।

  • countsमक्खी पर बढ़ते एक अलग पहली पास से बचने का एक और तरीका है। countsहर बार इसे उगाने के लिए आकार को दोगुना करने से प्रति सॉर्ट किए गए तत्व के लिए परिशोधित ओ (1) समय मिलता है (सबूत के लिए हैश टेबल सम्मिलन लागत विश्लेषण देखें कि घातीय वृद्धि हुई है)। एक नए के लिए अंत में बढ़ते हुए नए शून्य तत्वों को जोड़ना maxआसान है std::vector::resize। सदिश बढ़ने के बाद minमक्खी पर परिवर्तन और मोर्चे पर नए शून्य तत्वों को सम्मिलित किया जा सकता है std::copy_backward। फिर std::fillनए तत्वों को शून्य करने के लिए।

  • countsवेतन वृद्धि पाश एक हिस्टोग्राम है। यदि डेटा अत्यधिक दोहरावदार होने की संभावना है, और डिब्बे की संख्या छोटी है, तो एक ही बिन में स्टोर / रीलोड के क्रमिक डेटा निर्भरता की अड़चन को कम करने के लिए कई सरणियों पर अनियंत्रित होने के लायक हो सकता है । इसका मतलब है कि शुरुआत में शून्य की संख्या अधिक है, और अंत में अधिक लूप करने के लिए, लेकिन हमारे 0 से 100 नंबर के लाखों उदाहरणों के लिए अधिकांश सीपीयू पर इसका मूल्य होना चाहिए, खासकर अगर इनपुट पहले से ही (आंशिक रूप से) सॉर्ट किया जा सकता है और एक ही नंबर के लंबे रन।

  • ऊपर दिए गए एल्गोरिथ्म में, हम एक min == maxचेक का उपयोग तब करते हैं जब प्रत्येक तत्व का समान मान हो (जिस स्थिति में संग्रह क्रमबद्ध हो)। यह पूरी तरह से जांचने के बजाय वास्तव में संभव है कि क्या संग्रह के पहले से ही सॉर्ट किए गए हैं, जबकि बिना अतिरिक्त समय बर्बाद किए संग्रह के चरम मूल्यों को ढूंढना है (यदि पहले पास अभी भी मेमोरी टोंटी है, तो मिनट और अधिकतम अपडेट करने के अतिरिक्त काम के साथ)। हालांकि इस तरह के एक एल्गोरिथ्म मानक पुस्तकालय में मौजूद नहीं है और एक को लिखना बाकी गिनती प्रकार लिखने से अधिक थकाऊ होगा। इसे पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है।

  • चूंकि एल्गोरिथ्म केवल पूर्णांक मानों के साथ काम करता है, इसलिए उपयोगकर्ताओं को स्पष्ट प्रकार की गलतियाँ करने से रोकने के लिए स्थैतिक दावे का उपयोग किया जा सकता है। कुछ संदर्भों में, प्रतिस्थापन प्रतिस्थापन std::enable_if_tको प्राथमिकता दी जा सकती है।

  • हालांकि आधुनिक सी ++ शांत है, भविष्य में सी ++ और भी ठंडा हो सकता है: संरचित बाइंडिंग और रेंज टीएस के कुछ हिस्से एल्गोरिथ्म को भी साफ करेंगे।


@TemplateRex यदि यह एक मनमानी तुलना वस्तु लेने में सक्षम था, तो यह गिनती को एक तुलनात्मक प्रकार बना देगा, और तुलना प्रकार O (n log n) की तुलना में बेहतर सबसे खराब स्थिति नहीं हो सकती है। काउंटिंग सॉर्ट में ओ (एन + आर) का सबसे खराब मामला है, जिसका अर्थ है कि यह वैसे भी एक तुलना प्रकार नहीं हो सकता है। Integers की तुलना की जा सकती है लेकिन इस संपत्ति का उपयोग उस प्रकार को करने के लिए नहीं किया जाता है (इसका उपयोग केवल उसी में किया जाता है std::minmax_elementजिसमें केवल जानकारी एकत्र की जाती है)। उपयोग की जाने वाली संपत्ति तथ्य यह है कि पूर्णांक का उपयोग सूचकांकों या ऑफसेट के रूप में किया जा सकता है, और यह कि वे बाद की संपत्ति को संरक्षित करते हुए वृद्धिशील हैं।
मोरवीन

रेंजेस टीएस वास्तव में बहुत अच्छा है, उदाहरण के लिए अंतिम लूप खत्म हो सकता है counts | ranges::view::filter([](auto c) { return c != 0; })ताकि आपको बार-बार नॉनजेरो काउंट के लिए परीक्षण न करना पड़े fill_n
टेम्प्लेट

(मैं में लेखन पाया small एक rather और appart- मैं उन्हें तिल संपादित विषय में reggae_sort रख सकते हैं?)
बूढ़ा

@greybeard आप जो कुछ भी करना चाहते हैं, वह कर सकते हैं: p
Morwenn

मुझे संदेह है कि counts[]मक्खी पर बढ़ने से पहले एक जीत बनाम इनपुट के साथ इनपुट minmax_elementको ट्रेस करना होगा। विशेष रूप से उपयोग-मामले के लिए जहां यह आदर्श है, एक छोटी सी सीमा में कई दोहरावों के साथ बहुत बड़े इनपुट, क्योंकि आप जल्दी countsसे अपने पूर्ण आकार तक बढ़ेंगे, कुछ शाखा गलतफहमी या आकार-दोहराव के साथ। (बेशक, सीमा पर एक छोटी-सी सीमा को जानना आपको एक minmax_elementस्कैन से बचने और हिस्टोग्राम लूप के अंदर सीमा-जाँच से बचने देगा।)
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.