वहाँ कुछ भी है कि पुनरावृत्ति के साथ किया जा सकता है कि छोरों के साथ नहीं किया जा सकता है?


126

ऐसे समय होते हैं जब लूप का उपयोग करने की तुलना में रिकर्सन का उपयोग करना बेहतर होता है, और ऐसे समय में जहां लूप का उपयोग करना पुनरावर्तन का उपयोग करने से बेहतर होता है। "सही" चुनने से कोड की कम लाइनों में संसाधनों और / या परिणाम को बचाया जा सकता है।

क्या ऐसे कोई मामले हैं जहां एक कार्य केवल लूप के बजाय पुनरावृत्ति का उपयोग करके किया जा सकता है?


13
मुझे गंभीरता से संदेह है। पुनरावृत्ति एक गौरवशाली पाश है।
को ऑर्बिट

6
डायवर्जिंग दिशाओं को देखकर, जिसमें उत्तर चलते हैं (और एक बेहतर प्रदान करने में खुद को विफल कर दिया है) क्या आप किसी को पक्ष में जवाब देने की कोशिश कर सकते हैं यदि आप थोड़ी अधिक पृष्ठभूमि प्रदान करते हैं और आप किस तरह के उत्तर के बाद हैं। क्या आप काल्पनिक मशीनों (असीमित भंडारण और रन-टाइम के साथ) के लिए एक सैद्धांतिक प्रमाण चाहते हैं? या व्यावहारिक उदाहरण? (जहाँ मैं हास्यास्पद रूप से जटिल हो सकता हूँ "के रूप में योग्य हो सकता है" नहीं किया जा सकता है "।) या कुछ अलग?
5gon12eder

8
@LightnessRacesinOrbit मेरे गैर-देशी-अंग्रेजी बोलने वाले कान के लिए, "पुनरावृत्ति एक शानदार लूप है" आपको लगता है कि "आप कहीं भी पुनरावर्ती कॉल के बजाय एक लूपिंग निर्माण का उपयोग कर सकते हैं, और अवधारणा वास्तव में खुद का नाम लायक है" । शायद मैं "महिमा कुछ" मुहावरे की गलत व्याख्या करता हूं।
hyde

13
एकरमन समारोह के बारे में क्या? en.wikipedia.org/wiki/Ackermann_function , लूपिंग के माध्यम से विशेष रूप से उपयोगी नहीं बल्कि असंभव है। (आप इस वीडियो को youtube.com/watch?v=i7sm9dzFtEI कंप्यूटरफाइल से जांचना भी चाह सकते हैं )
WizardOfMenlo

8
@WizardOfMenlo befunge कोड ERRE समाधान का कार्यान्वयन है (जो एक संवादात्मक समाधान भी है ... एक स्टैक के साथ)। एक ढेर के साथ एक पुनरावृत्ति दृष्टिकोण एक पुनरावर्ती कॉल का अनुकरण कर सकता है। उपयुक्त रूप से शक्तिशाली किसी भी प्रोग्रामिंग पर, एक लूपिंग निर्माण का उपयोग दूसरे को अनुकरण करने के लिए किया जा सकता है। निर्देशों के साथ रजिस्टर मशीन INC (r), JZDEC (r, z)ट्यूरिंग मशीन को लागू कर सकती है। यह कोई 'पुनरावृत्ति' नहीं है - यदि एक और शून्य है तो एक छलांग है। यदि एकरमैन फ़ंक्शन कम्प्यूटेबल है (यह है), तो वह रजिस्टर मशीन कर सकती है।

जवाबों:


164

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

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

अक्सर, किसी समस्या का पुनरावर्ती समाधान प्रीटियर होता है। यह एक तकनीकी शब्द है, और यह मायने रखता है।


120
मूल रूप से, पुनरावृत्ति के बजाय लूप करने का मतलब मैन्युअल रूप से स्टैक को संभालना है।
सिलविउ बर्किया

15
... ढेर (ओं) । निम्नलिखित स्थिति दृढ़ता से एक से अधिक ढेर होने को प्राथमिकता दे सकती है। एक पुनरावर्ती कार्य पर विचार करें जो Aएक पेड़ में कुछ पाता है। हर Aबार उस चीज़ का सामना होता है, यह एक और पुनरावर्ती फ़ंक्शन लॉन्च करता है Bजो उस स्थिति में सबट्री में संबंधित चीज़ पाता है जहां इसे लॉन्च किया गया था A। एक बार जब Bयह पुनरावृत्ति को पूरा कर लेता है A, और बाद में अपनी खुद की पुनरावृत्ति जारी रखता है। एक के लिए एक ढेर घोषित कर सकता है Aऔर के लिए एक B, या डाल Bअंदर ढेर Aपाश। यदि कोई एकल स्टैक का उपयोग करने पर जोर देता है, तो चीजें वास्तव में जटिल हो जाती हैं।
रॉन्ग

35
Therefore, the one thing recursion can do that loops can't is make some tasks super easy. और एक चीज जो लूप्स कर सकती है वह यह है कि रिकर्सन कुछ कार्यों को सुपर आसान नहीं बना सकता है। क्या आपने कुरूप, अनैच्छिक चीजों को देखा है जो आपको सबसे स्वाभाविक रूप से पुनरावृत्ति की समस्याओं को भोली पुनरावृत्ति से पूंछ पुनरावृत्ति में बदलने के लिए करना है ताकि वे स्टैक को उड़ा न दें?
मेसन व्हीलर

10
@MasonWheeler 99% समय उन "चीजों" को एक पुनरावृत्ति-संचालक के अंदर mapया fold( जैसे यदि आप उन्हें आदिम पर विचार करने के लिए चुनते हैं, तो बेहतर रूप से समझाया जा सकता है , मुझे लगता है कि आप एक तीसरे विकल्प के रूप में उपयोग कर सकते हैं fold/ unfoldलोशन या पुनरावृत्ति के लिए तीसरे विकल्प के रूप में )। जब तक आप लाइब्रेरी कोड नहीं लिख रहे हैं, तब तक ऐसे कई मामले नहीं हैं जहाँ आपको पुनरावृत्ति के कार्यान्वयन के बारे में चिंता करनी चाहिए, बजाय इसके कि यह कार्य पूरा होने वाला है - व्यवहार में, इसका मतलब है कि स्पष्ट लूप और स्पष्ट पुनरावृत्ति दोनों समान रूप से खराब हैं सार जो शीर्ष स्तर पर बचा जाना चाहिए।
लेउशेंको

7
आप दो तारों की पुनरावृत्ति तुलनात्मक रूप से तुलना करके कर सकते हैं, लेकिन बस प्रत्येक चरित्र की तुलना, एक-एक करके, जब तक आप एक बेमेल न हो जाएं बेहतर प्रदर्शन करने और पाठक के लिए अधिक स्पष्ट होने के लिए उपयुक्त है।
स्टीवन बर्नैप

78

नहीं।

गणना करने के लिए आवश्यक न्यूनतम स्थानों की बहुत मूल बातें करने के लिए, आपको बस लूप करने में सक्षम होना चाहिए (यह अकेला पर्याप्त नहीं है, बल्कि एक आवश्यक घटक है)। इससे कोई फर्क नहीं पड़ता कि कैसे

कोई भी प्रोग्रामिंग भाषा जो ट्यूरिंग मशीन को लागू कर सकती है, ट्यूरिंग पूर्ण कहलाती है । और बहुत सारी भाषाएं हैं जो पूरी तरह से ट्यूरिंग हैं।

वहाँ के रास्ते में मेरी पसंदीदा भाषा "जो वास्तव में काम करती है?" ट्यूरिंग संपूर्णता FRACTRAN की है , जो कि ट्यूरिंग पूर्ण है । इसमें एक लूप संरचना है, और आप इसमें ट्यूरिंग मशीन को लागू कर सकते हैं। इस प्रकार, जो कुछ भी कम्प्यूटेशनल है, उसे ऐसी भाषा में लागू किया जा सकता है जिसमें पुनरावृत्ति न हो। इसलिए, कुछ भी नहीं है कि पुनरावर्तन आपको कम्प्यूटेबिलिटी के संदर्भ में दे सकता है जो सरल लूपिंग नहीं कर सकता है।

यह वास्तव में कुछ बिंदुओं को उबालता है:

  • ट्यूरिंग मशीन पर जो कुछ भी कंप्‍यूटेबल है वह कंप्‍यूटेबल है
  • कोई भी भाषा जो ट्यूरिंग मशीन को लागू कर सकती है (जिसे ट्यूरिंग पूर्ण कहा जाता है), किसी भी अन्य भाषा की गणना कर सकती है
  • चूँकि ऐसी भाषाओं में ट्यूरिंग मशीनें होती हैं जिनमें पुनरावर्तन का अभाव होता है (और कुछ अन्य हैं जो केवल पुनरावृत्ति होती है जब आप कुछ अन्य एसोलैंग्स में आते हैं), यह आवश्यक रूप से सच है कि कुछ भी नहीं है जो आप पुनरावृत्ति के साथ कर सकते हैं जो आप के साथ कर सकते हैं लूप (और आप लूप के साथ कुछ भी नहीं कर सकते हैं जो आप रिकर्सन के साथ नहीं कर सकते हैं)।

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

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


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

यदि आपके पास है, तो कहने के लिए, एक गुट विधि के रूप में लिखा है:

int fact(int n) {
    return fact(n, 1);
}

int fact(int n, int accum) {
    if(n == 0) { return 1; }
    if(n == 1) { return accum; }
    return fact(n-1, n * accum);
}

इस प्रकार की पुनरावृत्ति को लूप के रूप में फिर से लिखा जाएगा - कोई स्टैक का उपयोग नहीं किया गया। इस तरह के दृष्टिकोण वास्तव में अक्सर लिखे गए लूप की तुलना में अधिक सुरुचिपूर्ण और समझने में आसान होते हैं, लेकिन फिर से, प्रत्येक पुनरावर्ती कॉल के लिए एक समान लूप लिखा जा सकता है और प्रत्येक लूप के लिए एक पुनरावर्ती कॉल लिखा जा सकता है।

कई बार ऐसे भी होते हैं जहां साधारण लूप को टेल कॉल में बदलना पुनरावर्ती कॉल को जटिल और समझने में अधिक कठिन हो सकता है।


यदि आप इसके सिद्धांत पक्ष में आना चाहते हैं, तो चर्च ट्यूरिंग थीसिस देखें । उपयोगी होने के लिए आप CS.SE पर चर्च-ट्यूरिंग-थीसिस भी पा सकते हैं ।


29
ट्यूरिंग संपूर्णता को बहुत अधिक फेंक दिया जाता है जैसे यह मायने रखता है। बहुत सारी चीज़ें ट्यूरिंग कम्प्लीट हैं ( जैसे मैजिक द गैदरिंग ), लेकिन इसका मतलब यह नहीं है कि ट्यूरिंग कम्प्लीट कुछ और ही है। कम से कम एक स्तर पर नहीं जो मायने रखता है। मैं मैजिक द गैदरिंग के साथ एक पेड़ नहीं चलना चाहता।
Scant रोजर

7
एक बार जब आप एक समस्या को कम कर सकते हैं "यह एक ट्यूरिंग मशीन के बराबर शक्ति है" यह वहाँ पाने के लिए पर्याप्त है। ट्यूरिंग मशीन एक कम बाधा है, लेकिन यह सब है कि जरूरत है। कुछ भी नहीं है एक लूप कर सकते हैं कि पुनरावृत्ति नहीं कर सकता है, न ही इसके विपरीत।

4
इस उत्तर में दिया गया कथन निश्चित रूप से सही है, लेकिन मैंने यह कहने का साहस किया कि तर्क वास्तव में ठोस नहीं है। ट्यूरिंग मशीनों में पुनरावृत्ति की कोई प्रत्यक्ष अवधारणा नहीं है, इसलिए "आप पुनरावृत्ति के बिना ट्यूरिंग मशीन का अनुकरण कर सकते हैं" वास्तव में कुछ भी साबित नहीं करता है। कथन को साबित करने के लिए आपको जो दिखाना होगा, वह यह है कि ट्यूरिंग मशीनें पुनरावृत्ति का अनुकरण कर सकती हैं। यदि आप यह नहीं दिखाते हैं, तो आपको विश्वास करना होगा कि चर्च-ट्यूरिंग परिकल्पना भी पुनरावृत्ति (जो यह करती है) के लिए रखती है, लेकिन ओपी ने इस पर सवाल उठाया।
5gon12eder

10
ओपी का प्रश्न "कैन" है, न कि "सर्वश्रेष्ठ", या "सबसे कुशलतापूर्वक" या कुछ अन्य क्वालिफायर। "ट्यूरिंग कम्प्लीट" का अर्थ है कि जो कुछ भी किया जा सकता है वह पुनरावृत्ति के साथ भी किया जा सकता है। किसी विशेष भाषा के कार्यान्वयन में इसे करने का सबसे अच्छा तरीका है या नहीं यह एक पूरी तरह से अलग सवाल है।
स्टीवन बर्नैप

7
"कैन" बहुत अधिक नहीं है जो "सर्वश्रेष्ठ" के समान है। जब आप "नहीं" के लिए "सबसे अच्छा नहीं" गलती करते हैं, तो आप पंगु हो जाते हैं क्योंकि कोई फर्क नहीं पड़ता कि आप किस तरह से कुछ करते हैं, लगभग हमेशा एक बेहतर तरीका होता है।
स्टीवन बर्नैप

31

क्या ऐसे कोई मामले हैं जहां एक कार्य केवल लूप के बजाय पुनरावृत्ति का उपयोग करके किया जा सकता है?

आप हमेशा पुनरावर्ती एल्गोरिदम को एक लूप में बदल सकते हैं, जो अस्थायी स्थिति को संग्रहीत करने के लिए एक अंतिम-इन-प्रथम-आउट डेटा संरचना (AKA स्टैक) का उपयोग करता है, क्योंकि पुनरावर्ती कॉल ठीक यही है, एक स्टैक में वर्तमान स्थिति को संग्रहीत करना, एल्गोरिथ्म के साथ आगे बढ़ना। फिर बाद में राज्य को बहाल करना। तो संक्षिप्त जवाब है: नहीं, ऐसे कोई मामले नहीं हैं

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

क्या यह पुनरावृत्ति है, यदि आप पुनरावर्तन को स्वयं लागू करते हैं, तो अलग "पुश स्थिति" और "जंप टू शुरुआत" और "पॉप स्टेट" चरणों के रूप में? और इसका उत्तर है: नहीं, इसे अभी भी पुनरावृत्ति नहीं कहा जाता है, इसे स्पष्ट स्टैक के साथ पुनरावृत्ति कहा जाता है (यदि आप स्थापित शब्दावली का उपयोग करना चाहते हैं)।


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

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


2
स्टैकलेस प्रोसेसर के लिए असेंबली के साथ काम करते समय, ये दोनों तकनीक अचानक एक हो जाती हैं।
जोशुआ

@ जोशुआ वास्तव में! यह अमूर्तन के स्तर की बात है। यदि आप एक स्तर या दो निचले पर जाते हैं, तो यह सिर्फ तर्क द्वार है,।
हाइड

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

@ पुरानी पोस्ट, लेकिन रैंडम एक्सेस स्टैक की आवश्यकता क्यों है? इसके अलावा, सभी वास्तविक कंप्यूटर नहीं हैं, तो पीडीएएस से भी कम, क्योंकि उनके पास केवल सीधे पहुंच योग्य स्मृति की सीमित मात्रा है, और उस पर कोई स्टैक नहीं है (उस मेमोरी का उपयोग करके)? यह बहुत व्यावहारिक अमूर्त नहीं लगता है, अगर यह कहता है कि "हम वास्तविकता में पुनरावृत्ति नहीं कर सकते हैं"।
हाइड

20

यह निर्भर करता है कि आप कितनी सख्ती से "पुनरावृत्ति" को परिभाषित करते हैं।

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

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

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  if (m == 0)
    return  n+1;
  if (n == 0)
    return Ackermann(m - 1, 1);
  else
    return Ackermann(m - 1, Ackermann(m, n - 1));
}

