पुनरावृत्ति से पुनरावृत्ति तक जाने का मार्ग


349

मैंने सरल समस्याओं को हल करने के लिए अपने कई वर्षों के प्रोग्रामिंग पर काफी पुनरावृत्ति का उपयोग किया है, लेकिन मैं पूरी तरह से जानता हूं कि कभी-कभी आपको मेमोरी / गति समस्याओं के कारण पुनरावृत्ति की आवश्यकता होती है।

इसलिए, किसी समय मैं बहुत दूर अतीत में कोशिश करने और खोजने के लिए गया था कि क्या कोई "पैटर्न" या पाठ्य-पुस्तक का तरीका पुनरावृत्ति के लिए एक सामान्य पुनरावृत्ति दृष्टिकोण को बदलने के लिए मौजूद था और कुछ भी नहीं मिला। या कम से कम कुछ भी नहीं है जो मुझे याद है कि यह मदद करेगा।

  • क्या सामान्य नियम हैं?
  • क्या कोई "पैटर्न" है?

4
मुझे यह श्रृंखला जानकारीपूर्ण लगी
orionrush

जवाबों:


334

आमतौर पर, मैं पुनरावर्ती एल्गोरिदम द्वारा पुनरावर्ती एल्गोरिथ्म को प्रतिस्थापित करता हूं जो उन मापदंडों को धक्का देता है जो आमतौर पर एक स्टैक पर पुनरावर्ती फ़ंक्शन को पारित किया जाएगा। वास्तव में, आप प्रोग्राम स्टैक की जगह ले रहे हैं।

Stack<Object> stack;
stack.push(first_object);
while( !stack.isEmpty() ) {
   // Do something
   my_object = stack.pop();

  // Push other objects on the stack.

}

नोट: यदि आपके पास एक से अधिक पुनरावर्ती कॉल हैं और आप कॉल के क्रम को संरक्षित करना चाहते हैं, तो आपको उन्हें रिवर्स ऑर्डर में स्टैक में जोड़ना होगा:

foo(first);
foo(second);

द्वारा प्रतिस्थापित किया जाना है

stack.push(second);
stack.push(first);

संपादित करें: लेख स्टैक्स एंड रिकर्सन एलिमिनेशन (या आर्टिकल बैकअप लिंक ) इस विषय पर अधिक विवरण में जाता है।


4
यदि आप एक कतार के साथ अपने स्टैक को प्रतिस्थापित करते हैं जो ऐड ऑर्डर को उलटने की समस्या को हल नहीं करता है?
शमूएलवर्न

2
मैंने इसे कागज पर काम किया और वे दो अलग-अलग चीजें हैं। यदि आप अपने द्वारा जोड़े गए आदेश को उलट देते हैं तो यह आपको सामान्य रूप से आगे की ओर ले जाता है, लेकिन आपका ट्रैवर्सल अभी भी गहराई-पहली खोज है। लेकिन अगर आप पूरी चीज़ को एक कतार में बदलते हैं, तो अब आप गहराई-पहले ट्रैवर्सल की बजाय चौड़ाई-पहला कर रहे हैं।
पीटी

1
मैंने अभी हाल ही में यह सामान्य तरीके से किया है, जहां मेरे नोड मुलाक़ात फ़ंक्शन (node)->()को (node)->[actions]एक्शन के साथ बदल दिया गया है () -> [actions]। फिर बाहर की तरफ, आप बस स्टैक से एक क्रिया / निरंतरता को पॉप करते हैं, इसे लागू / निष्पादित करते हैं, स्टैक पर वापस लौटाए गए कार्यों को क्रम में और दोहराते हैं। आकस्मिक / जटिल ट्रावेलल्स, आप बस कैप्चर करते हैं जो संदर्भ-गणना वाले पॉइंटर्स में स्थानीय स्टैक चर होते हैं जो आप अपने थ्रो में बंद करते हैं, फिर बाद के थ्रो पिछले उप-ट्रैवर्सल आदि के परिणामों पर आकस्मिक हो सकते हैं
अनुभववादी

6
स्टैकओवरफ्लो से बचने के लिए कभी-कभी हम पुनरावृत्ति से बचते हैं। लेकिन हमारे अपने स्टैक को बनाए रखने से स्टैकओवरफ्लो भी होगा। तो हम अपने स्वयं के ढेर के साथ पुनरावृत्ति क्यों लागू करना चाहते हैं?
झू ली

