পুনরাবৃত্তি থেকে পুনরাবৃত্তিতে যাওয়ার উপায়


349

আমি আমার বহু বছরের প্রোগ্রামিংয়ে সাধারণ সমস্যাগুলি সমাধান করার জন্য পুনরাবৃত্তিটি বেশ ব্যবহার করেছি, তবে আমি পুরোপুরি সচেতন যে কখনও কখনও স্মৃতি / গতির সমস্যার কারণে আপনার পুনরাবৃত্তি প্রয়োজন।

সুতরাং, একদম অতীতে আমি চেষ্টা করতে গিয়েছিলাম যে কোনও পুনরাবৃত্তির কাছে সাধারণ পুনরাবৃত্তি পদ্ধতির রূপান্তর করার কোনও "প্যাটার্ন" বা পাঠ্য-পুস্তকের কোনও অস্তিত্ব আছে কিনা এবং খুঁজে পাইনি। বা কমপক্ষে এমন কিছুই যা আমি মনে করতে পারি এটির সাহায্য করবে।

  • সাধারণ নিয়ম আছে?
  • "প্যাটার্ন" আছে কি?

4
আমি এই সিরিজটি তথ্যপূর্ণ পেয়েছি: blog.moertel.com/posts/2013-05-11-recursive-to-iterative.html
orionrush

উত্তর:


333

সাধারণত, আমি একটি পুনরাবৃত্ত অ্যালগরিদমকে পুনরাবৃত্ত অ্যালগরিদম দ্বারা প্রতিস্থাপন করি যেগুলি সাধারণত পরামিতিগুলিকে স্ট্যাকের মধ্যে পুনরাবৃত্ত ফাংশনে পাঠানো হয় push আসলে, আপনি প্রোগ্রামটির স্ট্যাকটি নিজের একটি দ্বারা প্রতিস্থাপন করছেন।

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]। তারপরে বাইরের দিকে, আপনি কেবল স্ট্যাকের বাইরে একটি ক্রিয়া / ধারাবাহিকতা পপ করুন, এটি প্রয়োগ করুন / সম্পাদন করুন, স্ট্যাকের উপরে ফিরে আসা ক্রিয়াকলাপগুলিকে বিপরীত ক্রমে চাপুন এবং পুনরাবৃত্তি করুন। আকস্মিক / জটিল traversals, আপনি শুধু যা চায় তা ক্যাপচার রেফারেন্স-গণনা পয়েন্টার স্থানীয় স্ট্যাকের ভেরিয়েবল হতো যে আপনার ঘনিষ্ঠ আপনার thunks ওভার, তারপর পরবর্তী thunks পূর্ববর্তী উপ-traversals ইত্যাদি ফলাফলে সাপেক্ষ হতে পারে
experquisite

6
স্ট্যাকওভারফ্লো এড়াতে কখনও কখনও আমরা পুনরাবৃত্তি এড়াতে পারি। তবে আমাদের নিজস্ব স্ট্যাক বজায় রাখার ফলেও স্ট্যাকওভারফ্লো হবে। তাহলে আমরা কেন আমাদের নিজস্ব স্ট্যাক দিয়ে পুনরাবৃত্তি বাস্তবায়ন করতে চাই?
ঝু লি

8
@ ঝুলি যদি আমরা ব্যবহার newকরি তবে আমরা স্ট্যাকের পরিবর্তে স্তূপে একটি অবজেক্ট তৈরি করতে পারি। স্ট্যাকের বিপরীতে, গাদা মেমরির বিধিনিষেধ নেই। Gribblelab.org/CBootCamp/7_
স্মৃতি_ট্যাক_ভিএস_এইচ

77

সত্যই, এটি করার সর্বাধিক সাধারণ উপায় হ'ল নিজের স্ট্যাকটি রাখা। এখানে সিটিতে একটি পুনরাবৃত্ত কুইকোর্টের ফাংশন রয়েছে:

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
কোনও নির্দিষ্ট পুনরাবৃত্তির জন্য বরাদ্দ দেওয়ার জন্য সর্বাধিক স্ট্যাকটি কীভাবে কার্যকর করা যায় সে সম্পর্কে কোনও ধারণা?
লেসিক্যালস্কোপ