पहला पुनरावर्ती कॉल पुनरावृत्त बनाना आसान है:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
  if (m == 0)
    return  n+1;
  if (n == 0)
  {
    m--;
    n = 1;
    goto restart;
  }
  else
    return Ackermann(m - 1, Ackermann(m, n - 1));
}

हम फिर से सफाई कर सकते हैं दूर करने के gotoलिए दूर करने के लिए velociraptors और Dijkstra की छाया:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  while(m != 0)
  {
    if (n == 0)
    {
      m--;
      n = 1;
    }
    else
      return Ackermann(m - 1, Ackermann(m, n - 1));
  }
  return  n+1;
}

लेकिन अन्य पुनरावर्ती कॉल को हटाने के लिए हमें कुछ कॉल के मूल्यों को एक स्टैक में संग्रहीत करना होगा:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  Stack<BigInteger> stack = new Stack<BigInteger>();
  stack.Push(m);
  while(stack.Count != 0)
  {
    m = stack.Pop();
    if(m == 0)
      n = n + 1;
    else if(n == 0)
    {
      stack.Push(m - 1);
      n = 1;
    }
    else
    {
      stack.Push(m - 1);
      stack.Push(m);
      --n;
    }
  }
  return n;
}

अब, जब हम स्रोत कोड पर विचार करते हैं, तो हमने निश्चित रूप से अपनी पुनरावर्ती पद्धति को पुनरावृति में बदल दिया है।