8
@ZhuLi यदि हम उपयोग newकरते हैं तो हम ढेर के बजाय ढेर पर एक वस्तु बना सकते हैं। स्टैक के विपरीत, हीप में मेमोरी प्रतिबंध नहीं है। देखें gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html
yuqli

77

वास्तव में, ऐसा करने का सबसे आम तरीका है कि आप अपना खुद का स्टैक रखें। यहाँ C में एक पुनरावर्ती क्विकॉर्ट फ़ंक्शन है:

void quicksort(int* array, int left, int right)
{
    if(left >= right)
        return;

    int index = partition(array, left, right);
    quicksort(array, left, index - 1);
    quicksort(array, index + 1, right);
}

यहां बताया गया है कि कैसे हम अपने स्वयं के स्टैक को रखकर इसे पुनरावृत्त बना सकते हैं:

void quicksort(int *array, int left, int right)
{
    int stack[1024];
    int i=0;

    stack[i++] = left;
    stack[i++] = right;

    while (i > 0)
    {
        right = stack[--i];
        left = stack[--i];

        if (left >= right)
             continue;

        int index = partition(array, left, right);
        stack[i++] = left;
        stack[i++] = index - 1;
        stack[i++] = index + 1;
        stack[i++] = right;
    }
}

जाहिर है, यह उदाहरण स्टैक सीमाओं की जांच नहीं करता है ... और वास्तव में आप स्टैक को सबसे खराब स्थिति के आधार पर आकार दे सकते हैं जो बाएं और दाएं मान दिए गए हैं। लेकिन आप विचार समझ गये।


1
किसी विशेष पुनरावर्तन के लिए आवंटित करने के लिए अधिकतम स्टैक कैसे करें, इस पर कोई विचार?
lexicalscope

मान लीजिए कि आपके पास एक पुनरावर्ती एल्गोरिथ्म है O(N) = O(R*L), जहां L"लेयर आर" के लिए जटिलता का योग है, उदाहरण के लिए, इस मामले में आपके पास O(N)विभाजन करने वाले प्रत्येक चरण में काम है, पुनरावर्ती गहराई है O(R), अर्थात सबसे खराब स्थिति O(N), O(logN)यहां औसतन ।
कालथ

48

ऐसा लगता है कि किसी ने भी पता नहीं लगाया है कि पुनरावर्ती कार्य शरीर में एक से अधिक बार कॉल करता है, और पुनरावृत्ति में एक विशेष बिंदु पर वापस लौटता है (यानी आदिम-पुनरावर्ती नहीं)। ऐसा कहा जाता है कि प्रत्येक पुनरावृत्ति को पुनरावृत्ति में बदल दिया जा सकता है , इसलिए ऐसा प्रतीत होता है कि यह संभव होना चाहिए।

मैं सिर्फ यह कैसे करना है का एक C # उदाहरण के साथ आया था। मान लीजिए कि आपके पास निम्न पुनरावर्ती कार्य है, जो एक पोस्टऑर्डर ट्रैवर्सल की तरह काम करता है, और यह कि एबीसीट्रीऑन 3-एरी ट्री है जिसमें पॉइंटर्स ए, बी, सी है।

public static void AbcRecursiveTraversal(this AbcTreeNode x, List<int> list) {
        if (x != null) {
            AbcRecursiveTraversal(x.a, list);
            AbcRecursiveTraversal(x.b, list);
            AbcRecursiveTraversal(x.c, list);
            list.Add(x.key);//finally visit root
        }
}

पुनरावृत्त समाधान:

        int? address = null;
        AbcTreeNode x = null;
        x = root;
        address = A;
        stack.Push(x);
        stack.Push(null)    

        while (stack.Count > 0) {
            bool @return = x == null;

            if (@return == false) {

                switch (address) {
                    case A://   
                        stack.Push(x);
                        stack.Push(B);
                        x = x.a;
                        address = A;
                        break;
                    case B:
                        stack.Push(x);
                        stack.Push(C);
                        x = x.b;
                        address = A;
                        break;
                    case C:
                        stack.Push(x);
                        stack.Push(null);
                        x = x.c;
                        address = A;
                        break;
                    case null:
                        list_iterative.Add(x.key);
                        @return = true;
                        break;
                }

            }


            if (@return == true) {
                address = (int?)stack.Pop();
                x = (AbcTreeNode)stack.Pop();
            }


        }

