प्रत्यावर्तन क्या है और जब मैं इसे इस्तेमाल करना चाहिए?

वोट
121

जिन विषयों के मेलिंग सूचियों और ऑनलाइन चर्चा पर नियमित रूप से आने के लिए लगता है में से एक कंप्यूटर विज्ञान की डिग्री कर की खूबियों (या अभाव क्या है)। एक तर्क यह है कि नकारात्मक पार्टी के लिए फिर से समय और आने के लिए लगता है कि वे साल के कुछ नंबर के लिए कोडिंग किया गया है और वे प्रत्यावर्तन कभी उपयोग नहीं किया है।

तो सवाल यह है:

  1. प्रत्यावर्तन क्या है?
  2. जब मैं प्रत्यावर्तन का प्रयोग करेंगे?
  3. क्यों लोगों प्रत्यावर्तन का उपयोग नहीं करते?
06/08/2008 को 03:29
का स्रोत उपयोगकर्ता
अन्य भाषाओं में...                            


40 जवाब

वोट
9
  1. एक समारोह है कि खुद को कॉल
  2. एक समारोह हो सकता है जब (आसानी से) एक सरल ऑपरेशन के साथ साथ समस्या के कुछ छोटे से हिस्से पर एक ही समारोह में विघटित। मैं नहीं बल्कि, कहना चाहिए, यह यह प्रत्यावर्तन लिए एक अच्छे उम्मीदवार बनाता है।
  3. वे करते हैं!

विहित उदाहरण भाज्य जो की तरह लग रहा है:

int fact(int a) 
{
  if(a==1)
    return 1;

  return a*fact(a-1);
}

सामान्य तौर पर, प्रत्यावर्तन जरूरी तेजी (समारोह कॉल उपरि उच्च हो जाता है क्योंकि पुनरावर्ती कार्यों छोटे होते हैं, ऊपर देखें) और कुछ समस्याओं से ग्रस्त कर सकते हैं (ढेर अतिप्रवाह किसी को?) नहीं है। कुछ वे गैर तुच्छ मामलों में 'सही' प्राप्त करने के लिए कठिन हो जाते हैं, लेकिन मैं वास्तव में उस में नहीं खरीदते हैं का कहना है। कुछ स्थितियों में, प्रत्यावर्तन सबसे समझ में आता है और सबसे सुंदर और स्पष्ट को किसी खास समारोह में लिखने के लिए है। ऐसा लगता है कि कुछ भाषाओं पुनरावर्ती समाधान के पक्ष में है और उन्हें और अधिक अनुकूलन (लिस्प मन में आता है)।

06/08/2008 को 03:35
का स्रोत उपयोगकर्ता

वोट
7

एक पुनरावर्ती समारोह एक है जो अपने आप कॉल है। सबसे आम कारण है कि मैं इसे एक वृक्ष संरचना traversing है उपयोग करने के लिए मिल गया है। उदाहरण के लिए, अगर मैं चेक बॉक्स के साथ एक TreeView है (लगता है एक नया कार्यक्रम, "स्थापित करने के लिए सुविधाओं का चयन" पृष्ठ की स्थापना), मैं एक "सभी की जाँच करें" बटन जो इस (स्यूडोकोड) की तरह कुछ हो जाएगा चाहते हो सकता है:

function cmdCheckAllClick {
    checkRecursively(TreeView1.RootNode);
}

function checkRecursively(Node n) {
    n.Checked = True;
    foreach ( n.Children as child ) {
        checkRecursively(child);
    }
}

तो आप देख सकते है कि checkRecursively प्रथम नोड जो यह पारित हो जाता है की जाँच करता है, तो उस नोड के बच्चों में से प्रत्येक के लिए खुद को कहता है।

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

मैं एक कारण है कि लोगों को यह प्रयोग नहीं करना चाहिए, जब उचित नहीं सोच सकते हैं। यह कुछ परिस्थितियों में, और दूसरों में नहीं उपयोगी है।

मुझे लगता है कि क्योंकि यह एक दिलचस्प तकनीक है, कुछ कोडर शायद ऊपर, अधिक बार उन्हें करना चाहिए की तुलना में इसे का उपयोग वास्तविक औचित्य के बिना खत्म हो। यह कुछ हलकों में प्रत्यावर्तन एक बुरा नाम दे दिया है।

06/08/2008 को 03:44
का स्रोत उपयोगकर्ता

वोट
28

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

विहित उदाहरण एक नियमित n के क्रमगुणित उत्पन्न करने के लिए है। n के क्रमगुणित 1 और एन के बीच संख्या के सभी गुणा किया जाता है। सी # में एक सतत समाधान इस तरह दिखता है:

public int Fact(int n)
{
  int fact = 1;

  for( int i = 2; i <= n; i++)
  {
    fact = fact * i;
  }

  return fact;
}

वहाँ पुनरावृत्ति समाधान के बारे में आश्चर्य की बात कुछ भी नहीं है और यह किसी के साथ सी # परिचित करने के लिए अर्थपूर्ण होनी चाहिए।

पुनरावर्ती समाधान मान्यता है कि वें क्रमगुणित n * तथ्य है द्वारा पाया जाता है (n-1)। या दूसरे शब्दों में कहें, यदि आप जानते हैं कि एक विशेष भाज्य संख्या आप अगले एक की गणना कर सकते है। यहाँ सी # में पुनरावर्ती समाधान है:

public int FactRec(int n)
{
  if( n < 2 )
  {
    return 1;
  }

  return n * FactRec( n - 1 );
}

इस समारोह के पहले भाग एक के रूप में जाना जाता है बेस प्रकरण (या कभी कभी गार्ड खण्ड) और क्या हमेशा के लिए चलने से एल्गोरिथ्म को रोकता है। यह सिर्फ मान 1 जब भी समारोह 1 या उससे कम की एक मूल्य के साथ कहा जाता है देता है। दूसरे भाग को और अधिक रोचक है और कहा जाता है रिकर्सिव चरण । यहाँ हम एक थोड़ा संशोधित पैरामीटर (हम 1 से यह घटती) के साथ एक ही विधि कॉल और फिर n के हमारे प्रति के साथ परिणाम गुणा।

जब पहली बार सामना करना पड़ा इस भ्रामक तरह का तो यह जांच करने के लिए शिक्षाप्रद है कि यह कैसे काम करता है जब रन हो सकता है। कल्पना कीजिए कि हम FactRec फोन (5)। हम नियमित दर्ज करते हैं, आधार मामला द्वारा उठाया नहीं कर रहे हैं और इसलिए हम इस तरह अंत:

// In FactRec(5)
return 5 * FactRec( 5 - 1 );

// which is
return 5 * FactRec(4);

अगर हम पैरामीटर 4 हम फिर से गार्ड खंड द्वारा बंद कर दिया नहीं कर रहे हैं और इसलिए हम पर अंत के साथ विधि दोबारा डालनी:

// In FactRec(4)
return 4 * FactRec(3);

हम वापसी मान में इस वापसी मान स्थानापन्न यदि उपरोक्त पर हम पाते हैं

// In FactRec(5)
return 5 * (4 * FactRec(3));

यह आप कैसे अंतिम समाधान तो पर पहुंचे है कि हम तेजी से ट्रैक और नीचे रास्ते पर हर कदम दिखाता हूँ के रूप में एक सुराग देना चाहिए:

return 5 * (4 * FactRec(3));
return 5 * (4 * (3 * FactRec(2)));
return 5 * (4 * (3 * (2 * FactRec(1))));
return 5 * (4 * (3 * (2 * (1))));

यही कारण है कि अंतिम प्रतिस्थापन होता है जब आधार मामला शुरू हो रहा है। इस बिंदु पर हम हल करने के लिए जो पहली जगह में factorials की परिभाषा के लिए सीधे बराबर एक सरल algrebraic सूत्र है।

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

06/08/2008 को 03:54
का स्रोत उपयोगकर्ता

वोट
0

मैं उन दोनों के बीच एक विभाजक के साथ स्ट्रिंग की एक सूची को श्रेणीबद्ध करने के लिए एक पुनरावर्ती समारोह बनाया है। मैं यह ज्यादातर का उपयोग एसक्यूएल भाव पैदा करने के लिए, 'के रूप में क्षेत्रों की एक सूची पारित करके आइटम ' और एक ' अल्पविराम + स्पेस ' विभाजक के रूप में। यहाँ समारोह (यह कुछ Borland बिल्डर देशी डेटा प्रकार का उपयोग करता है, लेकिन किसी भी अन्य पर्यावरण फिट करने के लिए अनुकूलित किया जा सकता) है:

String ArrangeString(TStringList* items, int position, String separator)
{
  String result;

  result = items->Strings[position];

  if (position <= items->Count)
    result += separator + ArrangeString(items, position + 1, separator);

  return result;
}

मैं इसे इस तरह कहते हैं:

String columnsList;
columnsList = ArrangeString(columns, 0, ", ");

कल्पना कीजिए कि आप एक सरणी नाम 'है क्षेत्रों इसके अंदर इस डेटा के साथ': ' ALBUMNAME ', ' RELEASEDATE ', ' labelId '। तो फिर तुम फ़ंक्शन को कॉल करें:

ArrangeString(fields, 0, ", ");