यह देखते हुए कि यह किसके लिए संकलित किया गया है, हमने कोड को बदल दिया है जो कॉल स्टैक का उपयोग कोड में पुनरावृत्ति को लागू करने के लिए करता है (और ऐसा नहीं किया गया कोड ऐसा करने के लिए जो कोड में बहुत छोटे मानों के लिए स्टैक-ओवरफ़्लो अपवाद को फेंक देगा जो केवल होगा वापसी के लिए एक लंबे समय तक ले लो [देखें कि मैं अपने एकरमैन फ़ंक्शन को स्टैक के अतिप्रवाह से कैसे रोक सकता हूं? कुछ और अनुकूलन के लिए जो इसे वास्तव में कई और संभावित इनपुटों के लिए वापस लाते हैं])।

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

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

बेशक, यह सब आपके पास एक विकल्प है। दोनों भाषाएँ हैं जो पुनरावर्ती कॉलों को रोकती हैं, और ऐसी भाषाएँ जिनमें पुनरावृत्ति के लिए आवश्यक लूपिंग संरचनाओं का अभाव है।


कॉल स्टैक को किसी समकक्ष के साथ बदलना केवल तभी संभव है जब कॉल स्टैक बाध्य हो या कॉल स्टैक के बाहर एक अनबाउंड मेमोरी तक पहुंच हो। समस्याओं का एक महत्वपूर्ण वर्ग है जो पुश-डाउन ऑटोमेटा द्वारा हल किया जाता है जिसमें असीमित कॉल स्टैक होता है, लेकिन केवल राज्यों की सीमित संख्या हो सकती है।
सुपरकैट