5
यह वास्तव में उपयोगी है, मुझे पुनरावृत्ति का पुनरावृत्त संस्करण लिखना पड़ा, जो कि एन-बार खुद चुटकुले करता है, आपकी पोस्ट के लिए धन्यवाद।
वोज्शिएक कुलिक

1
इसका सबसे अच्छा उदाहरण है कि मैंने उन स्थितियों के लिए कॉल स्टैक पुनरावृत्ति का कभी देखा है जहां विधि के भीतर कई पुनरावर्ती कॉल किए जा रहे हैं। अच्छी नौकरी।
सीसीएस

1
आपने मुझे "ऐसा लगता है कि किसी ने भी संबोधित नहीं किया है जहां पुनरावर्ती कार्य शरीर में एक से अधिक बार कॉल करता है, और पुनरावृत्ति में एक विशिष्ट बिंदु पर लौटने का काम करता है" और फिर मैं पहले से ही अपवित्र हो गया। ठीक है, अब मैं आपके शेष उत्तर को पढ़ने जा रहा हूं और देख रहा हूं कि क्या मेरा समयपूर्व उत्थान उचित था। (क्योंकि मुझे उस का उत्तर जानने की सख्त आवश्यकता है)।
mydoghasworms

1
@mydoghasworms - इतने लंबे समय के बाद इस सवाल पर लौटते हुए, मुझे एक पल के लिए याद आया कि मैं क्या सोच रहा था। आशा है कि उत्तर ने मदद की।
टी। वेबस्टर

1
मुझे इस समाधान का विचार पसंद आया, लेकिन यह मुझे भ्रमित करने वाला लगा। मैं अजगर में बाइनरी पेड़ के लिए सरलीकृत संस्करण में लिखा है, शायद यह मदद मिलेगी किसी विचार को समझने के लिए: gist.github.com/azurkin/abb258a0e1a821cbb331f2696b37c3ac
azurkin

33

अपने पुनरावर्ती कॉल करने के लिए प्रयास करें टेल रिकर्सन (पुनरावर्ती जहां अंतिम विवरण पुनरावर्ती कॉल है)। एक बार जब आप ऐसा कर लेते हैं, तो इसे पुनरावृति में बदलना आम तौर पर बहुत आसान होता है।


2
: कुछ JIT की पूंछ प्रत्यावर्तन को बदलने ibm.com/developerworks/java/library/j-diag8.html
Liran Orevi

दुभाषियों से भरपूर (अर्थात योजना सबसे अधिक जानी जाती है) पूंछ पुनरावृत्ति को अच्छी तरह से अनुकूलित करेगी। मुझे पता है कि जीसीसी, एक निश्चित अनुकूलन के साथ, पूंछ पुनरावृत्ति करता है (भले ही सी इस तरह के अनुकूलन के लिए एक अजीब विकल्प है)।
new123456

19

ठीक है, सामान्य तौर पर, पुनरावृत्ति को केवल एक भंडारण चर का उपयोग करके पुनरावृत्ति के रूप में नकल किया जा सकता है। ध्यान दें कि पुनरावृत्ति और पुनरावृत्ति आम तौर पर समकक्ष हैं; एक को लगभग हमेशा दूसरे में बदला जा सकता है। एक पूंछ-पुनरावर्ती कार्य बहुत आसानी से पुनरावृत्त में परिवर्तित हो जाता है। बस संचायक चर को एक स्थानीय बनाएं, और पुनरावृत्ति के बजाय पुनरावृति करें। यहाँ C ++ में एक उदाहरण दिया गया है (C यह एक डिफ़ॉल्ट तर्क के उपयोग के लिए नहीं था):

// tail-recursive
int factorial (int n, int acc = 1)
{
  if (n == 1)
    return acc;
  else
    return factorial(n - 1, acc * n);
}

// iterative
int factorial (int n)
{
  int acc = 1;
  for (; n > 1; --n)
    acc *= n;
  return acc;
}