@ অ্যালেক্সালস্কোপ ধরুন আপনার মধ্যে পুনরাবৃত্ত এলগোরিদম রয়েছে O(N) = O(R*L)যেখানে L"স্তর আর" এর জন্য জটিলতার যোগফল রয়েছে, উদাহরণস্বরূপ এই ক্ষেত্রে O(N)পার্টিশনগুলি করার প্রতিটি ধাপে আপনার কাজ রয়েছে, পুনরাবৃত্ত গভীরতা O(R), অর্থাৎ সবচেয়ে খারাপ পরিস্থিতি O(N), গড় গড় O(logN)এখানে।
কালেথ

48

দেখে মনে হচ্ছে যে পুনরাবৃত্তির ক্রিয়াকলাপ শরীরে একাধিকবার কল করে এবং পুনরাবৃত্তির নির্দিষ্ট বিন্দুতে ফিরে আসাকে পরিচালনা করে (যেমন আদিম-পুনরাবৃত্ত নয়)। বলা হয়ে থাকে যে প্রতিটি পুনরাবৃত্তি পুনরাবৃত্তিতে রূপান্তরিত হতে পারে , সুতরাং এটি সম্ভবত হওয়া উচিত বলে মনে হয়।

আমি কীভাবে এটি করতে পারি তার একটি সি # উদাহরণ দিয়ে এসেছি। ধরুন আপনার নীচের পুনরাবৃত্ত ফাংশন রয়েছে যা একটি পোস্টর্ডার ট্র্যাভারসালের মতো কাজ করে এবং AbcTreeNode একটি 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
এটি সত্যিই উপকারী, আমাকে পুনরুদ্ধারের পুনরাবৃত্ত সংস্করণ লিখতে হয়েছিল যা নিজেই এন-বার ivokes করেছিল, আপনার পোস্টের জন্য ধন্যবাদ আমি এটি করেছি।
ওয়াজেসিচ কুলিক

1
এটি এমন সর্বোত্তম উদাহরণ হতে হবে যা আমি ইতিমধ্যে কল স্ট্যাক পুনরাবৃত্তি এমন পরিস্থিতিতে দেখেছি যেখানে পদ্ধতিতে একাধিক পুনরাবৃত্তি কল করা হচ্ছে for সুন্দর কাজ.
সিসিএস

1
আপনি আমাকে বলেছিলেন "মনে হয় এমন কেউই সম্বোধন করেন নি যেখানে পুনরাবৃত্তির ক্রিয়াটি দেহে একবারে নিজেকে কল করে এবং পুনরাবৃত্তির একটি নির্দিষ্ট বিন্দুতে ফিরে আসে" এবং তারপরে আমি ইতিমধ্যে upvated করেছি। ঠিক আছে, এখন আমি আপনার বাকী উত্তরটি পড়তে যাচ্ছি এবং দেখুন যে আমার অকালবর্ধনটি ন্যায়সঙ্গত ছিল কিনা। (কারণ এর জবাব আমার খুব জেনে রাখা দরকার)।
mydoghasworms

1
@mydoghasworms - তাই বহুদিন পরে এই প্রশ্নের ফিরে এটা এমনকি নেন আমাকে স্মরণ করার ভাবছিলাম কি একটি মুহূর্ত। আশা করি উত্তরটি সাহায্য করেছে।
টি ওয়েবস্টার

1
আমি এই সমাধানটির ধারণাটি পছন্দ করেছিলাম তবে এটি আমার কাছে বিভ্রান্তিকর বলে মনে হয়েছিল। পাইথনে বাইনারি গাছের জন্য আমি সরলিকৃত সংস্করণ লিখেছিলাম, এটি কারও ধারণাটি বুঝতে সহায়তা করবে: gist.github.com/azurkin/abb258a0e1a821cbb331f2696b37c3ac
আজুরকিন

33

আপনার recursive কল করতে সংগ্রাম লেঙ্গুড় Recursion (পুনরাবৃত্তির যেখানে শেষ কথাটা recursive কল যায়)। আপনার এটি একবার হয়ে গেলে এটিকে পুনরাবৃত্তিতে রূপান্তর করা সাধারণত বেশ সহজ।



প্রচুর দোভাষী (যেমন স্কিম সর্বাধিক পরিচিত) লেজ পুনরাবৃত্তি ভালভাবে অনুকূলিত করবে। আমি জানি যে একটি নির্দিষ্ট অপ্টিমাইজেশনের সাথে জিসিসি লেজ পুনরাবৃত্তি করে (যদিও সি এরকম অপ্টিমাইজেশনের জন্য বিজোড় পছন্দ)।
new123456

19