यह सबसे अच्छा उत्तर है, शायद एकमात्र सही उत्तर है। यहां तक ​​कि दूसरा उदाहरण अभी भी पुनरावर्ती है, और इस स्तर पर, मूल प्रश्न का उत्तर नहीं है । पुनरावृत्ति की एक व्यापक परिभाषा के साथ, एकरमैन फ़ंक्शन के लिए पुनरावृत्ति से बचना असंभव है।
गेरिट

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

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

9

शास्त्रीय उत्तर "नहीं" है, लेकिन मुझे इस बारे में विस्तृत जानकारी देने की अनुमति दें कि मुझे "हां" बेहतर उत्तर क्यों लगता है।


आगे बढ़ने से पहले, आइए एक संगणना और जटिलता के दृष्टिकोण से कुछ हटकर करें:

  • उत्तर "नहीं" है यदि आपको लूपिंग के दौरान एक सहायक स्टैक की अनुमति है।
  • यदि आपको लूपिंग के दौरान अतिरिक्त डेटा की अनुमति नहीं है तो इसका उत्तर "हां" है।

ठीक है, अब, एक पैर को अभ्यास-भूमि में डालते हैं, दूसरे पैर को सिद्धांत-भूमि में रखते हैं।


कॉल स्टैक एक नियंत्रण संरचना है, जबकि एक मैनुअल स्टैक एक डेटा संरचना है। नियंत्रण और डेटा समान अवधारणाएं नहीं हैं , लेकिन वे इस अर्थ में बराबर हैं कि वे एक दूसरे के लिए कम हो सकते हैं (या एक दूसरे के माध्यम से "उत्सर्जित") एक संगणनीयता या जटिलता के दृष्टिकोण से।