समारोह काम करने के लिए शुरू होता है के रूप में, चर ' परिणाम ' सरणी है, जो 'है की स्थिति को 0 में से मूल्य प्राप्त ALBUMNAME '।

तो फिर यह जाँच करता है स्थिति से निपटने के लिए है पिछले एक है अगर। ऐसा नहीं है के रूप में, तो यह विभाजक और एक समारोह है, जो, हे भगवान, यह एक ही समारोह है का परिणाम के साथ परिणाम कोनकैटेनेट्स किया गया। लेकिन इस बार, यह बाहर की जाँच, यह अपने आप में स्थिति के लिए 1 जोड़ने कहते हैं।

ArrangeString(fields, 1, ", ");

यह दोहरा, एक LIFO ढेर बनाने रहता है, जब तक यह एक बिंदु है जहां स्थिति से निपटा जा रहा है कि पिछले एक तक पहुँच जाता है, तो समारोह की सूची पर उस स्थिति पर केवल आइटम देता है, अब और श्रृंखलाबद्ध नहीं। फिर ढेर पीछे की ओर concatenated है।

समझ गया? आप नहीं करते हैं, मैं एक और तरीका यह समझाने के लिए है। : ओ)

06/08/2008 को 04:00
का स्रोत उपयोगकर्ता

वोट
5

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

लोग कई कारणों से के लिए प्रत्यावर्तन से बचने:

  1. के रूप में कार्यात्मक प्रोग्रामिंग करने के लिए विरोध ज्यादातर लोगों (अपने आप को शामिल) प्रक्रियात्मक या वस्तु उन्मुख प्रोग्रामिंग पर अपने प्रोग्रामिंग दांत काट। इस तरह के लोगों के लिए, पुनरावृत्ति दृष्टिकोण (आमतौर पर छोरों का उपयोग करके) और अधिक प्राकृतिक महसूस करता है।

  2. हममें से वे जो प्रक्रियात्मक या ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग पर हमारी प्रोग्रामिंग दांत काट अक्सर प्रत्यावर्तन से बचने के लिए, क्योंकि यह त्रुटियों की संभावना है बताया गया है।

  3. हम अक्सर बताया कि प्रत्यावर्तन धीमी है। कॉलिंग और एक नियमित से लौटने के बार-बार ढेर धक्का और पॉपिंग, जो पाशन की तुलना में धीमी है की एक बहुत कुछ शामिल है। मुझे लगता है कि कुछ भाषाओं दूसरों से बेहतर संभाल, और उन भाषाओं सबसे अधिक संभावना उन जहां प्रभावी प्रतिमान प्रक्रियात्मक या वस्तु उन्मुख है नहीं कर रहे हैं।

  4. प्रोग्रामिंग भाषाओं मैं का उपयोग किया है की कम से कम एक जोड़ी के लिए, मैं सुनवाई सिफारिशों क्योंकि इसके ढेर है कि गहरे नहीं है अगर यह एक निश्चित गहराई से परे हो जाता है प्रत्यावर्तन उपयोग करने के लिए नहीं याद है।

06/08/2008 को 04:12
का स्रोत उपयोगकर्ता

वोट
5

यहाँ एक सरल उदाहरण है: एक सेट में कितने तत्वों। (वहाँ बेहतर तरीके बातें गिनती करने के लिए हैं, लेकिन यह एक अच्छा सरल पुनरावर्ती उदाहरण है।)

सबसे पहले, हम दो नियमों की जरूरत है:

  1. अगर सेट खाली है, सेट में आइटम की गणना शून्य (ओह!) है।
  2. अगर सेट खाली नहीं है, गिनती एक से अधिक सेट के बाद एक आइटम निकाल दिया जाता है में आइटमों की संख्या है।

मान लीजिए आप इस तरह एक सेट है: [XXX]। के गिनती देखते हैं कि कितने आइटम हैं।

  1. सेट है [XXX] जो खाली नहीं है, तो हम शासन 2. लागू मदों की संख्या एक से अधिक में आइटमों की संख्या है [xx] (यानी हम एक आइटम निकाल)।
  2. सेट [xx] है, तो हम फिर से नियम 2 लागू होते हैं: [x] की एक वस्तु के + संख्या।
  3. [] की एक वस्तु के + संख्या: सेट [x], जो अभी भी नियम 2 से मेल खाता है।
  4. अब सेट [], जो नियम 1 से मेल खाता है: गिनती शून्य है!
  5. हम चरण 4 में जवाब पता अब जब कि (0), हम हल कर सकते हैं चरण 3 (1 + 0)
  6. इसी तरह, अब हम चरण 3 में जवाब पता है कि (1), हम हल कर सकते हैं चरण 2 (1 + 1)
  7. और अंत में अब है कि हम (2) चरण 2 में जवाब पता है, हम (+2 1) चरण 1 हल कर सकते हैं और में मदों की संख्या प्राप्त [XXX], जो 3. हुर्रे है!

