std :: next_permutation कार्यान्वयन स्पष्टीकरण


110

मैं उत्सुक था कि कैसे std:next_permutationलागू किया गया था इसलिए मैंने gnu libstdc++ 4.7संस्करण निकाला और पहचानकर्ताओं को संचित किया और निम्नलिखित डेमो का निर्माण करने के लिए प्रारूपण किया ...

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename It>
bool next_permutation(It begin, It end)
{
        if (begin == end)
                return false;

        It i = begin;
        ++i;
        if (i == end)
                return false;

        i = end;
        --i;

        while (true)
        {
                It j = i;
                --i;

                if (*i < *j)
                {
                        It k = end;

                        while (!(*i < *--k))
                                /* pass */;

                        iter_swap(i, k);
                        reverse(j, end);
                        return true;
                }

                if (i == begin)
                {
                        reverse(begin, end);
                        return false;
                }
        }
}

int main()
{
        vector<int> v = { 1, 2, 3, 4 };

        do
        {
                for (int i = 0; i < 4; i++)
                {
                        cout << v[i] << " ";
                }
                cout << endl;
        }
        while (::next_permutation(v.begin(), v.end()));
}

आउटपुट अपेक्षित है: http://ideone.com/4nZdx

मेरे सवाल हैं: यह कैसे काम करता है? का अर्थ क्या है i, jऔर k? निष्पादन के विभिन्न भागों में उनका क्या मूल्य है? इसकी शुद्धता के प्रमाण का एक रेखाचित्र क्या है?

मुख्य लूप में प्रवेश करने से पहले यह केवल तुच्छ 0 या 1 तत्व सूची मामलों की जाँच करता है। मुख्य लूप के प्रवेश पर मैं अंतिम तत्व (एक अंतिम छोर नहीं) की ओर इशारा कर रहा हूं और सूची कम से कम 2 तत्वों की लंबी है।

मुख्य लूप के शरीर में क्या चल रहा है?


अरे आपने कोड का वह टुकड़ा कैसे निकाला? जब मैंने #include <एल्गोरिथम> चेक किया, तो कोड पूरी तरह से अलग था जिसमें अधिक कार्य शामिल थे
मंजूनाथ

जवाबों:


172

आइए कुछ क्रमपरिवर्तन पर नजर डालते हैं:

1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
...

हम एक क्रमचय से दूसरे पर कैसे जाते हैं? सबसे पहले, आइए चीजों को थोड़ा अलग तरीके से देखें। हम तत्वों को अंकों के रूप में और क्रमपरिवर्तन के रूप में देख सकते हैं । इस तरह से समस्या को देखते हुए हम "आरोही" क्रम में क्रमपरिवर्तन / संख्याओं को क्रमबद्ध करना चाहते हैं

जब हम संख्याओं का आदेश देते हैं तो हम उन्हें "सबसे छोटी राशि से बढ़ाना" चाहते हैं। उदाहरण के लिए जब गिनती करते हैं तो हम 1, 2, 3, 10, नहीं गिनते हैं ... क्योंकि अभी भी 4, 5 हैं, ... बीच में और हालांकि 10 3 से बड़ा है, लापता संख्याएं हैं जिनके द्वारा प्राप्त किया जा सकता है एक छोटी राशि से 3 की वृद्धि। ऊपर दिए गए उदाहरण में हम देखते हैं कि 1लंबे समय तक पहली संख्या के रूप में रहता है क्योंकि पिछले 3 "अंकों" के कई पुनरावर्तन हैं जो एक छोटी राशि द्वारा क्रमचय को "बढ़ाते हैं"।

तो आखिर हम कब "उपयोग" करते हैं 1? जब अंतिम 3 अंकों के केवल अधिक क्रमपरिवर्तन नहीं होते हैं।
और पिछले 3 अंकों के अधिक क्रमपरिवर्तन कब नहीं हैं? जब अंतिम 3 अंक अवरोही क्रम में हों।