यह भेद कब हो सकता है? जब आप वास्तविक दुनिया के उपकरणों के साथ काम कर रहे हों। यहाँ एक उदाहरण है:

कहते हैं कि आप एन-वे लागू कर रहे हैं mergesort। आपके पास एक forलूप हो सकता है जो प्रत्येक Nसेगमेंट से गुजरता है, mergesortउन पर अलग से कॉल करता है, फिर परिणामों को मर्ज करता है।

आप इसे OpenMP के साथ कैसे समानांतर कर सकते हैं?

पुनरावर्ती क्षेत्र में, यह बेहद सरल है: बस #pragma omp parallel forअपने लूप के चारों ओर रखो जो 1 से एन तक जाता है, और आप कर रहे हैं। पुनरावृत्त क्षेत्र में, आप ऐसा नहीं कर सकते। आपको थ्रेड्स को मैन्युअल रूप से फैलाना होगा और उन्हें उपयुक्त डेटा मैन्युअल रूप से पास करना होगा ताकि वे जान सकें कि क्या करना है।

दूसरी ओर, अन्य उपकरण हैं (जैसे कि स्वचालित वेक्टराइज़र, जैसे #pragma vector) जो लूप के साथ काम करते हैं लेकिन पुनरावृत्ति के साथ पूरी तरह से बेकार हैं।

बिंदु जा रहा है, सिर्फ इसलिए कि आप साबित कर सकते हैं कि दो प्रतिमान गणितीय रूप से समान हैं, इसका मतलब यह नहीं है कि वे अभ्यास में समान हैं। एक समस्या जो एक प्रतिमान में स्वचालित होने के लिए तुच्छ हो सकती है (जैसे, लूप समानांतरकरण) दूसरे प्रतिमान में हल करने के लिए अधिक कठिन हो सकती है।

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

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


इससे परे भी, इस बात पर विचार करें कि पुश-डाउन ऑटोमैटन के साथ हल की जा सकने वाली समस्याओं का सेट उस सेट से बड़ा होता है जिसे एक परिमित ऑटोमेटन (चाहे नियतात्मक या गैर) के साथ हल किया जा सकता है, लेकिन उस सेट से छोटा होता है जिसे हल किया जा सकता है। ट्यूरिंग मशीन।
सुपरकैट

8

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

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

संक्षेप में:

  • पुनरावर्तन का मामला = नियंत्रण प्रवाह + ढेर (+ ढेर)
  • लूप केस = नियंत्रण प्रवाह + ढेर

यह मानते हुए कि नियंत्रण प्रवाह भाग यथोचित शक्तिशाली है, एकमात्र अंतर उपलब्ध मेमोरी प्रकारों में है। इसलिए, हम 4 मामलों से बचे हुए हैं (व्यक्त शक्ति को कोष्ठक में सूचीबद्ध किया गया है):

  1. कोई ढेर, कोई ढेर नहीं: पुनरावृत्ति और गतिशील संरचनाएं असंभव हैं। (पुनरावृत्ति = पाश)
  2. ढेर, कोई ढेर नहीं: पुनरावृत्ति ठीक है, गतिशील संरचनाएं असंभव हैं। (पुनरावृत्ति> पाश)
  3. कोई ढेर, ढेर: पुनरावृत्ति असंभव है, गतिशील संरचनाएं ठीक हैं। (पुनरावृत्ति = पाश)
  4. ढेर, ढेर: पुनरावृत्ति और गतिशील संरचनाएं ठीक हैं। (पुनरावृत्ति = पाश)

यदि खेल के नियम थोड़े कड़े हैं और लूप का उपयोग करने के लिए पुनरावर्ती कार्यान्वयन को रोक दिया गया है, तो हम इसके बजाय इसे प्राप्त करते हैं:

  1. कोई ढेर, कोई ढेर नहीं: पुनरावृत्ति और गतिशील संरचनाएं असंभव हैं। (पुनरावृत्ति <पाश)
  2. ढेर, कोई ढेर नहीं: पुनरावृत्ति ठीक है, गतिशील संरचनाएं असंभव हैं। (पुनरावृत्ति> पाश)
  3. कोई ढेर, ढेर: पुनरावृत्ति असंभव है, गतिशील संरचनाएं ठीक हैं। (पुनरावृत्ति <पाश)
  4. ढेर, ढेर: पुनरावृत्ति और गतिशील संरचनाएं ठीक हैं। (पुनरावृत्ति = पाश)

पिछले परिदृश्य के साथ महत्वपूर्ण अंतर यह है कि स्टैक मेमोरी की कमी लूप्स के बिना पुनरावृत्ति की अनुमति नहीं देती है क्योंकि निष्पादन की प्रक्रिया के दौरान अधिक चरणों को करने के लिए कोड की लाइनें होती हैं।


2

हाँ। कई सामान्य कार्य हैं जो पुनरावृत्ति का उपयोग करना आसान है, लेकिन सिर्फ छोरों के साथ असंभव है:

  • जिससे स्टैक ओवरफ्लो हो गया।
  • पूरी तरह से भ्रमित करने वाले शुरुआती प्रोग्रामर।
  • तेजी से दिखने वाले कार्यों का निर्माण करना जो वास्तव में O (n ^ n) हैं।

3
कृपया, ये लूप्स के साथ वास्तव में आसान हैं, मैं उन्हें हर समय देखता हूं। हेक, थोड़े प्रयास के साथ आपको लूप की भी आवश्यकता नहीं है। भले ही पुनरावृत्ति आसान हो।
AviD

1
वास्तव में, ए (0, एन) = एन + 1; A (m, 0) = A (m-1,1) यदि m> 0; A (m, n) = A (m-1, A (m, n-1)) यदि m> 0, n> 0 O (n ^ n) (m = n के लिए) :) की तुलना में थोड़ा अधिक बढ़ता है
जॉन डॉन