ভাল, সাধারণভাবে, কেবল স্টোরেজ ভেরিয়েবল ব্যবহার করে পুনরাবৃত্তি পুনরাবৃত্তি হিসাবে নকল করা যায়। দ্রষ্টব্য এবং পুনরাবৃত্তি সাধারণত সমতুল্য; একজন প্রায় সর্বদা অন্যটিতে রূপান্তরিত হতে পারে। একটি পুচ্ছ-পুনরাবৃত্তি ফাংশন খুব সহজেই একটি পুনরাবৃত্তের মধ্যে রূপান্তরিত হয়। কেবল সঞ্চয়কারীকে স্থানীয় একটি পরিবর্তনশীল করে তুলুন এবং পুনরাবৃত্তির পরিবর্তে পুনরাবৃত্তি করুন। এখানে সি ++ এর একটি উদাহরণ রয়েছে (সি এটি কোনও ডিফল্ট যুক্তি ব্যবহারের জন্য নয়):

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

এমনকি স্ট্যাক ব্যবহার করা পুনরাবৃত্ত আলগোরিদিমকে পুনরাবৃত্তিতে রূপান্তরিত করে না। সাধারণ পুনরাবৃত্তি হ'ল ফাংশন ভিত্তিক পুনরাবৃত্তি এবং যদি আমরা স্ট্যাক ব্যবহার করি তবে এটি স্ট্যাক ভিত্তিক পুনরাবৃত্তি হয়ে যায়। কিন্তু এটি এখনও পুনরাবৃত্তি।

রিকার্সিভ অ্যালগরিদমের জন্য, স্পেস জটিলতা হ'ল ও (এন) এবং সময়ের জটিলতা হ'ল ও (এন)। পুনরাবৃত্ত অ্যালগরিদমের জন্য, স্থান জটিলতা হ'ল (1) এবং সময়ের জটিলতা হ'ল (এন)।

তবে যদি আমরা জটিলতার ক্ষেত্রে স্ট্যাক জিনিস ব্যবহার করি তবে একই থাকে। আমি মনে করি কেবলমাত্র লেজ পুনরাবৃত্তি পুনরাবৃত্তিতে রূপান্তর করা যায়।


1
আমি আপনার প্রথম বিট সাথে একমত, কিন্তু আমি মনে করি আমি দ্বিতীয় অনুচ্ছেদে ভুল বুঝছি। কেবল মেমরির copy = new int[size]; for(int i=0; i<size; ++i) copy[i] = source[i];স্থান এবং সময়ের জটিলতা উভয়ই ডি (ম) এর উপর ভিত্তি করে ডেটার আকারের উপর ভিত্তি করে একটি অ্যারের ক্লোনিংয়ের বিষয়টি বিবেচনা করুন তবে এটি স্পষ্টতই একটি পুনরাবৃত্ত অ্যালগরিদম।
পোনকডুডল

13

Stacks এবং পুনরাবৃত্তির বর্জন নিবন্ধ যেমনটি গাদা উপর স্ট্যাক ফ্রেম 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);
    }    
}

আইট্রেটিভ কোড:

// 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();
    }
}

খেয়াল করুন কীভাবে কোডটির কাঠামোটি এখনও পুনরাবৃত্ত যুক্তি অনুসারে সত্য থেকে যায় এবং পরিবর্তনগুলি ন্যূনতম হয়, ফলস্বরূপ কম সংখ্যক বাগ রয়েছে gs তুলনার জন্য, আমি পরিবর্তনগুলি ++ এবং - এর সাথে চিহ্নিত করেছি। 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

@ জনএক্স 2, stackitemআরম্ভ না করে অবশ্যই চাপ দেওয়া হবে না। তবে হ্যাঁ, 0 থেকে আরম্ভ করার ফলে ত্রুটি ধরা পড়বে।
চিতান

v.pop_back()পরিবর্তে উভয়ই ফেরতের ঠিকানাগুলি বিবৃতিতে সেট করা হয় না কেন ?
is7s

7

"চালিয়ে যাওয়ার শৈলীর জন্য" গুগল অনুসন্ধান করুন। একটি লেজ পুনরাবৃত্ত শৈলীতে রূপান্তর করার জন্য একটি সাধারণ পদ্ধতি রয়েছে; পুচ্ছ রিকার্সিভ ফাংশনগুলি লুপগুলিতে পরিণত করার জন্য একটি সাধারণ পদ্ধতিও রয়েছে।


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

}

উপরের উদাহরণটি বাইনারি অনুসন্ধান গাছে পুনরাবৃত্ত ডিএফএসের পুনরাবৃত্তির একটি উদাহরণ :)
অমিত