मुझे जानते हुए, मैंने शायद कोड में गलती की है, लेकिन विचार है।


14

यहां तक ​​कि स्टैक का उपयोग करने से पुनरावृत्ति एल्गोरिदम को पुनरावृत्त में परिवर्तित नहीं किया जाएगा। सामान्य पुनरावृत्ति कार्य आधारित पुनरावर्तन है और यदि हम स्टैक का उपयोग करते हैं तो यह स्टैक आधारित पुनरावृत्ति बन जाता है। लेकिन इसकी अभी भी पुनरावृत्ति है।

पुनरावर्ती एल्गोरिदम के लिए, अंतरिक्ष जटिलता O (N) है और समय जटिलता O (N) है। पुनरावृत्ति एल्गोरिदम के लिए, अंतरिक्ष जटिलता O (1) है और समय जटिलता O (N) है।

लेकिन अगर हम जटिलता के संदर्भ में स्टैक चीजों का उपयोग करते हैं, तो वही रहता है। मुझे लगता है कि केवल पूंछ पुनरावृत्ति को पुनरावृत्ति में परिवर्तित किया जा सकता है।


1
मैं आपके पहले बिट से सहमत हूं, लेकिन मुझे लगता है कि मैं दूसरे पैराग्राफ को गलत समझ रहा हूं। स्मृति copy = new int[size]; for(int i=0; i<size; ++i) copy[i] = source[i];स्थान और समय की जटिलता की प्रतिलिपि बनाने के माध्यम से किसी सरणी को क्लोन करने पर विचार करें दोनों डेटा के आकार के आधार पर O (N) हैं, लेकिन यह स्पष्ट रूप से एक पुनरावृत्त एल्गोरिथ्म है।
पोंकाडूडल

13

ढेर और प्रत्यावर्तन उन्मूलन लेख कैप्चर ढेर पर ढेर फ्रेम externalizing का विचार है, लेकिन एक प्रदान नहीं करता है सीधा और repeatable परिवर्तित करने के लिए जिस तरह से। नीचे एक है।

पुनरावृत्ति कोड में परिवर्तित करते समय, किसी को यह पता होना चाहिए कि पुनरावर्ती कॉल मनमाने ढंग से गहरे कोड ब्लॉक से हो सकता है। इसका न केवल पैरामीटर, बल्कि तर्क पर लौटने का बिंदु भी है जो निष्पादित होने के लिए रहता है और चर की स्थिति जो बाद की स्थिति में भाग लेते हैं, जो मायने रखते हैं। नीचे कम से कम परिवर्तनों के साथ पुनरावृति कोड में परिवर्तित करने का एक बहुत ही सरल तरीका है।

इस पुनरावर्ती कोड पर विचार करें:

struct tnode
{
    tnode(int n) : data(n), left(0), right(0) {}
    tnode *left, *right;
    int data;
};

void insertnode_recur(tnode *node, int num)
{
    if(node->data <= num)
    {
        if(node->right == NULL)
            node->right = new tnode(num);
        else
            insertnode(node->right, num);
    }
    else
    {
        if(node->left == NULL)
            node->left = new tnode(num);
        else
            insertnode(node->left, num);
    }    
}

Iterative कोड:

// Identify the stack variables that need to be preserved across stack 
// invocations, that is, across iterations and wrap them in an object
struct stackitem 
{ 
    stackitem(tnode *t, int n) : node(t), num(n), ra(0) {}
    tnode *node; int num;
    int ra; //to point of return
};

void insertnode_iter(tnode *node, int num) 
{
    vector<stackitem> v;
    //pushing a stackitem is equivalent to making a recursive call.
    v.push_back(stackitem(node, num));

    while(v.size()) 
    {
        // taking a modifiable reference to the stack item makes prepending 
        // 'si.' to auto variables in recursive logic suffice
        // e.g., instead of num, replace with si.num.
        stackitem &si = v.back(); 
        switch(si.ra)
        {
        // this jump simulates resuming execution after return from recursive 
        // call 
            case 1: goto ra1;
            case 2: goto ra2;
            default: break;
        } 

        if(si.node->data <= si.num)
        {
            if(si.node->right == NULL)
                si.node->right = new tnode(si.num);
            else
            {
                // replace a recursive call with below statements
                // (a) save return point, 
                // (b) push stack item with new stackitem, 
                // (c) continue statement to make loop pick up and start 
                //    processing new stack item, 
                // (d) a return point label
                // (e) optional semi-colon, if resume point is an end 
                // of a block.

                si.ra=1;
                v.push_back(stackitem(si.node->right, si.num));
                continue; 
ra1:            ;         
            }
        }
        else
        {
            if(si.node->left == NULL)
                si.node->left = new tnode(si.num);
            else
            {
                si.ra=2;                
                v.push_back(stackitem(si.node->left, si.num));
                continue;
ra2:            ;
            }
        }

        v.pop_back();
    }
}