1
@JohnDonn एक बिट से अधिक, यह सुपर घातीय है। n = 3 n ^ n ^ n के लिए n = 4 n ^ n ^ n ^ n ^ n और इतने पर। n से n पावर n समय।
हारून मैकमिलिन

1

पुनरावर्ती कार्यों और आदिम पुनरावर्ती कार्यों के बीच अंतर है। आदिम पुनरावर्ती कार्य वे हैं जो लूप का उपयोग करके गणना की जाती है, जहां लूप निष्पादन शुरू होने से पहले प्रत्येक लूप की अधिकतम पुनरावृत्ति गणना की जाती है। (और "पुनरावर्ती" का यहाँ पुनरावर्तन के उपयोग से कोई लेना-देना नहीं है)।

आदिम पुनरावर्ती कार्य पुनरावर्ती कार्यों की तुलना में कड़ाई से कम शक्तिशाली होते हैं। यदि आप पुनरावर्तन का उपयोग करने वाले कार्य करते हैं, तो आपको वही परिणाम मिलेगा, जहां पुनरावृत्ति की अधिकतम गहराई की गणना पहले से की जानी है।


3
मुझे यकीन नहीं है कि यह ऊपर दिए गए प्रश्न पर कैसे लागू होता है? क्या आप कृपया उस कनेक्शन को और अधिक स्पष्ट कर सकते हैं?
Yakk

