परवान चढ़ना आलसी होना


87

मैं एक एल्गोरिथ्म की तलाश में एक सेट के क्रमपरिवर्तन को इस तरह से उत्पन्न कर रहा हूं कि मैं क्लीजुरे में उनकी एक आलसी सूची बना सकूं। यानी मैं क्रमपरिवर्तन की एक सूची पर पुनरावृत्ति करना चाहूंगा, जहां प्रत्येक अनुरोध की गणना तब तक नहीं की जाती है जब तक कि मैं इसे अनुरोध नहीं करता हूं, और सभी क्रमपरिवर्तन को एक बार में मेमोरी में संग्रहीत नहीं करना पड़ता है।

वैकल्पिक रूप से मैं एक एल्गोरिथ्म की तलाश कर रहा हूं, जहां एक निश्चित सेट दिया जाता है, यह उस सेट के "अगले" क्रमचय को लौटा देगा, इस तरह से कि बार-बार अपने स्वयं के आउटपुट पर फ़ंक्शन को कॉल करने से मूल सेट के सभी क्रमपरिवर्तन के माध्यम से चक्र होगा, में कुछ आदेश (क्या आदेश मायने नहीं रखता है)।

क्या ऐसा कोई एल्गोरिदम है? मैंने जिन परमीशन-जनरेटिंग एल्गोरिदम को देखा है, उनमें से अधिकांश को एक साथ (आमतौर पर रिकर्सिवली) जेनरेट करने की प्रवृत्ति है, जो बहुत बड़े सेटों को मापता नहीं है। क्लोजर (या एक अन्य कार्यात्मक भाषा) में एक कार्यान्वयन उपयोगी होगा लेकिन मैं इसे स्यूडोकोड से समझ सकता हूं।

जवाबों:


139

हाँ, वहाँ है एक "अगले क्रमपरिवर्तन" एल्गोरिथ्म, और यह भी काफी आसान है। C ++ मानक टेम्प्लेट लाइब्रेरी (STL) में एक फ़ंक्शन भी है next_permutation

एल्गोरिथ्म वास्तव में अगला क्रमचय पाता है - लेक्सोग्राफिक रूप से अगला। विचार यह है: मान लीजिए कि आपको एक अनुक्रम दिया गया है, "32541" कहें। अगले क्रमपरिवर्तन क्या है?

यदि आप इसके बारे में सोचते हैं, तो आप देखेंगे कि यह "34125" है। और आपके विचार शायद कुछ इस प्रकार थे: "32541" में,

  • "32" को नियत रखने और "541" भाग में बाद के क्रमांकन को खोजने का कोई तरीका नहीं है, क्योंकि यह क्रमचय पहले से ही 5,4 के लिए अंतिम है, और 1 - यह घटते क्रम में क्रमबद्ध है।
  • तो आपको "2" को कुछ बड़े में बदलना होगा - वास्तव में, "541" भाग में इससे छोटी संख्या में, अर्थात् 4।
  • अब, एक बार जब आप तय कर लेते हैं कि क्रमचय "34" के रूप में शुरू होगा, तो बाकी संख्याएँ बढ़ते क्रम में होनी चाहिए, इसलिए उत्तर "34125" है।

एल्गोरिथ्म तर्क की उस लाइन को ठीक से लागू करना है:

  1. सबसे लंबी "पूंछ" ढूंढें जो घटते क्रम में आदेशित है। ("541" भाग)
  2. पूंछ (4) से पहले छोटी संख्या ("2") से बड़ी संख्या में बदलें (4)।
  3. बढ़ते क्रम में पूंछ को क्रमबद्ध करें।

आप (1.) कुशलता से अंत में शुरू करके और पीछे की ओर तब तक जा सकते हैं जब तक कि पिछला तत्व मौजूदा तत्व से छोटा न हो। आप "2" के साथ "4" को स्वैप करके केवल (3) कर सकते हैं, इसलिए आपके पास "34521" होगा। एक बार जब आप ऐसा कर लेते हैं, तो आप (3.) के लिए एक छँटाई एल्गोरिथ्म का उपयोग करने से बच सकते हैं, क्योंकि पूंछ था, और अभी भी है (इस बारे में सोचो), घटते क्रम में क्रमबद्ध, इसलिए इसे केवल उल्टा करने की आवश्यकता है।

C ++ कोड ठीक यही करता है ( /usr/include/c++/4.0.0/bits/stl_algo.hअपने सिस्टम के स्रोत को देखें, या इस लेख को देखें ); अपनी भाषा में अनुवाद करने के लिए यह सरल होना चाहिए: [पढ़ें "बिडायरेक्शनलिएटर" को "पॉइंटर" के रूप में, यदि आप C ++ पुनरावृत्तियों से अपरिचित हैं। falseअगले क्रमपरिवर्तन नहीं होने पर कोड वापस आ जाता है, अर्थात हम पहले से ही घटते क्रम में हैं।]