हम के रूप में इस का प्रतिनिधित्व कर सकते हैं:

count of [x x x] = 1 + count of [x x]
                 = 1 + (1 + count of [x])
                 = 1 + (1 + (1 + count of []))
                 = 1 + (1 + (1 + 0)))
                 = 1 + (1 + (1))
                 = 1 + (2)
                 = 3

जब एक पुनरावर्ती समाधान को लागू करने, आप आमतौर पर कम से कम 2 नियम है:

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

अगर हम स्यूडोकोड करने के लिए ऊपर का अनुवाद, हम पाते हैं:

numberOfItems(set)
    if set is empty
        return 0
    else
        remove 1 item from set
        return 1 + numberOfItems(set)

वहाँ एक बहुत अधिक उपयोगी उदाहरण (एक पेड़ traversing, उदाहरण के लिए), जो मुझे यकीन है कि अन्य लोगों को कवर किया जाएगा हूँ।

06/08/2008 को 04:12
का स्रोत उपयोगकर्ता

वोट
0

मैं प्रत्यावर्तन का उपयोग करें। क्या है कि (, जो मैं नहीं माध्यम से) एक सीएस डिग्री ... होने के साथ क्या संबंध है

सामान्य उपयोग मैं पाया है:

  1. साइटमैप - दस्तावेज़ जड़ से शुरू फाइल सिस्टम के माध्यम से recurse
  2. मकड़ियों - एक वेबसाइट के माध्यम से रेंगने ईमेल पता, लिंक, आदि को खोजने के लिए
  3. ?
06/08/2008 को 04:13
का स्रोत उपयोगकर्ता

वोट
1

मारियो, मुझे समझ नहीं आता क्यों आपको लगता है कि उदाहरण के लिए प्रत्यावर्तन इस्तेमाल किया .. बस प्रत्येक प्रविष्टि के माध्यम से लूप क्यों नहीं? कुछ इस तरह:

String ArrangeString(TStringList* items, String separator)
{
    String result = items->Strings[0];

    for (int position=1; position < items->count; position++) {
        result += separator + items->Strings[position];
    }

    return result;
}

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

06/08/2008 को 04:19
का स्रोत उपयोगकर्ता

वोट
3

एक हल समस्या पर recurse करने के लिए: कुछ भी नहीं, बस हो गया।
एक खुला समस्या पर recurse करने के लिए: अगले कदम के लिए करते हैं, तो बाकी पर recurse।

06/08/2008 को 04:32
का स्रोत उपयोगकर्ता

वोट
86

का अच्छा स्पष्टीकरण की एक संख्या हैं प्रत्यावर्तन इस सूत्र में, इस सवाल का जवाब (तुम क्यों यह सबसे अधिक भाषाओं में उपयोग नहीं करना चाहिए के बारे में है। * प्रमुख अनिवार्य भाषा कार्यान्वयन के बहुमत में यानी सी, सी ++, बेसिक, अजगर के हर प्रमुख कार्यान्वयन , रूबी, जावा, और सी #) यात्रा बेहद प्रत्यावर्तन करना बेहतर है।

क्यों देखने के लिए, चरणों के बारे में है कि ऊपर भाषाओं में एक समारोह कॉल करने के लिए उपयोग करें:

  1. अंतरिक्ष पर बना ली है ढेर समारोह के तर्कों और स्थानीय चर के लिए
  2. समारोह के तर्कों इस नए अंतरिक्ष में कॉपी कर रहे हैं
  3. नियंत्रण कार्य करने के लिए कूदता है
  4. समारोह के कोड रन
  5. समारोह के परिणाम एक वापसी मान में बनाई जाए
  6. ढेर अपने पिछले स्थिति के लिए rewound है
  7. नियंत्रण जहां समारोह बुलाया गया था वापस करने के लिए कूदता है

इन सभी चरणों को करने से समय लगता है, आमतौर पर थोड़ा और अधिक की तुलना में यह एक पाश के माध्यम से पुनरावृति करने के लिए ले जाता है। हालांकि, वास्तविक समस्या चरण # 1 में है। जब कई कार्यक्रमों शुरू करते हैं, वे अपने ढेर के लिए स्मृति का एक भी हिस्सा आवंटित है, और जब वे कहते हैं कि स्मृति से बाहर चलाने (अक्सर, लेकिन हमेशा नहीं प्रत्यावर्तन के कारण), कार्यक्रम एक की वजह से दुर्घटनाओं ढेर अतिप्रवाह

तो इन भाषाओं में प्रत्याव