1
"लूप" के बीच महत्वपूर्ण अंतर के साथ अभेद्य "लूप" को "सीमित पुनरावृत्ति गणना के साथ लूप" और "अनलिमिटेड पुनरावृत्ति गिनती के साथ लूप" की जगह दें, जो मुझे लगा कि हर कोई सीएस 101 से जानता होगा।
gnasher729

यकीन है, लेकिन यह अभी भी सवाल पर लागू नहीं होता है। प्रश्न लूपिंग और पुनरावृत्ति के बारे में है, न कि आदिम पुनरावृत्ति और पुनरावृत्ति के बारे में। सोचिए अगर किसी ने C / C ++ अंतर के बारे में पूछा, और आपने K & R C और Ansi C. Sure के बीच अंतर के बारे में उत्तर दिया, जो चीजों को अधिक सटीक बनाता है, लेकिन यह प्रश्न का उत्तर नहीं देता है।
याक

1

यदि आप c ++ में प्रोग्रामिंग कर रहे हैं, और c ++ 11 का उपयोग करते हैं, तो एक चीज है जो पुनरावर्ती का उपयोग करके किया जाना है: बाधा कार्य। लेकिन मानक इसे 512 तक सीमित करता है, जैसा कि इस उत्तर में बताया गया है । इस मामले में लूप का उपयोग करना संभव नहीं है, क्योंकि उस स्थिति में फ़ंक्शन को कब्ज नहीं किया जा सकता है, लेकिन इसे c ++ 14 में बदल दिया जाता है।


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