template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first,
                      BidirectionalIterator last) {
    if (first == last) return false;
    BidirectionalIterator i = first;
    ++i;
    if (i == last) return false;
    i = last;
    --i;
    for(;;) {
        BidirectionalIterator ii = i--;
        if (*i <*ii) {
            BidirectionalIterator j = last;
            while (!(*i <*--j));
            iter_swap(i, j);
            reverse(ii, last);
            return true;
        }
        if (i == first) {
            reverse(first, last);
            return false;
        }
    }
}

ऐसा लग सकता है कि यह क्रमपरिवर्तन के समय O (n) समय ले सकता है, लेकिन यदि आप इसके बारे में अधिक ध्यान से सोचते हैं, तो आप यह साबित कर सकते हैं कि कुल मिलाकर सभी क्रमपरिवर्तन के लिए O (n!) समय लगता है, इसलिए केवल O (1) - निरंतर समय - क्रमपरिवर्तन।

अच्छी बात यह है कि एल्गोरिथ्म तब भी काम करता है जब आपके पास बार-बार तत्वों के साथ एक क्रम होता है: "232254421" के साथ, यह पूंछ को "54421" के रूप में पाएगा, "2" और "4" को स्वैप करेगा (इसलिए "23244221") ), बाकी को उलट दें, "232412245", जो अगले क्रमपरिवर्तन है।


2
यह काम करेगा, यह मानते हुए कि आपके पास तत्वों पर कुल आदेश है।
क्रिस कॉनवे

10
यदि आप एक सेट से शुरू करते हैं, तो आप मनमाने ढंग से तत्वों पर कुल आदेश को परिभाषित कर सकते हैं; तत्वों को अलग-अलग संख्याओं में मैप करें। :-)
श्रीवत्सआर

3
यह उत्तर केवल पर्याप्त उत्थान नहीं करता है, लेकिन मैं केवल एक बार इसे बढ़ा सकता हूं ... :-)
डैनियल सी। सोब्राल

1
@Masse: बिल्कुल नहीं ... मोटे तौर पर, आप 1 से बड़ी संख्या में जा सकते हैं। उदाहरण का उपयोग करना: 32541 से शुरू करें। पूंछ 541 है। आवश्यक चरणों को करने के बाद, अगला क्रमचय 34125 है। अब पूंछ सिर्फ 5 है। 5 का उपयोग करके 3412 बढ़ाना और स्वैप करना, अगला क्रमांकन 342 है। अब पूंछ है। 52, लंबाई 2. फिर यह 34215 (पूंछ की लंबाई 1), 34251 (पूंछ की लंबाई 2), 34512 (लंबाई 1), 34521 (लंबाई 3), 35124 (लंबाई 1), आदि हो जाती है। आप सही हैं कि पूंछ। अधिकांश समय, यही वजह है कि एल्गोरिथ्म में कई कॉल पर अच्छा प्रदर्शन होता है।
श्रीवत्सआर

1
@SamStoelinga: आप वास्तव में सही हैं। O (n लॉग एन) O (लॉग एन!) है। मुझे ओ (एन!) कहना चाहिए था।
श्रीवत्सआर

42

यह मानते हुए कि हम अनुमति दिए जा रहे मूल्यों पर लेक्सोग्राफ़िक आदेश के बारे में बात कर रहे हैं, दो सामान्य दृष्टिकोण हैं जिनका आप उपयोग कर सकते हैं:

  1. तत्वों के एक क्रमपरिवर्तन को अगले क्रमपरिवर्तन (श्रीवत्सआर के रूप में पोस्ट किया गया), या
  2. सीधे 0 से ऊपर की nगिनती करते हुए, वें क्रमपरिवर्तन की गणना करें n

उन लोगों के लिए (मेरी तरह ;-) जो मूल निवासी के रूप में c ++ नहीं बोलते हैं, दृष्टिकोण 1 को निम्नलिखित छद्म कोड से लागू किया जा सकता है, "बाएं" पर सूचकांक शून्य के साथ एक सरणी के शून्य-आधारित अनुक्रमण को मानते हुए (कुछ अन्य संरचना का प्रतिस्थापन) , एक सूची के रूप में, "अभ्यास के रूप में छोड़ दिया जाता है";;

1. scan the array from right-to-left (indices descending from N-1 to 0)
1.1. if the current element is less than its right-hand neighbor,
     call the current element the pivot,
     and stop scanning
1.2. if the left end is reached without finding a pivot,
     reverse the array and return
     (the permutation was the lexicographically last, so its time to start over)
2. scan the array from right-to-left again,
   to find the rightmost element larger than the pivot
   (call that one the successor)
3. swap the pivot and the successor
4. reverse the portion of the array to the right of where the pivot was found
5. return

