आप पांच फूलों से युक्त फूलदान कैसे खाली करते हैं?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर आप फूलदान को चार फूलों से खाली करते हैं।
आप चार फूलों से युक्त फूलदान कैसे खाली करते हैं?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर आप फूलदान को तीन फूलों से खाली करते हैं।
आप तीन फूलों से युक्त फूलदान कैसे खाली करते हैं?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर आप फूलदान को दो फूलों से खाली करते हैं।
आप फूलदान को दो फूलों से कैसे खाली करते हैं?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर एक फूलदान युक्त फूलदान खाली करते हैं।
आप एक फूल से युक्त फूलदान को कैसे खाली करते हैं?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर आप फूलदान खाली कर देते हैं जिसमें कोई फूल नहीं होता है।
आप फूलदान कैसे खाली करते हैं जिसमें कोई फूल नहीं है?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं, लेकिन फूलदान खाली है इसलिए आप काम कर रहे हैं।
यह दोहराव है। आइए इसे सामान्य करें:
आप एन फूल वाले फूलदान को कैसे खाली करते हैं ?
उत्तर: यदि फूलदान खाली नहीं है, तो आप एक फूल निकालते हैं और फिर आप फूलदान को N-1 फूल से खाली करते हैं।
हम्म, क्या हम उसे कोड में देख सकते हैं?
void emptyVase( int flowersInVase ) {
if( flowersInVase > 0 ) {
// take one flower and
emptyVase( flowersInVase - 1 ) ;
} else {
// the vase is empty, nothing to do
}
}
हम्म, हम सिर्फ एक लूप में ऐसा नहीं कर सकते थे?
क्यों, हाँ, पुनरावृत्ति को पुनरावृत्ति से बदला जा सकता है, लेकिन अक्सर पुनरावृत्ति अधिक सुरुचिपूर्ण होती है।
पेड़ों की बात करते हैं। कंप्यूटर विज्ञान में, एक पेड़ एक संरचना है जो नोड्स से बना होता है , जहां प्रत्येक नोड में कुछ संख्या में बच्चे होते हैं जो नोड्स या नल भी होते हैं। एक बाइनरी ट्री नोड्स से बना एक पेड़ है जिसमें दो बच्चे हैं, जिन्हें आमतौर पर "बाएं" और "दाएं" कहा जाता है; फिर से बच्चे नोड्स या अशक्त हो सकते हैं। एक जड़ एक नोड है जो किसी अन्य नोड का बच्चा नहीं है।
कल्पना कीजिए कि एक नोड, अपने बच्चों के अलावा, एक मूल्य, एक संख्या है, और कल्पना करें कि हम किसी पेड़ में सभी मूल्यों को योग करना चाहते हैं।
किसी एक नोड में मूल्य का योग करने के लिए, हम नोड के मूल्य को उसके बाएं बच्चे के मूल्य में, यदि कोई हो, और उसके सही बच्चे के मूल्य में, यदि कोई हो, जोड़ देंगे। अब याद रखें कि बच्चे, यदि वे अशक्त नहीं हैं, तो वे भी नोड हैं।
तो बाएं बच्चे को योग करने के लिए, हम अपने स्वयं के बच्चे के मूल्य में, यदि कोई हो, और यदि कोई है, तो उसके दाएं बच्चे के मूल्य में बाल नोड का मूल्य जोड़ देगा।
इसलिए बाएं बच्चे के बाएं बच्चे के मूल्य का योग करने के लिए, हम अपने स्वयं के बाएं बच्चे के मूल्य में, यदि कोई हो, और उसके सही बच्चे के मूल्य, यदि कोई हो, के मूल्य को जोड़ देंगे।
शायद आपने अनुमान लगाया है कि मैं इसके साथ कहाँ जा रहा हूँ, और कुछ कोड देखना चाहेंगे? ठीक है:
struct node {
node* left;
node* right;
int value;
} ;
int sumNode( node* root ) {
// if there is no tree, its sum is zero
if( root == null ) {
return 0 ;
} else { // there is a tree
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
}
}
ध्यान दें कि बच्चों को यह देखने के लिए स्पष्ट रूप से परीक्षण करने के बजाय कि क्या वे शून्य या नोड्स हैं, हम केवल शून्य नोड के लिए पुनरावर्ती फ़ंक्शन को शून्य बनाते हैं।
तो कहते हैं कि हमारे पास एक पेड़ है जो इस तरह दिखता है (संख्या मान हैं, बच्चों के लिए स्लैश बिंदु और @ का अर्थ है सूचक बिंदु शून्य करने के लिए):
5
/ \
4 3
/\ /\
2 1 @ @
/\ /\
@@ @@
यदि हम रूट पर समनोड कहते हैं (मान 5 के साथ नोड), हम वापस आ जाएंगे:
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
उस जगह का विस्तार करते हैं। हर जगह हम समनोड देखते हैं, हम इसे रिटर्न स्टेटमेंट के विस्तार के साथ बदल देंगे:
sumNode( node-with-value-5);
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
return 5 + 4 + sumNode( node-with-value-2 ) + sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + sumNode(null ) + sumNode( null ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + 0 + 0 ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 ;
return 5 + 4
+ 2 + 0 + 0
+ 1
+ 3 ;
return 5 + 4
+ 2
+ 1
+ 3 ;
return 5 + 4
+ 3
+ 3 ;
return 5 + 7
+ 3 ;
return 5 + 10 ;
return 15 ;
अब देखते हैं कि कैसे हमने एक समग्र टेम्पलेट के दोहराया आवेदन के रूप में विचार करके, मनमानी गहराई और "शाखा" की संरचना पर विजय प्राप्त की? हर बार हमारे समनोडे फ़ंक्शन के माध्यम से, हम केवल एक नोड का उपयोग करते हैं, यदि एक / अगर शाखा का उपयोग करते हैं, और दो साधारण रिटर्न स्टेटमेंट जो लगभग अपने विनिर्देश से सीधे, सीधे लिखे?
How to sum a node:
If a node is null
its sum is zero
otherwise
its sum is its value
plus the sum of its left child node
plus the sum of its right child node
यही पुनरावृत्ति की शक्ति है।
ऊपर का फूलदान उदाहरण पूंछ पुनरावृत्ति का एक उदाहरण है । वह सब पूंछ पुनरावृत्ति का अर्थ है कि पुनरावर्ती कार्य में, यदि हम पुनरावर्ती होते हैं (अर्थात, यदि हम कार्य को फिर से कहते हैं), तो अंतिम कार्य हमने किया था।
पेड़ का उदाहरण पूंछ पुनरावर्ती नहीं था, क्योंकि भले ही हमने जो आखिरी काम किया था वह सही बच्चे की पुनरावृत्ति करना था, इससे पहले कि हमने किया कि हम बाएं बच्चे की पुनरावृत्ति करें।
वास्तव में, जिस क्रम में हम बच्चों को बुलाते हैं, और वर्तमान नोड के मूल्य को जोड़ते हैं वह बिल्कुल भी मायने नहीं रखता है, क्योंकि इसके अलावा सराहनीय है।
अब एक ऑपरेशन को देखते हैं जहां ऑर्डर मायने रखता है। हम नोड्स के एक द्विआधारी पेड़ का उपयोग करेंगे, लेकिन इस बार आयोजित मूल्य एक चरित्र होगा, संख्या नहीं।
हमारे पेड़ के पास एक विशेष गुण होगा, कि किसी भी नोड के लिए, उसका चरित्र वर्णमाला के क्रम के बाद ( उसके वर्णानुसार) बच्चे और उसके पहले या उससे पहले का चरित्र होता है। (वर्णमाला क्रम में) उसके दाहिने बच्चे द्वारा रखा गया चरित्र।
हम जो करना चाहते हैं, वह पेड़ को वर्णमाला के क्रम में प्रिंट करना है। यह करना आसान है, पेड़ को विशेष संपत्ति दी जाती है। हम सिर्फ बाएं बच्चे को प्रिंट करते हैं, फिर नोड के चरित्र को, फिर दाएं बच्चे को।
हम सिर्फ विली-निली प्रिंट नहीं करना चाहते हैं, इसलिए हम अपने फ़ंक्शन को प्रिंट करने के लिए पास करेंगे। यह एक प्रिंट (चार) फ़ंक्शन के साथ एक वस्तु होगी; हमें इस बारे में चिंता करने की ज़रूरत नहीं है कि यह कैसे काम करता है, बस जब प्रिंट को कॉल किया जाता है, तो यह कुछ प्रिंट करेगा, कहीं।
आइए देखते हैं कि कोड में:
struct node {
node* left;
node* right;
char value;
} ;
// don't worry about this code
class Printer {
private ostream& out;
Printer( ostream& o ) :out(o) {}
void print( char c ) { out << c; }
}
// worry about this code
int printNode( node* root, Printer& printer ) {
// if there is no tree, do nothing
if( root == null ) {
return ;
} else { // there is a tree
printNode( root->left, printer );
printer.print( value );
printNode( root->right, printer );
}
Printer printer( std::cout ) ;
node* root = makeTree() ; // this function returns a tree, somehow
printNode( root, printer );
अब परिचालन के क्रम के अलावा, यह उदाहरण दिखाता है कि हम चीजों को पुनरावर्ती कार्य में पारित कर सकते हैं। केवल एक चीज जो हमें करनी है, वह यह सुनिश्चित करें कि प्रत्येक पुनरावर्ती कॉल पर, हम इसे जारी रखना चाहते हैं। हम नोड पॉइंटर और फंक्शन के लिए एक प्रिंटर से गुजरे, और प्रत्येक पुनरावर्ती कॉल पर, हमने उन्हें "डाउन" पास किया।
अब अगर हमारा पेड़ ऐसा दिखता है:
k
/ \
h n
/\ /\
a j @ @
/\ /\
@@ i@
/\
@@
हम क्या छापेंगे?
From k, we go left to
h, where we go left to
a, where we go left to
null, where we do nothing and so
we return to a, where we print 'a' and then go right to
null, where we do nothing and so
we return to a and are done, so
we return to h, where we print 'h' and then go right to
j, where we go left to
i, where we go left to
null, where we do nothing and so
we return to i, where we print 'i' and then go right to
null, where we do nothing and so
we return to i and are done, so
we return to j, where we print 'j' and then go right to
null, where we do nothing and so
we return to j and are done, so
we return to h and are done, so
we return to k, where we print 'k' and then go right to
n where we go left to
null, where we do nothing and so
we return to n, where we print 'n' and then go right to
null, where we do nothing and so
we return to n and are done, so
we return to k and are done, so we return to the caller
इसलिए यदि हम सिर्फ उन रेखाओं को देखें जिन्हें हमने मुद्रित किया था:
we return to a, where we print 'a' and then go right to
we return to h, where we print 'h' and then go right to
we return to i, where we print 'i' and then go right to
we return to j, where we print 'j' and then go right to
we return to k, where we print 'k' and then go right to
we return to n, where we print 'n' and then go right to
हम देखते हैं कि हम "अहीजन" मुद्रित करते हैं, जो वास्तव में वर्णमाला के क्रम में है।
हम वर्णमाला के क्रम में एक एकल पेड़ को प्रिंट करने का प्रबंधन करते हैं, सिर्फ एक नोड को वर्णमाला के क्रम में प्रिंट करने का तरीका जानते हुए। जो सिर्फ (क्योंकि हमारे पेड़ के पास वर्णानुक्रमिक बाद के मूल्यों के बाईं ओर मानों को ऑर्डर करने की विशेष संपत्ति थी) नोड के मूल्य को प्रिंट करने से पहले बाएं बच्चे को प्रिंट करने और नोड के मूल्य को प्रिंट करने के बाद सही बच्चे को प्रिंट करने के लिए जानना।
और वह पुनरावृत्ति की शक्ति है: पूरी चीजों को करने में सक्षम केवल यह जानने के लिए कि पूरे का एक हिस्सा कैसे करना है (और जानने के लिए कि कब रोकना है)।
अधिकांश भाषाओं में, ऑपरेटर को याद करते हुए || ("या") शॉर्ट-सर्किट जब इसका पहला ऑपरेंड सच होता है, तो सामान्य पुनरावर्ती कार्य होता है:
void recurse() { doWeStop() || recurse(); }
ल्यूक एम टिप्पणी:
एसओ को इस तरह के जवाब के लिए बिल्ला बनाना चाहिए। बधाई हो!
धन्यवाद, ल्यूक! लेकिन, वास्तव में, क्योंकि मैंने इस उत्तर को चार से अधिक बार संपादित किया (अंतिम उदाहरण को जोड़ने के लिए, लेकिन ज्यादातर टाइपो को सही करने और इसे पॉलिश करने के लिए - एक छोटे नेटबुक कीबोर्ड पर टाइप करना कठिन है), मुझे इसके लिए और अधिक अंक नहीं मिल सकते हैं । जो कुछ हद तक मुझे भविष्य के उत्तरों में उतने ही प्रयास से हतोत्साहित करता है।
मेरी टिप्पणी यहाँ उस पर देखें: /programming/128434/what-are-community-wiki-posts-in-stackoverflow/718699#718699