ध्यान दें कि कोड की संरचना अभी भी कैसे पुनरावर्ती तर्क के लिए सही है और संशोधन न्यूनतम हैं, जिसके परिणामस्वरूप बग की संख्या कम है। तुलना के लिए, मैंने ++ और - के साथ परिवर्तनों को चिह्नित किया है। V.push_back को छोड़कर अधिकांश नए सम्मिलित ब्लॉक, किसी भी परिवर्तित पुनरावृत्ति तर्क के लिए सामान्य हैं

void insertnode_iter(tnode *node, int num) 
{

+++++++++++++++++++++++++

    vector<stackitem> v;
    v.push_back(stackitem(node, num));

    while(v.size())
    {
        stackitem &si = v.back(); 
        switch(si.ra)
        {
            case 1: goto ra1;
            case 2: goto ra2;
            default: break;
        } 

------------------------

        if(si.node->data <= si.num)
        {
            if(si.node->right == NULL)
                si.node->right = new tnode(si.num);
            else
            {

+++++++++++++++++++++++++

                si.ra=1;
                v.push_back(stackitem(si.node->right, si.num));
                continue; 
ra1:            ;    

-------------------------

            }
        }
        else
        {
            if(si.node->left == NULL)
                si.node->left = new tnode(si.num);
            else
            {

+++++++++++++++++++++++++

                si.ra=2;                
                v.push_back(stackitem(si.node->left, si.num));
                continue;
ra2:            ;

-------------------------

            }
        }

+++++++++++++++++++++++++

        v.pop_back();
    }

-------------------------

}

इससे मुझे बहुत मदद मिली है, लेकिन वहाँ एक समस्या है: stackitemवस्तुओं को एक कचरा मूल्य के साथ आवंटित किया जाता है ra। सब कुछ अभी भी सबसे अधिक मामले में काम करता है, लेकिन raसंयोग से 1 या 2 होना चाहिए आपको गलत व्यवहार मिलेगा। इसका समाधान ra0.
JanX2

@ JanX2, stackitemको प्रारंभ किए बिना धक्का नहीं दिया जाना चाहिए। लेकिन हाँ, 0 को प्रारंभ करना त्रुटियों को पकड़ लेगा।
चेतन

v.pop_back()इसके बजाय दोनों विवरणों को विवरण पर सेट क्यों नहीं किया जाता है?
21

7

"निरंतरता गुजर शैली" के लिए Google खोजें। पूंछ पुनरावर्ती शैली में परिवर्तित करने के लिए एक सामान्य प्रक्रिया है; पूंछ पुनरावर्ती कार्यों को छोरों में बदलने के लिए एक सामान्य प्रक्रिया भी है।


6

बस समय की हत्या ... एक पुनरावर्ती कार्य

void foo(Node* node)
{
    if(node == NULL)
       return;
    // Do something with node...
    foo(node->left);
    foo(node->right);
}

में परिवर्तित किया जा सकता है

void foo(Node* node)
{
    if(node == NULL)
       return;

    // Do something with node...

    stack.push(node->right);
    stack.push(node->left);

    while(!stack.empty()) {
         node1 = stack.pop();
         if(node1 == NULL)
            continue;
         // Do something with node1...
         stack.push(node1->right);             
         stack.push(node1->left);
    }

}

उपरोक्त उदाहरण द्विआधारी खोज पेड़ पर पुनरावृत्त dfs के पुनरावर्ती का एक उदाहरण है :)
अमित

5

आम तौर पर स्टैक ओवरफ्लो से बचने की तकनीक पुनरावर्ती कार्यों के लिए होती है जिसे ट्रैम्पोलिन तकनीक कहा जाता है जिसे व्यापक रूप से जावा देवों द्वारा अपनाया जाता है।