यहां एक उदाहरण है जो CADB के वर्तमान क्रमांकन से शुरू होता है:

1. scanning from the right finds A as the pivot in position 1
2. scanning again finds B as the successor in position 3
3. swapping pivot and successor gives CBDA
4. reversing everything following position 1 (i.e. positions 2..3) gives CBAD
5. CBAD is the next permutation after CADB

दूसरे दृष्टिकोण के लिए ( nवें क्रमपरिवर्तन की प्रत्यक्ष गणना ), याद रखें कि तत्वों के N!क्रमपरिवर्तन Nहैं। इसलिए, यदि आप Nतत्वों की अनुमति दे रहे हैं, तो पहले (N-1)!क्रमपरिवर्तन सबसे छोटे तत्व से (N-1)!शुरू होना चाहिए , अगले क्रमपरिवर्तन दूसरे सबसे छोटे से शुरू होना चाहिए, और इसी तरह। यह निम्नलिखित पुनरावर्ती दृष्टिकोण की ओर जाता है (फिर से छद्म कोड में, क्रमपरिवर्तन और 0 से स्थितियां):

To find permutation x of array A, where A has N elements:
0. if A has one element, return it
1. set p to ( x / (N-1)! ) mod N
2. the desired permutation will be A[p] followed by
   permutation ( x mod (N-1)! )
   of the elements remaining in A after position p is removed

इसलिए, उदाहरण के लिए, ABCD का 13 वां क्रमांकन निम्नानुसार पाया जाता है:

perm 13 of ABCD: {p = (13 / 3!) mod 4 = (13 / 6) mod 4 = 2; ABCD[2] = C}
C followed by perm 1 of ABD {because 13 mod 3! = 13 mod 6 = 1}
  perm 1 of ABD: {p = (1 / 2!) mod 3 = (1 / 2) mod 2 = 0; ABD[0] = A}
  A followed by perm 1 of BD {because 1 mod 2! = 1 mod 2 = 1}
    perm 1 of BD: {p = (1 / 1!) mod 2 = (1 / 1) mod 2 = 1; BD[1] = D}
    D followed by perm 0 of B {because 1 mod 1! = 1 mod 1 = 0}
      B (because there's only one element)
    DB
  ADB
CADB

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

तो, ABCD के क्रमपरिवर्तन को पार करने के लिए, बस 0 से 23 (4--1) तक की गणना करें और सीधे इसी क्रमपरिवर्तन की गणना करें।


1
++ आपका जवाब कमतर है। स्वीकार किए गए उत्तर से दूर नहीं करने के लिए, लेकिन दूसरा दृष्टिकोण अधिक शक्तिशाली है क्योंकि इसे संयोजन के रूप में भी सामान्यीकृत किया जा सकता है। एक पूरी चर्चा अनुक्रम से सूचकांक तक रिवर्स फ़ंक्शन को दिखाएगी।
को Sente

1
वास्तव में। मैं पिछली टिप्पणी से सहमत हूं - भले ही मेरा जवाब पूछे गए विशिष्ट प्रश्न के लिए थोड़ा कम संचालन करता है, यह दृष्टिकोण अधिक सामान्य है, क्योंकि यह उदाहरण के लिए काम करता है जो कि एक दिए गए से के कदमों की दूरी पर है।
श्रीवत्सआर

4

आपको जांच करनी चाहिए विकिपीडिया पर पर्मुटेशन लेख की । इसके अलावा, फैक्टरैडिक संख्याओं की अवधारणा है ।

वैसे भी, गणितीय समस्या काफी कठिन है।

में C#आप एक का उपयोग कर सकते हैं iterator, और उपयोग करके क्रमचय एल्गोरिथ्म को रोक सकते हैं yield। इसके साथ समस्या यह है कि आप आगे और पीछे नहीं जा सकते हैं, या एक का उपयोग कर सकते हैं index


5
"वैसे भी, गणितीय समस्या काफी कठिन है।" नहीं यह नहीं है :-)
श्रीवत्सआर

ठीक है, यह है .. अगर आप फैक्टरोडिक संख्याओं के बारे में नहीं जानते हैं तो ऐसा कोई तरीका नहीं है जिससे आप स्वीकार्य समय में उचित एल्गोरिदम के साथ आ सकते हैं। यह विधि को जाने बिना 4 डिग्री के समीकरण को हल करने की कोशिश करने जैसा है।
बोगदान मैक्सिम

1
क्षमा करें, मुझे लगा कि आप मूल समस्या के बारे में बात कर रहे हैं। मैं अभी भी नहीं देखता कि आपको "फैक्टरैडिक संख्या" की आवश्यकता क्यों है ... वैसे भी प्रत्येक को एन को असाइन करना बहुत आसान है! किसी दिए गए सेट के क्रमपरिवर्तन, और एक संख्या से क्रमपरिवर्तन का निर्माण करना। [बस कुछ गतिशील प्रोग्रामिंग / गिनती ..]
श्रीवत्सआर