अहा! यह एल्गोरिथ्म को समझने की कुंजी है। हम केवल एक "अंक" की स्थिति को बदलते हैं जब सही करने के लिए सब कुछ अवरोही क्रम में होता है क्योंकि यदि यह अवरोही क्रम में नहीं है, तब भी जाने के लिए और अधिक क्रमपरिवर्तन हैं (यानी हम थोड़ी मात्रा में क्रमांकन "बढ़ा सकते हैं") ।

अब कोड पर वापस चलते हैं:

while (true)
{
    It j = i;
    --i;

    if (*i < *j)
    { // ...
    }

    if (i == begin)
    { // ...
    }
}

लूप में पहले 2 लाइनों से, jएक तत्व है और iइसके पहले का तत्व है।
फिर, यदि तत्व बढ़ते क्रम में हैं, ( if (*i < *j)) कुछ करते हैं।
अन्यथा, यदि पूरी चीज अवरोही क्रम में है, ( if (i == begin)) तो यह अंतिम क्रमपरिवर्तन है।
अन्यथा, हम जारी रखते हैं और हम देखते हैं कि जम्मू और मैं अनिवार्य रूप से अपघटित हैं।

अब हम if (i == begin)भाग को समझते हैं, इसलिए हमें समझने की आवश्यकता है कि यह if (*i < *j)हिस्सा है।

यह भी ध्यान दें: "फिर अगर तत्व बढ़ते क्रम में हैं ..." जो हमारे पिछले अवलोकन का समर्थन करता है कि हमें केवल एक अंक के लिए कुछ करने की आवश्यकता है "जब सब कुछ सही क्रम में होता है"। आरोही क्रम ifकथन अनिवार्य रूप से बाईं ओर का स्थान है जहाँ "दाईं ओर सब कुछ अवरोही क्रम में है"।

आइए फिर से कुछ उदाहरण देखें:

...
1 4 3 2
2 1 3 4
...
2 4 3 1
3 1 2 4
...

हम देखते हैं कि जब किसी अंक का अधिकार अवरोही क्रम में होता है, तो हम अगले सबसे बड़े अंक को खोजते हैं और उसे सामने रखते हैं और फिर शेष अंकों को आरोही क्रम में रखते हैं

आइए कोड को देखें:

It k = end;

while (!(*i < *--k))
    /* pass */;

iter_swap(i, k);
reverse(j, end);
return true;

ठीक है, चूंकि दाईं ओर की चीजें अवरोही क्रम में हैं, इसलिए "अगला सबसे बड़ा अंक" खोजने के लिए हमें बस अंत से चलना होगा, जिसे हम कोड की पहली 3 पंक्तियों में देखते हैं।

अगला, हम iter_swap()कथन के साथ सामने वाले को "अगला सबसे बड़ा अंक" स्वैप करते हैं और तब से हम जानते हैं कि अंक अगला सबसे बड़ा था, हम जानते हैं कि दाईं ओर के अंक अभी भी अवरोही क्रम में हैं, इसलिए इसे आरोही क्रम में रखना है, हमें बस यह करना reverse()है।


12
आश्चर्यजनक व्याख्या

2
समझाने के लिए धन्यवाद! इस एल्गोरिथम को लेक्सिकोग्राफिक क्रम में जनरेशन कहा जाता है । ऐसे एल्गोरिथ्म की संख्या में हैं Combinatorics, लेकिन यह सबसे शास्त्रीय एक है।
चेन आरओ

1
इस तरह के एल्गोरिथ्म की जटिलता क्या है?
user72708

leetcode की अच्छी व्याख्या है, leetcode.com/problems/next-permutation/solution
bicepjai

40

Gcc कार्यान्वयन लेक्सिकोग्राफ़िक क्रम में क्रमपरिवर्तन उत्पन्न करता है। विकिपीडिया इसे इस प्रकार बताता है:

निम्नलिखित एल्गोरिथ्म एक दिए गए क्रमपरिवर्तन के बाद अगला क्रमपरिवर्तन शाब्दिक रूप से उत्पन्न करता है। यह दिए गए क्रमचय को इन-प्लेस में बदलता है।

  1. सबसे बड़ा सूचकांक k का पता लगाएं, जैसे कि [k] <[k + 1]। यदि ऐसा कोई सूचकांक मौजूद नहीं है, तो क्रमपरिवर्तन अंतिम क्रमपरिवर्तन है।
  2. सबसे बड़ा इंडेक्स l का पता लगाएं, जैसे कि [k] <a [l]। चूँकि k + 1 ऐसा सूचकांक है, l अच्छी तरह से परिभाषित है और k <l को संतुष्ट करता है।
  3. एक [एल] एक [एल] के साथ स्वैप करें।
  4. अनुक्रम को एक [k + 1] से उल्टा और अंतिम तत्व सहित [n]।

AFAICT, सभी कार्यान्वयन समान आदेश उत्पन्न करते हैं।
MSalters

12

नुथ इस एल्गोरिथ्म और इसके सामान्यीकरण के बारे में गहराई में चला जाता है 7.2.1.2 और 7.2.1.3 आर्ट ऑफ़ कंप्यूटर प्रोग्रामिंग के अनुभागों में । वह इसे "एलगोरिदम एल" कहता है - जाहिरा तौर पर यह 13 वीं शताब्दी का है।


1
क्या आप पुस्तक के नाम का उल्लेख कर सकते हैं?
Grobber

3
TAOCP = कंप्यूटर प्रोग्रामिंग की कला

9

यहां अन्य मानक लाइब्रेरी एल्गोरिदम का उपयोग करते हुए एक पूर्ण कार्यान्वयन है:

template <typename I, typename C>
    // requires BidirectionalIterator<I> && Compare<C>
bool my_next_permutation(I begin, I end, C comp) {
    auto rbegin = std::make_reverse_iterator(end);
    auto rend = std::make_reverse_iterator(begin);
    auto rsorted_end = std::is_sorted_until(rbegin, rend, comp);
    bool has_more_permutations = rsorted_end != rend;
    if (has_more_permutations) {
        auto next_permutation_rend = std::upper_bound(
            rbegin, rsorted_end, *rsorted_end, comp);
        std::iter_swap(rsorted_end, next_permutation_rend);
    }
    std::reverse(rbegin, rsorted_end);
    return has_more_permutations;
}

डेमो


1
यह अच्छे चर नामों और चिंताओं को अलग करने के महत्व को रेखांकित करता है। is_final_permutationसे अधिक जानकारीपूर्ण है begin == end - 1। उन ऑपरेशनों से क्रमचय तर्क को कॉल करना is_sorted_until/ upper_boundअलग करना, और यह बहुत अधिक समझ में आता है। इसके अतिरिक्त अपर_बाउंड एक द्विआधारी खोज है, जबकि while (!(*i < *--k));रैखिक है, इसलिए यह अधिक प्रदर्शन है।
जोनाथन ग्व्रिच

1

वहाँ का उपयोग कर cppreference पर एक स्व व्याख्यात्मक संभव अनुकरण है<algorithm>

template <class Iterator>
bool next_permutation(Iterator first, Iterator last) {
    if (first == last) return false;
    Iterator i = last;
    if (first == --i) return false;
    while (1) {
        Iterator i1 = i, i2;
        if (*--i < *i1) {
            i2 = last;
            while (!(*i < *--i2));
            std::iter_swap(i, i2);
            std::reverse(i1, last);
            return true;
        }
        if (i == first) {
            std::reverse(first, last);
            return false;
        }
    }
}

सामग्री को लेक्सिकोग्राफ़िक रूप से अगले क्रमपरिवर्तन (इन-प्लेस) में बदलें और यदि मौजूद है तो वापस लौटें और यदि मौजूद नहीं है तो वापस लौटें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.