हालाँकि, C # के लिए यहाँ थोड़ा सहायक विधि है जो तर्क बदलने के लिए या कोड को अपरिहार्य बनाने के लिए आपके पुनरावर्ती कार्य को पुनरावृत्ति में बदल देती है। C # इतनी अच्छी भाषा है कि इसके साथ अद्भुत सामग्री संभव है।

यह सहायक विधि द्वारा विधि के कुछ हिस्सों को लपेटकर काम करता है। उदाहरण के लिए निम्नलिखित पुनरावर्ती कार्य:

int Sum(int index, int[] array)
{
 //This is the termination condition
 if (int >= array.Length)
 //This is the returning value when termination condition is true
 return 0;

//This is the recursive call
 var sumofrest = Sum(index+1, array);

//This is the work to do with the current item and the
 //result of recursive call
 return array[index]+sumofrest;
}

में बदल जाता है:

int Sum(int[] ar)
{
 return RecursionHelper<int>.CreateSingular(i => i >= ar.Length, i => 0)
 .RecursiveCall((i, rv) => i + 1)
 .Do((i, rv) => ar[i] + rv)
 .Execute(0);
}

4

उन चीजों के बारे में सोचना जो वास्तव में एक स्टैक की आवश्यकता होती हैं:

यदि हम पुनरावृत्ति के पैटर्न पर विचार करते हैं:

if(task can be done directly) {
    return result of doing task directly
} else {
    split task into two or more parts
    solve for each part (possibly by recursing)
    return result constructed by combining these solutions
}

उदाहरण के लिए, हनोई का क्लासिक टॉवर

if(the number of discs to move is 1) {
    just move it
} else {
    move n-1 discs to the spare peg
    move the remaining disc to the target peg
    move n-1 discs from the spare peg to the target peg, using the current peg as a spare
}

इसे एक स्पष्ट स्टैक पर काम कर रहे लूप में अनुवादित किया जा सकता है, इसे निम्न प्रकार से:

place seed task on stack
while stack is not empty 
   take a task off the stack
   if(task can be done directly) {
      Do it
   } else {
      Split task into two or more parts
      Place task to consolidate results on stack
      Place each task on stack
   }
}

हनोई के टॉवर के लिए यह बन जाता है:

stack.push(new Task(size, from, to, spare));
while(! stack.isEmpty()) {
    task = stack.pop();
    if(task.size() = 1) {
        just move it
    } else {
        stack.push(new Task(task.size() -1, task.spare(), task,to(), task,from()));
        stack.push(new Task(1, task.from(), task.to(), task.spare()));
        stack.push(new Task(task.size() -1, task.from(), task.spare(), task.to()));
    }
}

यहां काफी लचीलापन है कि आप अपने स्टैक को कैसे परिभाषित करते हैं। आप अपने स्टैक को उन Commandवस्तुओं की सूची बना सकते हैं जो परिष्कृत चीजें करते हैं। या आप विपरीत दिशा में जा सकते हैं और इसे सरल प्रकारों की सूची बना सकते हैं (उदाहरण के लिए, "ढेर" पर intएक तत्व के बजाय 4 तत्व हो सकते हैं Task)।

इसका मतलब यह है कि स्टैक के लिए मेमोरी जावा निष्पादन स्टैक के बजाय ढेर में है, लेकिन यह आपके लिए उपयोगी हो सकता है।


3

देखने के लिए एक पैटर्न फ़ंक्शन के अंत में एक पुनरावृत्ति कॉल है (जिसे पूंछ-पुनरावृत्ति कहा जाता है)। इसे आसानी से थोड़ी देर से बदला जा सकता है। उदाहरण के लिए, फ़ंक्शन फू:

void foo(Node* node)
{
    if(node == NULL)
       return;
    // Do something with node...
    foo(node->left);
    foo(node->right);
}

फू के लिए एक कॉल के साथ समाप्त होता है। इसके साथ प्रतिस्थापित किया जा सकता है:

void foo(Node* node)
{
    while(node != NULL)
    {
        // Do something with node...
        foo(node->left);
        node = node->right;
     }
}

जो दूसरी पुनरावर्ती कॉल को समाप्त करता है।