1
मुहावरेदार सी # में, एक इटोमेरेटर को एन्यूमरेटर के रूप में अधिक सही ढंग से संदर्भित किया जाता है ।
ड्रू नोक

@ श्रीवत्सआर: आप सभी क्रमपरिवर्तन उत्पन्न करने की कमी को कैसे पूरा करेंगे? उदाहरण के लिए यदि आपको n! वें क्रमपरिवर्तन की आवश्यकता है।
याकूब

3

उन्हें उत्पन्न करने के लिए क्रमपरिवर्तन एल्गोरिदम के अधिक उदाहरण।

स्रोत: http://www.ddj.com/altect/201200326

  1. Fike के एल्गोरिथ्म का उपयोग करता है, जो कि सबसे तेजी से ज्ञात में से एक है।
  2. अलोगो को लेक्सोग्राफिक ऑर्डर का उपयोग करता है।
  3. गैर-भौगोलिक का उपयोग करता है, लेकिन आइटम 2 की तुलना में तेज़ी से चलता है।

1।


PROGRAM TestFikePerm;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
WriteLn;
permcount := permcount + 1;
END;

PROCEDURE FikePerm ;
{Outputs permutations in nonlexicographic order.  This is Fike.s algorithm}
{ with tuning by J.S. Rohl.  The array marks[1..marksizn] is global.  The   }
{ procedure WriteArray is global and displays the results.  This must be}
{ evoked with FikePerm(2) in the calling procedure.}
VAR
    dn, dk, temp : INTEGER;
BEGIN
IF 
THEN BEGIN { swap the pair }
    WriteArray;
    temp :=marks[marksize];
    FOR dn :=  DOWNTO 1
    DO BEGIN
        marks[marksize] := marks[dn];
        marks [dn] := temp;
        WriteArray;
        marks[dn] := marks[marksize]
        END;
    marks[marksize] := temp;
    END {of bottom level sequence }
ELSE BEGIN
    FikePerm;
    temp := marks[k];
    FOR dk :=  DOWNTO 1
    DO BEGIN
        marks[k] := marks[dk];
        marks[dk][ := temp;
        FikePerm;
        marks[dk] := marks[k];
        END; { of loop on dk }
    marks[k] := temp;l
    END { of sequence for other levels }
END; { of FikePerm procedure }

BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii;
permcount := 0;
WriteLn ;
WrieLn;
FikePerm ; { It always starts with 2 }
WriteLn ;
ReadLn;
END.

2।


PROGRAM TestLexPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; permcount := permcount + 1; WriteLn; END;

PROCEDURE LexPerm ; { Outputs permutations in lexicographic order. The array marks is global } { and has n or fewer marks. The procedure WriteArray () is global and } { displays the results. } VAR work : INTEGER: mp, hlen, i : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray ; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN LexPerm<>; hlen := DIV 2; FOR i := 1 TO hlen DO BEGIN { Another swap } work := marks[i]; marks[i] := marks[n - i]; marks[n - i] := work END; work := marks[n]; { More swapping } marks[n[ := marks[mp]; marks[mp] := work; WriteArray; END; LexPerm<> END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii; permcount := 1; { The starting position is permutation } WriteLn < Starting position: >; WriteLn LexPerm ; WriteLn < PermCount is , permcount>; ReadLn; END.

3।


PROGRAM TestAllPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] of INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; WriteLn; permcount := permcount + 1; END;

PROCEDURE AllPerm (n : INTEGER); { Outputs permutations in nonlexicographic order. The array marks is } { global and has n or few marks. The procedure WriteArray is global and } { displays the results. } VAR work : INTEGER; mp, swaptemp : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN ALLPerm<< n - 1>>; IF > THEN swaptemp := 1 ELSE swaptemp := mp; work := marks[n]; marks[n] := marks[swaptemp}; marks[swaptemp} := work; WriteArray; AllPerm< n-1 >; END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii permcount :=1; WriteLn < Starting position; >; WriteLn; Allperm < marksize>; WriteLn < Perm count is , permcount>; ReadLn; END.


2

क्रमपरिवर्तन फ़ंक्शन clojure.contrib.lazy_seqs में पहले से ही ऐसा करने का दावा है।


धन्यवाद, मुझे इसकी जानकारी नहीं थी। यह आलसी होने का दावा करता है, लेकिन दुख की बात है कि यह बहुत खराब प्रदर्शन करता है और स्टैक को आसानी से ओवरफ्लो कर देता है।
ब्रायन कार्पेर

उदाहरण के लिए, इस उत्तर में आलस्य निश्चित रूप से स्टैक ओवरफ्लो का कारण बन सकता है ।
क्रॉकेएया
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.