ट्री वॉकिंग में कई पुनरावर्ती कॉल (प्रत्येक बच्चे के लिए एक) हैं, फिर भी यह स्पष्ट रूप से एक स्पष्ट स्टैक का उपयोग करके लूप में बदल जाता है। दूसरी ओर पार्सर अक्सर रूपांतरित होने के लिए परेशान होते हैं।
कोडइन्चोसोस

@CodesInChaos संपादित किया गया।
गुलशन

-6

मैं अन्य सवालों से सहमत हूं। कुछ भी नहीं है जो आप पुनरावृत्ति के साथ कर सकते हैं जो आप लूप के साथ नहीं कर सकते हैं।

लेकिन , मेरी राय में पुनरावृत्ति बहुत खतरनाक हो सकती है। सबसे पहले, कुछ और अधिक कठिन यह समझने के लिए कि वास्तव में कोड में क्या हो रहा है। दूसरा, कम से कम C ++ (जावा मैं निश्चित नहीं हूं) के लिए प्रत्येक रिकर्सन स्टेप का मेमोरी पर प्रभाव पड़ता है क्योंकि प्रत्येक मेथड कॉल में मेमोरी संचय और विधियों के हेडर के आरंभ का कारण बनता है। इस तरह आप अपने ढेर को उड़ा सकते हैं। बस एक उच्च इनपुट मूल्य के साथ फाइबोनैचि संख्याओं की पुनरावृत्ति का प्रयास करें।


2
रिबॉक्शन के साथ फाइबोनैचि संख्याओं का एक भोली पुनरावर्ती कार्यान्वयन स्टैक स्पेस से बाहर चलने से पहले "समय से बाहर" चलेगा। मुझे लगता है कि अन्य समस्याएं हैं जो इस उदाहरण के लिए बेहतर हैं। इसके अलावा, कई समस्याओं के लिए एक लूप संस्करण में रिकर्सिव के रूप में एक ही मेमोरी प्रभाव होता है, बस स्टैक के बजाय ढेर पर (यदि आपकी प्रोग्रामिंग भाषा उन लोगों को अलग करती है)।
पाओलो एबरमन

6
लूप भी "बहुत खतरनाक" हो सकता है यदि आप लूप वेरिएबल को बढ़ाना भूल जाते हैं ...
h22

2
तो, वास्तव में, जानबूझकर स्टैक ओवरफ्लो का उत्पादन करना एक ऐसा कार्य है जो पुनरावृत्ति का उपयोग किए बिना बहुत मुश्किल हो जाता है।
5gon12eder

@ 5gon12eder जो हमें लाता है कि एक पुनरावर्ती एल्गोरिथ्म में ढेर अतिप्रवाह से बचने के लिए क्या तरीके हैं? - TCO, या मेमोसेशन संलग्न करने के लिए लेखन उपयोगी हो सकता है। Iterative बनाम पुनरावर्ती दृष्टिकोण भी दिलचस्प है क्योंकि यह फिबोनाची के लिए दो अलग-अलग पुनरावर्ती दृष्टिकोणों से संबंधित है।

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