3
अभी भी मेरे लिए पुनरावर्ती लग रहा है ... :)
नथन

2
ठीक है, हाँ - लेकिन यह पुनरावर्ती के रूप में आधा है। अन्य पुनरावृत्ति से छुटकारा पाने के लिए एक और तकनीक का उपयोग करने की आवश्यकता होती है ...
मार्क बेसी

2

एक प्रश्न जो इस के डुप्लिकेट के रूप में बंद किया गया था, उसमें एक बहुत विशिष्ट डेटा संरचना थी:

यहां छवि विवरण दर्ज करें

नोड में निम्नलिखित संरचना थी:

typedef struct {
    int32_t type;
    int32_t valueint;
    double  valuedouble;
    struct  cNODE *next;
    struct  cNODE *prev;
    struct  cNODE *child;
} cNODE;

पुनरावर्ती विलोपन कार्य निम्न प्रकार देखा गया:

void cNODE_Delete(cNODE *c) {
    cNODE*next;
    while (c) {
        next=c->next;
        if (c->child) { 
          cNODE_Delete(c->child)
        }
        free(c);
        c=next;
    }
}

सामान्य तौर पर, पुनरावर्ती कार्यों के लिए एक स्टैक से बचना हमेशा संभव नहीं होता है जो एक से अधिक बार (या एक बार भी) आह्वान करता है। हालांकि, इस विशेष संरचना के लिए, यह संभव है। विचार सभी नोड्स को एक सूची में समतल करना है। यह वर्तमान नोड के childशीर्ष पंक्ति की सूची के अंत में लगाकर पूरा किया गया है ।

void cNODE_Delete (cNODE *c) {
    cNODE *tmp, *last = c;
    while (c) {
        while (last->next) {
            last = last->next;   /* find last */
        }
        if ((tmp = c->child)) {
            c->child = NULL;     /* append child to last */
            last->next = tmp;
            tmp->prev = last;
        }
        tmp = c->next;           /* remove current */
        free(c);
        c = tmp;
    }
}

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


1

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

अब खेल में आता है।

इसलिए यदि आप एक पुनरावृत्त कार्यक्रम लिखते हैं और हर बार एक स्टैक पर राज्य को बचाते हैं और तब ज़रूरत पड़ने पर स्टैक से मानों को निकालते हैं, तो आपने पुनरावर्ती प्रोग्राम को पुनरावृति में सफलतापूर्वक बदल दिया है!

प्रमाण सरल और विश्लेषणात्मक है।

पुनरावृत्ति में कंप्यूटर एक स्टैक रखता है और पुनरावृति संस्करण में आपको स्टैक को मैन्युअल रूप से बनाए रखना होगा।

इसके बारे में सोचें, बस एक गहराई से पहली खोज (ग्राफ़ पर) पुनरावर्ती कार्यक्रम को डीएफएस पुनरावृत्ति कार्यक्रम में परिवर्तित करें।

शुभकामनाएं!


1

स्टैक का उपयोग करके पुनरावर्ती कार्य को पुनरावृत्ति में बदलने का एक और सरल और पूर्ण उदाहरण।

#include <iostream>
#include <stack>
using namespace std;

int GCD(int a, int b) { return b == 0 ? a : GCD(b, a % b); }

struct Par
{
    int a, b;
    Par() : Par(0, 0) {}
    Par(int _a, int _b) : a(_a), b(_b) {}
};

int GCDIter(int a, int b)
{
    stack<Par> rcstack;

    if (b == 0)
        return a;
    rcstack.push(Par(b, a % b));

    Par p;
    while (!rcstack.empty()) 
    {
        p = rcstack.top();
        rcstack.pop();
        if (p.b == 0)
            continue;
        rcstack.push(Par(p.b, p.a % p.b));
    }

    return p.a;
}

int main()
{
    //cout << GCD(24, 36) << endl;
    cout << GCDIter(81, 36) << endl;

    cin.get();
    return 0;
}

0

एक प्रणाली किसी भी पुनरावर्ती कार्य को कैसे लेती है इसका एक मोटा विवरण और एक स्टैक का उपयोग करके इसे निष्पादित करता है:

इसका उद्देश्य बिना विवरण के विचार दिखाना था। इस फ़ंक्शन पर विचार करें जो एक ग्राफ के नोड्स को प्रिंट करेगा:

function show(node)
0. if isleaf(node):
1.  print node.name
2. else:
3.  show(node.left)
4.  show(node)
5.  show(node.right)

उदाहरण के लिए ग्राफ: A-> B A-> C शो (A) B, A, C प्रिंट करेगा

फ़ंक्शन कॉल का अर्थ है स्थानीय स्थिति और निरंतरता बिंदु को सहेजना ताकि आप वापस आ सकें, और फिर उस फ़ंक्शन को कूदें जिसे आप कॉल करना चाहते हैं।

उदाहरण के लिए, मान लीजिए कि शो (ए) चलना शुरू होता है। फंक्शन कॉल ऑन लाइन 3. शो (बी) का अर्थ है - स्टैक में आइटम जोड़ें "आपको स्थानीय चर राज्य नोड = ए" के साथ लाइन 2 पर जारी रखने की आवश्यकता होगी - नोड लाइन 0 नोड के साथ = बी।

कोड निष्पादित करने के लिए, सिस्टम निर्देशों के माध्यम से चलता है। जब एक फ़ंक्शन कॉल का सामना करना पड़ता है, तो सिस्टम जानकारी को धक्का देता है जहां उसे वापस आने की आवश्यकता होती है, वह फ़ंक्शन कोड चलाता है, और जब फ़ंक्शन पूरा होता है, तो उस जानकारी को पॉप करता है जहां उसे जारी रखने के लिए जाने की आवश्यकता होती है।


0

यह लिंक कुछ स्पष्टीकरण प्रदान करता है और कई पुनरावर्ती कॉलों के बीच सटीक स्थान पर पहुंचने के लिए "स्थान" रखने के विचार का प्रस्ताव करता है:

हालांकि, ये सभी उदाहरण उन परिदृश्यों का वर्णन करते हैं जिनमें एक पुनरावर्ती कॉल को निश्चित मात्रा में किया जाता है। जब आपके पास कुछ हो तो चीजें मुश्किल हो जाती हैं:

function rec(...) {
  for/while loop {
    var x = rec(...)
    // make a side effect involving return value x
  }
}

0

एक आलसी इटरेटर का उपयोग करके पुनरावर्ती ट्रैवर्सल को पुनरावृत्ति में परिवर्तित करने का एक सामान्य तरीका है, जो कई इटरेटर आपूर्तिकर्ताओं (लैम्ब्डा अभिव्यक्ति जो एक इटेरेटर लौटाता है) को समेटता है। Iterator को मेरा Converting Recursive Traversal देखें ।


0

मेरे उदाहरण क्लोजर में हैं, लेकिन किसी भी भाषा में अनुवाद करने के लिए काफी आसान होना चाहिए।

इस फ़ंक्शन को देखते हुए StackOverflowजो n के बड़े मानों के लिए है:

(defn factorial [n]
  (if (< n 2)
    1
    (*' n (factorial (dec n)))))

हम एक ऐसे संस्करण को परिभाषित कर सकते हैं जो निम्नलिखित तरीके से अपने स्वयं के स्टैक का उपयोग करता है:

(defn factorial [n]
  (loop [n n
         stack []]
    (if (< n 2)
      (return 1 stack)
      ;; else loop with new values
      (recur (dec n)
             ;; push function onto stack
             (cons (fn [n-1!]
                     (*' n n-1!))
                   stack)))))

कहां returnपरिभाषित किया गया है:

(defn return
  [v stack]
  (reduce (fn [acc f]
            (f acc))
          v
          stack))

यह अधिक जटिल कार्यों के लिए भी काम करता है, उदाहरण के लिए एकरमैन फ़ंक्शन :

(defn ackermann [m n]
  (cond
    (zero? m)
    (inc n)

    (zero? n)
    (recur (dec m) 1)

    :else
    (recur (dec m)
           (ackermann m (dec n)))))

में रूपांतरित किया जा सकता है:

(defn ackermann [m n]
  (loop [m m
         n n
         stack []]
    (cond
      (zero? m)
      (return (inc n) stack)

      (zero? n)
      (recur (dec m) 1 stack)

      :else
      (recur m
             (dec n)
             (cons #(ackermann (dec m) %)
                   stack)))))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.