5

সাধারণত স্ট্যাকের ওভারফ্লো এড়ানোর কৌশলটি রিকার্সিভ ফাংশনগুলির জন্য বলা হয় ট্রাফোলিন টেকনিক যা জাভা দেবগণ ব্যাপকভাবে গ্রহণ করেন।

তবে, সি # এর জন্য এখানে একটি সামান্য সহায়ক পদ্ধতি রয়েছে যা যুক্তি পরিবর্তন করতে বা কোডটিকে বোধগম্য না করে আপনার পুনরাবৃত্ত ফাংশনটিকে পুনরাবৃত্তিতে পরিণত করে to সি # এমন একটি দুর্দান্ত ভাষা যা এর মাধ্যমে আশ্চর্যজনক জিনিসগুলি সম্ভব।

এটি কোনও সহায়ক পদ্ধতি দ্বারা পদ্ধতির অংশগুলিকে মোড়ানো দ্বারা কাজ করে। উদাহরণস্বরূপ নিম্নলিখিত পুনরাবৃত্তি ফাংশন:

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

সন্ধান করার জন্য একটি প্যাটার্ন হ'ল ফাংশনটির শেষে পুনরাবৃত্তি কল (তথাকথিত লেজ-পুনরাবৃত্তি)। এটি সহজেই কিছুক্ষণের সাথে প্রতিস্থাপন করা যায়। উদাহরণস্বরূপ, ফাংশন foo:

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

foo একটি কল দিয়ে শেষ হয়। এটি এর সাথে প্রতিস্থাপন করা যেতে পারে:

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)

উদাহরণস্বরূপ গ্রাফ: এ-> বি এ-> সি শো (এ) বি, এ, সি প্রিন্ট করবে

ফাংশন কলগুলির অর্থ স্থানীয় রাষ্ট্র এবং ধারাবাহিকতা সংরক্ষণ করুন যাতে আপনি ফিরে আসতে পারেন, এবং তারপরে আপনি যে ফাংশনটি কল করতে চান তাতে ঝাঁপিয়ে পড়ুন।

উদাহরণস্বরূপ, ধরুন শো (এ) চলমান শুরু হয়। লাইনে show. ফাংশন কল (শো) এর অর্থ - স্ট্যাকটিতে আইটেম যুক্ত করুন যার অর্থ "আপনাকে স্থানীয় ভেরিয়েবল স্টেট নোড = এ দিয়ে লাইন 2 এ চালিয়ে যেতে হবে" - নোট = বি দিয়ে গোটো লাইন 0 0

কোড কার্যকর করার জন্য, নির্দেশাবলী দিয়ে সিস্টেমটি চালিত হয়। যখন কোনও ফাংশন কলের মুখোমুখি হয়, সিস্টেমটি যেখানে এটি ছিল সেখানে ফিরে আসার জন্য প্রয়োজনীয় তথ্যটি পুশ করে, ফাংশন কোড চালায় এবং যখন ফাংশনটি সম্পূর্ণ হয়, তখন এটি চালিয়ে যাওয়ার জন্য কোথায় যেতে হবে সে সম্পর্কে তথ্য পপ করে।


0

এই লিঙ্কটি কিছু ব্যাখ্যা সরবরাহ করে এবং বেশ কয়েকটি পুনরাবৃত্ত কলগুলির মধ্যে সঠিক স্থানে পৌঁছতে সক্ষম হতে "অবস্থান" রাখার ধারণার প্রস্তাব দেয়:

যাইহোক, এই সমস্ত উদাহরণ দৃশ্যের বর্ণনা দেয় যাতে একটি পুনরাবৃত্ত কলকে একটি নির্দিষ্ট পরিমাণ সময় দেওয়া হয়। আপনার মতো কিছু থাকলে জিনিসগুলি আরও জটিল হয়ে উঠবে:

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

0

অলস পুনরাবৃত্তকারী যা একাধিক পুনরাবৃত্তকারী সরবরাহকারীদের সাথে সংঘবদ্ধ করে (ল্যাম্বডা এক্সপ্রেশন যা একটি পুনরাবৃত্তিকে ফেরত দেয়) ব্যবহার করে পুনরাবৃত্তির ট্র্যাভারসালকে আইট্রেটারে রূপান্তর করার একটি সাধারণ উপায় রয়েছে। আমার রূপান্তরিত পুনরাবৃত্তির ট্র্যাভারসালকে ইটেট্রেটারে দেখুন


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)))))
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.