सारांश
इस पाठ में कोई कोड नहीं है, और यह भूल से नहीं: एल्गोरिदम प्रोग्राम से पहले आता है, और जो सटीक रूप से वर्णन करना नहीं जानता कि क्या किया जाना चाहिए, वह उसे C++ में भी नहीं कह पाएगा। पहले यह सिखाया जाता है कि किसी समस्या को कैसे विनिर्दिष्ट करें, अर्थात कैसे घोषित करें कि वह कौन-से डेटा लेती है, उसे कौन-सा परिणाम देना है और कौन-सा संबंध उन्हें जोड़ता है, और दिखाया जाता है कि एक ही कथन अलग-अलग विनिर्देशों की अनुमति देता है। फिर एल्गोरिदम को एक परिमित, सुनिश्चित और व्यापक प्रक्रिया के रूप में परिभाषित किया जाता है, और क्रमिक घटाव द्वारा विभाजन का एल्गोरिदम क्रमांकित चरणों में लिखा जाता है। ट्रेस तालिका के साथ किसी एल्गोरिदम को हाथ से चलाना सिखाया जाता है, जो पूरे पाठ्यक्रम में समझने और डिबग करने का पहला उपकरण रहेगी। चरणों को जोड़ने के तीन तरीके (अनुक्रम, निर्णय और पुनरावृत्ति) प्रस्तुत किए जाते हैं, जिनसे हर एल्गोरिदम लिखा जाता है, और यह सीखा जाता है कि किसी ऐसी राशि के द्वारा कैसे सिद्ध करें कि पुनरावृत्ति समाप्त होती है, जो हर चक्कर में घटती है और अनंत तक नीचे नहीं जा सकती।
अधिगम उद्देश्य
- किसी सरल समस्या को उसके इनपुट, उसके आउटपुट और उन शर्तों को बताकर विनिर्दिष्ट करना जिन्हें उन्हें पूरा करना है।
- किसी एल्गोरिदम का क्रमांकित चरणों में, बिना अस्पष्टता के, स्पष्ट निर्णयों और पुनरावृत्तियों के साथ वर्णन करना।
- ट्रेस तालिका के साथ किसी एल्गोरिदम को हाथ से चलाना और किसी दिए गए इनपुट के लिए उसका परिणाम ज्ञात करना।
- किसी दी गई प्रक्रिया में अनुक्रम, निर्णय और पुनरावृत्ति को पहचानना, और यह सिद्ध करना कि कोई पुनरावृत्ति समाप्त होती है।
संगणनात्मक समस्या क्या है
संगणनात्मक समस्या दो प्रकार के डेटा के बीच एक संबंध है: वे जो प्राप्त होते हैं, जिन्हें इनपुट कहते हैं, और वे जो दिए जाने हैं, जिन्हें आउटपुट कहते हैं। उसे हल करने का अर्थ किसी एक स्थिति के लिए उत्तर पाना नहीं, बल्कि ऐसी विधि का होना है जो हर स्वीकार्य इनपुट के लिए ऐसा आउटपुट दे जो संबंध को पूरा करे। “17 को 5 से भाग देने पर कितना आता है?” एक प्रश्न है; “दो पूर्णांक दिए होने पर उनका भागफल और शेषफल ज्ञात करो” एक समस्या है, क्योंकि वह पूर्णांकों के सभी युग्मों के बारे में एक साथ बात करती है।
किंतु रोज़मर्रा की भाषा सटीक नहीं होती, और एक ही कथन को कई तरह से समझा जा सकता है। इसलिए हल करने से पहले विनिर्दिष्ट किया जाता है: लिखा जाता है कि क्या अंदर जाता है, क्या बाहर आता है और हर चीज़ को कौन-सी शर्तें पूरी करनी हैं। विनिर्देश के तीन भाग होते हैं। इनपुट डेटा को उनके प्रकार (पूर्णांक, दशमलव संख्याएँ, शब्द) के साथ और उनसे अपेक्षित शर्तों के साथ गिनाता है, और इन शर्तों को पूर्व-शर्त कहते हैं: जो हल का उपयोग करता है, वह इनका पालन करने का वचन देता है। आउटपुट परिणामों को गिनाता है। संबंध, या पश्च-शर्त, बताता है कि आउटपुट को इनपुट के सापेक्ष क्या पूरा करना है: जो हल लिखता है, वह वचन देता है कि जब भी पूर्व-शर्त का पालन हुआ हो, वह इसकी गारंटी देगा। इससे निष्कर्ष निकलता है कि विनिर्देश समस्या प्रस्तुत करने वाले और उसे हल करने वाले के बीच एक अनुबंध है, और पूर्व-शर्त के बाहर हल कोई वादा नहीं करता।
रोज़मर्रा का कथन: “तीन संख्याओं में सबसे बड़ी ढूँढ़ो”। विनिर्देश:
- इनपुट: तीन संख्याएँ , , (पूर्णांक या दशमलव, बिना किसी अन्य शर्त के)।
- आउटपुट: एक संख्या ।
- संबंध: तीनों डेटा में से एक है, और उनमें से कोई भी से बड़ा नहीं है; अर्थात , और ।
संबंध “तीनों में सबसे बड़ी” नहीं कहता, जो ठीक वही है जिसे सटीक करना था, बल्कि दो जाँचने योग्य शर्तें देता है। पहली शर्त माँगती है कि डेटा में से एक हो (इसके बिना संख्या 1000 लगभग किसी भी इनपुट के लिए दूसरी शर्त पूरी कर देती); दूसरी माँगती है कि कोई भी उससे आगे न निकले। और विनिर्देश साथ-साथ एक संदेह भी दूर कर देता है जिसे कथन ने खुला छोड़ दिया था: यदि दो डेटा अधिकतम पर बराबर हों, जैसे में, तो आउटपुट है, और इसमें कोई त्रुटि नहीं है।
एक ही कथन अलग-अलग विनिर्देशों की अनुमति देता है, और उनमें से चुनना एक निर्णय है, लापरवाही नहीं। सबसे शिक्षाप्रद स्थिति पूर्णांक विभाजन की है। पूर्णांकों और के लिए सब सहमत हैं: भागफल और शेषफल वे एकमात्र पूर्णांक हैं जो यह पूरा करते हैं
इस प्रकार, और के लिए और हैं, क्योंकि । लेकिन कथन “दो पूर्णांकों का भाग करो” यह नहीं बताता कि यदि ऋणात्मक हो तो क्या करें, और यह प्रश्न व्यर्थ नहीं है: और के लिए दो उचित उत्तर हैं।
पहला विनिर्देश: शेषफल कभी ऋणात्मक नहीं होता, । तब , और आउटपुट , है। गणित में यही पढ़ा जाता है, पाठ्यक्रम “बीजगणित और ज्यामिति I” के पाठ “विभाज्यता और शेषफल सहित विभाजन” में।
दूसरा विनिर्देश: भागफल दशमलव वाले विभाजन का परिणाम है, जिसका दशमलव भाग हटा दिया गया हो, अर्थात शून्य की ओर पूर्णांकित; शेषफल वह है जो कम पड़ता है, और उसका चिह्न भाज्य का होता है। तब कटकर हो जाता है, और : आउटपुट , है।
दोनों को पूरा करते हैं; वे शेषफल पर लगाई गई शर्त में भिन्न हैं। कोई भी त्रुटि नहीं है, लेकिन जो प्रोग्राम एक का उपयोग करते हुए यह मानता है कि वह दूसरे का उपयोग कर रहा है, वह ऋणात्मक संख्याओं के साथ गलत परिणाम देगा। एक बात पहले से बता दें: C++ दूसरा अपनाती है, जैसा कि पाठ अंकगणितीय संक्रियाएँ में दिखेगा; जो पहले की अपेक्षा करेगा, उसे आश्चर्य होगा।
एक तीसरा विकल्प भी है, जो व्यवहार में सबसे आम है: पूर्व-शर्त से उस स्थिति को बाहर कर देना। यदि विनिर्देश माँगता है, तो ऋणात्मक संख्याओं की समस्या गायब हो जाती है, इसलिए नहीं कि वह हल हो गई, बल्कि इसलिए कि हल का उपयोग करने वाले ने उसे न उठाने का वचन दिया है। जो नहीं किया जा सकता, वह है प्रश्न को अनिर्णीत छोड़ देना। और इसी कारण विनिर्देश माँगता है: होने पर ऐसे और मौजूद नहीं हैं जो संबंध को पूरा करें, और अनुबंध को यह कहना चाहिए।
एक आम त्रुटि उदाहरण से विनिर्दिष्ट करना है: “17 और 5 का आउटपुट 3 और 2 है”। उदाहरण स्पष्ट करता है, पर विनिर्दिष्ट नहीं करता, क्योंकि वह नहीं बताता कि बाकी इनपुट के साथ क्या होता है; अनंत भिन्न संबंध एक स्थिति में मेल खाते हैं। विनिर्देश एक व्यापक शर्त के रूप में लिखा जाता है, और उदाहरण बाद में उसे जाँचने के काम आते हैं।
एल्गोरिदम
समस्या विनिर्दिष्ट हो जाने के बाद उसे हल करने की विधि खोजी जाती है। एल्गोरिदम निर्देशों का एक अनुक्रम है जो, पूर्व-शर्त पूरी करने वाले किसी भी इनपुट पर लागू किए जाने पर, परिमित संख्या में चरणों में ऐसा आउटपुट देता है जो पश्च-शर्त पूरी करता है। परिभाषा में तीन अपेक्षाएँ हैं।
- परिमितता। एल्गोरिदम परिमित संख्या में निर्देशों से लिखा जाता है और, हर स्वीकार्य इनपुट के लिए, उनमें से परिमित संख्या में निर्देश चलाने के बाद समाप्त होता है। जो प्रक्रिया किसी इनपुट के लिए हमेशा चलती रहती है, वह उस इनपुट के लिए एल्गोरिदम नहीं है।
- सुनिश्चितता। हर निर्देश का एक ही अर्थ होता है, और उसे चलाने वाला हर क्षण जानता है कि क्या करना है और अगला निर्देश कौन-सा है। कोई भी निर्णय चलाने वाले के विवेक पर नहीं छोड़ा जाता।
- व्यापकता। एल्गोरिदम सभी स्वीकार्य इनपुट के लिए समस्या हल करता है, केवल एक स्थिति के लिए नहीं।
कुछ छूट लेकर, उस प्रक्रिया को भी एल्गोरिदम कहा जाता है जो सुनिश्चित है और समाप्त होती है, लेकिन किसी स्वीकार्य इनपुट के लिए पश्च-शर्त पूरी नहीं करती: कड़ाई से वह किसी अन्य समस्या का एल्गोरिदम है, और कहा जाएगा कि वह विनिर्दिष्ट समस्या के लिए गलत है। इस पाठ के प्रश्नों में इसी तरह बात की जाएगी, जो गलत एल्गोरिदमों का खंडन एक प्रति-उदाहरण से करते हैं।
खाना पकाने की विधि एल्गोरिदम जैसी लगती है, और इसीलिए यह सामान्य तुलना है; लेकिन लगभग कोई भी विधि एल्गोरिदम नहीं होती। “एक चुटकी नमक डालें” सुनिश्चित नहीं है, क्योंकि चुटकी कोई मात्रा नहीं है; “सुनहरा होने तक सेंकें” एक ऐसे निर्णय पर निर्भर है जिसे दो लोग अलग-अलग ढंग से करेंगे; “कई बार फेंटें” नहीं बताता कि कितनी बार। रसोइया इन रिक्तियों को अपने अनुभव से भरता है; कंप्यूटर के पास कोई अनुभव नहीं होता। इससे वह अपेक्षा निकलती है जो पूरे पाठ्यक्रम को नियंत्रित करती है: एल्गोरिदम का पालन वह भी कर सके जो समस्या को नहीं समझता, बिना कुछ अनुमान लगाए। यदि किसी चरण को चलाने के लिए यह जानना पड़े कि वह किस काम आता है, तो वह चरण ठीक से नहीं लिखा गया है।
इस पाठ्यक्रम के एल्गोरिदम पहले क्रमांकित चरणों में, हिंदी में, कुछ परिपाटियों के साथ लिखे जाते हैं। जो डेटा एल्गोरिदम के दौरान बदलते हैं, उन्हें अक्षरों या शब्दों से नाम दिया जाता है, जैसे या , और उन्हें चर कहते हैं। “ का मान रखें” लिखा जाता है, यह कहने के लिए कि की गणना के वर्तमान मान से की जाती है और परिणाम का नया मान बन जाता है; पिछला मान खो जाता है। संक्षिप्त लेखन में, जो नोट्स लेते समय प्रयोग होता है, वही निर्देश बाईं ओर के तीर से लिखा जाता है, जो कीबोर्ड पर r <- r - b है। निर्देश अपनी संख्या के क्रम में चलाए जाते हैं, जब तक कि उनमें से कोई कुछ और न कहे।
समस्या वही है जो पिछले अनुभाग में विनिर्दिष्ट की गई: इनपुट, दो पूर्णांक और ; आउटपुट, दो पूर्णांक और , जिनके लिए और । विचार बाँटने का है: जब तक संभव हो, में से के समूह निकाले जाते हैं, निकाले गए समूह गिने जाते हैं, और जो बचता है वह शेषफल है।
- का मान रखें, और का मान रखें।
- जब तक सत्य रहे, चरण 3 और 4 दोहराएँ; जब वह सत्य न रहे, तो चरण 5 पर जाएँ।
- का मान रखें।
- का मान रखें; चरण 2 पर लौटें।
- और आउटपुट करें, और समाप्त करें।
चरण 2 एक प्रश्न है जो हर बार पूछा जाता है: पहले चक्कर से पहले और हर चक्कर के बाद। चरण 3 और 4 पुनरावृत्ति की बॉडी बनाते हैं, यानी वह जो दोहराया जाता है। चलाने वाले के विवेक पर कुछ भी नहीं छोड़ा गया: हर क्षण केवल एक ही अगला निर्देश होता है।
यह एल्गोरिदम धीमा है (दस लाख को तीन से भाग देने में यह तीन लाख से अधिक चक्कर लगाता है), और विद्यालय की विभाजन-विधि कहीं तेज़ है; लेकिन यह सही है, और इसकी सरलता इसे एल्गोरिदमों पर विवेचन करना सीखने के लिए आदर्श बनाती है। पाठ्यक्रम सही और कुशल एल्गोरिदम के अंतर पर पाठ एल्गोरिदम की लागत में लौटेगा।
अंत में, एल्गोरिदम को उसके लेखन से अलग करना उचित है। घटाव द्वारा विभाजन का एल्गोरिदम वही रहता है चाहे उसे हिंदी में लिखा जाए, किसी आरेख में, C++ में या किसी अन्य प्रोग्रामिंग भाषा में: उसे संक्रियाओं का अनुक्रम परिभाषित करता है, शब्द नहीं। प्रोग्राम किसी एल्गोरिदम का ऐसी भाषा में लेखन है जिसे मशीन चला सके, और पाठ प्रोग्राम, कंपाइलर और C++ समझाएगा कि यह चलना कैसे होता है। यदि ऐसा है, तो कोई प्रोग्राम दो बहुत भिन्न तरीकों से गलत हो सकता है: क्योंकि एल्गोरिदम गलत है, और तब कोई भाषा उसे नहीं बचाती, या क्योंकि एल्गोरिदम सही है पर गलत लिखा गया। दोनों स्थितियों में भेद करना डिबग करने का आधा काम है, और इसीलिए पाठ्यक्रम हमेशा पहले एल्गोरिदम लिखता है।
एल्गोरिदम को हाथ से चलाना
किसी एल्गोरिदम को पढ़ लेना यह जानने के लिए काफ़ी नहीं कि वह क्या करता है; उसे चलाना पड़ता है। उसे हाथ से चलाना, जैसे मशीन चलाती, बिना चरण छोड़े और बिना परिणामों का अनुमान लगाए, प्रोग्राम लिखने वाले का सबसे उपयोगी कौशल है: इससे किसी एल्गोरिदम को लिखने से पहले जाँचा जाता है, किसी प्रोग्राम का आउटपुट उसे चलाने से पहले बताया जाता है, और विफल होने वाले प्रोग्राम की त्रुटि ढूँढ़ी जाती है।
उपकरण है ट्रेस तालिका: चलाए जा रहे चरण की संख्या के लिए एक स्तंभ, हर चर के लिए एक स्तंभ और, यदि उपयोगी हो, जाँची जाने वाली शर्तों के लिए एक स्तंभ; और चलाए गए हर चरण के लिए एक पंक्ति। हर पंक्ति में उस चर का नया मान लिखा जाता है जो उस चरण में बदला, और जो नहीं बदला उसे खाली छोड़ा जाता है, ताकि किसी चर का वर्तमान मान उसके स्तंभ में अंतिम लिखा मान हो। शर्तें उनके परिणाम के साथ लिखी जाती हैं।
हम क्रमिक घटाव द्वारा विभाजन को और के साथ चलाते हैं। चित्र का तीर उस चरण को दिखाता है जो अभी-अभी चला है; बिंदुदार रेखा याद दिलाती है कि चरण 4 चरण 2 पर लौटता है।
प्रमाण
चरण 1: प्रारंभिक मान दिए जाते हैं। इनपुट पूर्व-शर्त पूरी करता है, क्योंकि और ।
पहला चक्कर। चरण 2 में जाँचा जाता है कि , इसलिए बॉडी चलती है: चरण 3 पाँच का एक समूह निकालता है और चरण 4 उसे गिनता है।
दूसरा चक्कर: , और बॉडी फिर से चलती है।
तीसरा चक्कर: , और बॉडी फिर से चलती है।
चरण 2 में शर्त विफल होती है, क्योंकि : एल्गोरिदम चरण 5 पर जाता है और और देता है। आउटपुट पश्च-शर्त पूरी करता है।
पूरी तालिका में बारह पंक्तियाँ हैं: चरण 1, तीन-तीन पंक्तियों के तीन चक्कर (चरण 2, 3 और 4), विफल होने वाली जाँच और आउटपुट।
तालिका पर दो टिप्पणियाँ। पहली: चक्करों की संख्या भागफल, , के बराबर है, और चरण 2 बॉडी से एक बार अधिक, बार, चला; इस रूप की पुनरावृत्ति में ऐसा हमेशा होता है, क्योंकि अंतिम जाँच ही विफल होने वाली होती है। दूसरी: ट्रेस कुछ ऐसा दिखाता है जो एल्गोरिदम का लिखित रूप पहली नज़र में नहीं कहता; यदि , उदाहरण के लिए और , तो शर्त पहली बार में ही विफल हो जाती है, बॉडी एक भी बार नहीं चलती, और आउटपुट , है, जो सही है, क्योंकि । उस स्थिति को भूल जाना जिसमें पुनरावृत्ति नहीं चलती, एल्गोरिदम बनाने में सबसे आम त्रुटियों में से एक है, और छोटे इनपुट के साथ ट्रेस इसे पकड़ लेता है।
एक स्थिति का ट्रेस यह सिद्ध नहीं करता कि एल्गोरिदम सही है: वह एक स्थिति की जाँच करता है। लेकिन अच्छी तरह चुना गया ट्रेस (एक सामान्य स्थिति, एक सीमांत स्थिति, वह स्थिति जिसमें पुनरावृत्ति नहीं चलती) अधिकांश त्रुटियाँ पकड़ लेता है, और जो विफल होता है वह बताता है कि वे कहाँ हैं। पाठ सही लूप की रचना सिखाएगा कि स्थितियों से उपपत्ति तक कैसे पहुँचें।
अनुक्रम, निर्णय और पुनरावृत्ति
पास से देखें तो एल्गोरिदम बहुत थोड़े पुर्ज़ों से बनते हैं। कुछ प्रारंभिक निर्देश होते हैं (किसी चर को मान देना, कोई परिणाम देना) और उन्हें जोड़ने के तीन तरीके।
- अनुक्रम: एक निर्देश के बाद दूसरा, क्रम से। विभाजन के चरण 3 और 4 एक अनुक्रम हैं: पहले घटाया जाता है, फिर गिना जाता है।
- निर्णय: कोई शर्त पूरी होती है या नहीं, इसके अनुसार एक भाग या दूसरा चलाया जाता है (या एक भाग या कुछ नहीं)। “यदि , तो का मान रखें” एक निर्णय है।
- पुनरावृत्ति: जब तक कोई शर्त पूरी होती रहे, एक भाग बार-बार चलाया जाता है। विभाजन का चरण 2, अपनी बॉडी के साथ, एक पुनरावृत्ति है।
हर रूप का एक ही प्रवेश और एक ही निकास होता है, और उसका भीतरी भाग स्वयं तीनों में से कोई भी हो सकता है: पुनरावृत्ति की बॉडी में निर्णय हो सकते हैं, निर्णय की किसी शाखा में पुनरावृत्ति हो सकती है, और इसी प्रकार आगे। यदि ऐसा है, तो तीनों रूपों से किसी भी जटिलता के एल्गोरिदम बनाए जा सकते हैं, जैसे आपस में फिट होने वाले थोड़े-से पुर्ज़ों से कोई भी इमारत बनाई जा सकती है। कि ये हर एल्गोरिदम को व्यक्त करने के लिए पर्याप्त हैं, यह प्रोग्रामिंग के सिद्धांत का एक जाना-माना परिणाम है (इसे कोराडो बोहम और जुसेप्पे याकोपिनी ने 1966 में सिद्ध किया); यहाँ इसे सिद्ध नहीं किया जाता, लेकिन पूरा पाठ्यक्रम इसकी पुष्टि करता है: इसमें लिखे जाने वाले प्रोग्राम, चाहे कितने भी लंबे हों, इन तीन रूपों के अलावा कुछ उपयोग नहीं करेंगे, जो C++ में निर्णय के लिए if और पुनरावृत्ति के लिए while या for के रूप में लिखे जाते हैं, पाठ निर्णय और लूप में।
किसी एल्गोरिदम का रूप देखने के लिए एक उपयोगी संकेतन फ़्लोचार्ट है: हर निर्देश एक आयत है, हर शर्त दो निकासों वाला एक समचतुर्भुज (पूरी हुई, जिसे चित्र से अंकित करता है, और पूरी नहीं हुई, ), और तीर बताते हैं कि अगला चरण कौन-सा है। यह एक संकेतन है, लक्ष्य नहीं: यह किसी छोटे एल्गोरिदम को एक नज़र में देखने के काम आता है, पर बड़े पैमाने पर काम नहीं करता, और कोई भी बड़ा प्रोग्राम उसका चित्र बनाकर नहीं लिखता।
चित्र सामान्य निर्देशों और तथा एक शर्त के साथ तीनों रूप बनाता है, और फिर उन्हें क्रमिक घटाव द्वारा विभाजन के आरेख में जोड़ता है।
प्रमाण
अनुक्रम: चलता है और फिर ; एक ही प्रवेश और एक ही निकास है।
निर्णय: यदि पूरी होती है, तो चलता है; अन्यथा, । दोनों शाखाएँ एक ही निकास में मिलती हैं।
पुनरावृत्ति: जाँची जाती है; यदि वह पूरी होती है, तो चलता है और फिर से जाँची जाती है; अन्यथा, बाहर निकलते हैं। शर्त बॉडी से एक बार अधिक जाँची जाती है।
संरचना: क्रमिक घटाव द्वारा विभाजन एक अनुक्रम है (प्रारंभिक मान, पुनरावृत्ति, आउटपुट) जिसकी पुनरावृत्ति की बॉडी एक और अनुक्रम है (घटाना, गिनना)।
रोज़मर्रा की भाषा में लिखी किसी प्रक्रिया में तीनों रूपों को पहचानना उसे प्रोग्राम में बदलने का पहला कदम है। निर्णय का संकेत है “यदि”, “की स्थिति में” या “के अनुसार”; पुनरावृत्ति का, “जब तक”, “तब तक जब तक कि”, “हर एक के लिए” या “उतनी बार जितनी बार”। जिस प्रक्रिया में इनमें से कोई शब्द नहीं, वह एक अनुक्रम है, और यदि उसे बदलते आकार के इनपुट (संख्याओं की सूची, कोई टेक्स्ट) संभालने हों, तो उसमें एक पुनरावृत्ति की कमी है।
क्या यह समाप्त होता है?
परिमितता एल्गोरिदम की परिभाषा का हिस्सा है, और पुनरावृत्ति ही एकमात्र स्थान है जहाँ वह खो सकती है: पाँच चरणों का अनुक्रम पाँच चरण चलाता है, और निर्णय अपनी शाखाओं में से एक चलाता है; लेकिन पुनरावृत्ति अपनी बॉडी उतनी बार चलाती है जितनी बार शर्त पूरी होती है, और कुछ भी पहले से गारंटी नहीं देता कि वह पूरी होना बंद हो जाएगी।
यह सिद्ध करने के लिए कि कोई पुनरावृत्ति समाप्त होती है, चरों से परिकलित ऐसी राशि खोजी जाती है जो दो शर्तें पूरी करे: जब तक पुनरावृत्ति चलती रहे, वह एक प्राकृत संख्या (पूर्णांक और अऋणात्मक) हो, और हर चक्कर में निरंतर घटे। यदि ऐसा है, तो पुनरावृत्ति समाप्त होती है: प्राकृत संख्याओं का निरंतर घटता अनुक्रम अनंत नहीं हो सकता, क्योंकि हर चक्कर उसे कम से कम एक घटाता है और किसी प्राकृत संख्या से शून्य तक से अधिक सीढ़ियाँ नहीं हैं। इस राशि को वैरिएंट या सीमा फलन कहते हैं, और पाठ सही लूप की रचना इसका व्यवस्थित उपयोग करेगा।
क्रमिक घटाव द्वारा विभाजन में वैरिएंट है। जब तक पुनरावृत्ति चलती रहती है, , इसलिए एक प्राकृत संख्या है। हर चक्कर में का मान हो जाता है, और क्योंकि (पूर्व-शर्त), नया मान निश्चित रूप से छोटा है। अतः पुनरावृत्ति समाप्त होती है, और अधिक से अधिक चक्करों के बाद (वास्तव में ठीक चक्करों के बाद, यानी भागफल जितने)।
देखिए कि पूर्व-शर्त का उपयोग कहाँ हुआ। यदि एल्गोरिदम को के साथ चलाया जाए, तो चरण 3 को वैसा ही छोड़ देता है, शर्त हमेशा पूरी होती है और पुनरावृत्ति कभी समाप्त नहीं होती: एल्गोरिदम गलत नहीं है, बल्कि उसे ऐसा इनपुट दिया गया जिसे अनुबंध बाहर रखता था। और यदि ऋणात्मक होता, तो हर चक्कर में बढ़ता। पूर्व-शर्त कोई सजावट नहीं है: यह ठीक वही है जो विवेचन के काम करने के लिए आवश्यक है।
वैरिएंट की दोनों शर्तें आवश्यक हैं, और किसी में भी गलती करना आसान है। इस प्रक्रिया पर विचार करें, जिसका उद्देश्य यह तय करना है कि कोई प्राकृत संख्या सम है या नहीं: “जब तक , का मान रखें; यदि शून्य पर पहुँचें, तो सम था”। राशि हर चक्कर में घटती है, फिर भी यदि विषम है तो प्रक्रिया समाप्त नहीं होती: के साथ वह , , , से होकर गुज़रती है, और शर्त कभी विफल नहीं होती। जो पूरी नहीं होती, वह दूसरी शर्त है: प्राकृत संख्या नहीं रहती, क्योंकि पुनरावृत्ति की शर्त उसे शून्य से नीचे जाने से नहीं रोकती। सुधार यह है कि तब तक दोहराया जाए जब तक , और अंत में बचे मान, या , के अनुसार निर्णय लिया जाए; अब हर चक्कर में प्राकृत संख्या है, क्योंकि घटाया केवल तब जाता है जब , और वह घटती है: प्रक्रिया समाप्त होती है।
यह तय करना कि कोई पुनरावृत्ति समाप्त होती है या नहीं, हमेशा आसान नहीं होता। एक प्रसिद्ध उदाहरण: किसी प्राकृत संख्या से शुरू करके, जब तक , यदि सम है तो उसे दो से भाग दिया जाता है, और यदि विषम है तो उसके स्थान पर रखा जाता है। के साथ अनुक्रम है, और समाप्त होता है। किसी को ऐसा नहीं मिला जिसके लिए यह समाप्त न हो, और कंप्यूटरों से इसकी जाँच बहुत बड़ी संख्या में मानों के लिए की जा चुकी है; लेकिन कोई यह सिद्ध नहीं कर पाया कि यह हमेशा समाप्त होता है: यह कोलाट्ज़ अनुमान है, गणित की एक खुली समस्या। कोई ज्ञात राशि हर चक्कर में नहीं घटती। प्रोग्राम लिखने वाले के लिए सीख विनम्र और दृढ़ है: जिस एल्गोरिदम की समाप्ति सिद्ध करना हम नहीं जानते, वह पूरा नहीं हुआ है।
आम त्रुटियाँ
- अस्पष्ट चरण। “कई बार दोहराएँ”, “जब तक ठीक न हो जाए”, “कोई उपयुक्त संख्या लें”। हर चरण को समस्या समझे बिना चलाया जा सकना चाहिए; अन्यथा वह ठीक से नहीं लिखा गया है।
- उस स्थिति को भूल जाना जिसमें पुनरावृत्ति नहीं चलती। जिस पुनरावृत्ति की शर्त आरंभ में हो, वह अपनी बॉडी शून्य बार चला सकती है। हमेशा पूछना चाहिए कि उस स्थिति में एल्गोरिदम क्या देता है, और न्यूनतम इनपुट के साथ ट्रेस इसका उत्तर देता है।
- उदाहरण से विनिर्दिष्ट करना। “17 और 5 के लिए 3 और 2 देता है” विनिर्देश नहीं है; संबंध एक व्यापक शर्त के रूप में लिखा जाता है, और उदाहरण उसे स्पष्ट करता है।
- एल्गोरिदम को उसके प्रोग्राम से भ्रमित करना। विफल होने वाले प्रोग्राम में एल्गोरिदम गलत हो सकता है या उसका लेखन गलत हो सकता है। जिसने पहले एल्गोरिदम नहीं लिखा, वह नहीं जानता कि दोनों में से क्या खोजे।
- समाप्ति को मान लेना। किसी राशि का घटना पर्याप्त नहीं: जब तक दोहराव चलता है, उसे प्राकृत संख्या भी रहना चाहिए।
हल किए गए प्रश्न
पहले अनुभाग के तीन संख्याओं के अधिकतम का विनिर्देश पूरा करने वाला एल्गोरिदम लिखिए, और उसे इनपुट और के लिए हाथ से चलाइए।
हल
विचार: इनपुट पर क्रम से चलते हुए अब तक देखी गई सबसे बड़ी संख्या एक चर में रखी जाती है। पहला चरण: का मान रखें। इसके बाद देखे गए डेटा का अधिकतम है, जो केवल है।
दूसरा चरण: यदि , तो का मान रखें। तीसरा चरण: यदि , तो का मान रखें। चौथा चरण: आउटपुट करें। ये अनुक्रम में तीन निर्देश हैं, जिनमें से दो बिना वैकल्पिक शाखा वाले निर्णय हैं।
के लिए ट्रेस: पहले चरण के बाद ; दूसरे में असत्य है और नहीं बदलता; तीसरे में असत्य है और नहीं बदलता। दिया जाता है।
के लिए ट्रेस: पहले चरण के बाद ; दूसरे में , और का मान हो जाता है; तीसरे में असत्य है और नहीं बदलता। दिया जाता है, जो सही है: बराबरी के लिए अलग से कुछ करने की ज़रूरत नहीं।
औचित्य: हर चरण के बाद देखे गए डेटा में से एक है और उनमें से कोई उससे आगे नहीं निकलता। पहले चरण के बाद यह सत्य है, और हर निर्णय इसे बनाए रखता है: यदि नया डेटा बड़ा है, तो वही बन जाता है; अन्यथा, वह से आगे नहीं निकलता। अंत में तीनों देखे जा चुके हैं, और पश्च-शर्त पूरी करता है।
यदि लिखा होता, के स्थान पर, तो परिणाम वही रहता, क्योंकि बराबरी की स्थिति में बराबर मान दिया जाता है। और के बीच का चुनाव तब मायने रखता जब अधिकतम का स्थान भी माँगा जाता: के साथ बराबर स्थानों में से पहला दिया जाता है, और के साथ अंतिम, जिसे सूत्र कहता है।
ग्रेगोरियन कैलेंडर में कोई वर्ष लीप वर्ष होता है यदि वह से विभाज्य हो, सिवाय उनके जो से विभाज्य हैं, जो तभी लीप वर्ष होते हैं जब वे से भी विभाज्य हों। एक विद्यार्थी एल्गोरिदम प्रस्तावित करता है: “यदि वर्ष से विभाज्य है, तो हाँ दें; अन्यथा नहीं दें”। ऐसा वर्ष ढूँढ़िए जिसमें यह विफल होता है, और एक सही एल्गोरिदम उसके विनिर्देश के साथ लिखिए।
हल
विनिर्देश। इनपुट: एक पूर्णांक (ग्रेगोरियन कैलेंडर अक्टूबर 1582 से लागू है)। आउटपुट: हाँ या नहीं। संबंध: आउटपुट हाँ ठीक तब है जब , से विभाज्य हो और से नहीं, या से विभाज्य हो।
प्रस्तावित एल्गोरिदम अधिकांश वर्षों में सही बैठता है, और इसीलिए सही लगता है। इसका खंडन करने के लिए एक ऐसा वर्ष काफ़ी है जो से विभाज्य हो और से नहीं: , से विभाज्य है, और एल्गोरिदम हाँ कहता है; लेकिन लीप वर्ष नहीं था।
सुधारा गया एल्गोरिदम, तीन शृंखलाबद्ध निर्णयों में। चरण 1: यदि , से विभाज्य है, तो हाँ दें और समाप्त करें। चरण 2: यदि , से विभाज्य है, तो नहीं दें और समाप्त करें। चरण 3: यदि , से विभाज्य है, तो हाँ दें; अन्यथा नहीं दें।
प्रश्नों का क्रम आवश्यक है: वे सबसे विशेष नियम से सबसे व्यापक नियम की ओर जाते हैं। यदि पहले के बारे में पूछा जाता, तो वर्ष को अपवाद तक पहुँचने से पहले ही हाँ मिल जाता। चार वर्षों के लिए ट्रेस, हर स्थिति से एक: (चरण 1: हाँ), (चरण 2: नहीं), (चरण 3: हाँ), (चरण 3: नहीं)।
सीख केवल कैलेंडरों की नहीं है: एल्गोरिदम का खंडन एक ही प्रति-उदाहरण से होता है, और प्रति-उदाहरण नियमों की सीमाओं पर खोजे जाते हैं, सामान्य स्थितियों में नहीं। , और से परखने पर कुछ भी पता न चलता।
क्रमिक घटाव द्वारा विभाजन को और के साथ, और और के साथ हाथ से चलाइए। फिर सिद्ध कीजिए कि हर स्वीकार्य इनपुट के लिए एल्गोरिदम ठीक चक्करों के बाद समाप्त होता है और उसका आउटपुट पश्च-शर्त पूरी करता है।
हल
, के साथ ट्रेस: क्रम से , , , , होता है, और क्रम से , , , , । पाँचवीं जाँच, , विफल होती है। आउटपुट: , । यहाँ सीमांत स्थिति पूर्ण विभाजन की है: चौथा चक्कर इसलिए लगता है कि , और इसीलिए शर्त होनी चाहिए, नहीं।
, के साथ ट्रेस: पहली जाँच, , विफल होती है; बॉडी एक भी बार नहीं चलती। आउटपुट: , , जो सही है।
एक संबंध जो बना रहता है। पहले चक्कर से पहले, । हर चक्कर में , घटता है और , बढ़ता है, इसलिए नहीं बदलता। अतः यह समता हर चक्कर के बाद सत्य रहती है।
समाप्ति। जब तक दोहराव चलता है, , और एक प्राकृत संख्या है; हर चक्कर में वह घटता है। प्राकृत संख्याओं का निरंतर घटता अनुक्रम परिमित होता है: पुनरावृत्ति समाप्त होती है।
समाप्त होने पर शर्त विफल हुई: । इसके अतिरिक्त , क्योंकि तभी घटाया जाता है जब । बने रहने वाले संबंध के साथ मिलकर, आउटपुट पश्च-शर्त पूरी करता है; और क्योंकि चक्करों को गिनता है, चक्करों की संख्या भागफल है।
ऑनलाइन अभ्यास
नीचे दिए गए एल्गोरिदम को हाथ से चलाइए और खाने में वह मान लिखिए जो वह देता है, केवल अंक।
- का मान रखें, और का मान रखें।
- जब तक सत्य रहे, चरण 3 और 4 दोहराएँ; जब वह सत्य न रहे, तो चरण 5 पर जाएँ।
- यदि सम है, तो का मान रखें; यदि विषम है, तो का मान रखें।
- का मान रखें; चरण 2 पर लौटें।
- आउटपुट करें।
ब्राउज़र आपके उत्तर को तुरंत जाँचता है, पर अंक नहीं देता।
संकेत
तालिका बनाओ: पहले चक्कर में , से हो जाता है। के हर बदलाव पर एक चक्कर गिनो, जब तक का मान न हो जाए।
हल देखें
7नीचे दिया गया एल्गोरिदम, जो संक्षिप्त रूप में लिखा है, क्रमिक घटाव द्वारा विभाजन का भागफल और शेषफल निकालने वाला है, लेकिन उसमें पुनरावृत्ति की शर्त छूटी हुई है। अंडरस्कोर वाली रेखा को सही शर्त से बदलिए, बाकी कुछ बदले बिना।
ब्राउज़र आपके उत्तर को तुरंत जाँचता है, पर अंक नहीं देता।
संकेत
एल्गोरिदम को और के साथ हाथ से चलाओ: दूसरा चक्कर लगना ही चाहिए, भले ही शेषफल ठीक हो।
हल देखें
इनपुट: a (पूर्णांक, a >= 0), b (पूर्णांक, b > 0)1. q <- 02. r <- a3. जब तक r >= b दोहराएँ: r <- r - b q <- q + 14. आउटपुट q, rअभ्यास प्रश्न
हर प्रश्न के साथ एक संकेत है, जिसे स्वयं प्रयास करने के बाद ही खोलना अच्छा है, और एक उत्तर है, जो संकेत खोलने के बाद दिखता है।
समस्या “तीन संख्याओं को क्रम में लगाओ” का विनिर्देश (इनपुट, आउटपुट और संबंध) लिखिए और बताइए कि वह कौन-से ऐसे निर्णय लेने को बाध्य करता है जो कथन ने नहीं लिए थे। फिर जाँचिए कि विनिर्देश इनपुट के लिए ये दो आउटपुट अस्वीकार करता है: और , और को स्वीकार करता है।
संकेत
एक “क्रमबद्ध” आउटपुट का इनपुट से कोई लेना-देना न हो, ऐसा हो सकता है। कौन-सी शर्त आउटपुट की संख्याओं को इनपुट की संख्याओं से जोड़ती है, दोहराई गई संख्याओं सहित? आरोही या अवरोही? सख़्त या नहीं?
उत्तर
इनपुट: तीन संख्याएँ , , । आउटपुट: तीन संख्याएँ , , । संबंध: , और , का एक पुनर्विन्यास है, अर्थात उसमें वही मान उतनी ही पुनरावृत्तियों के साथ हैं। निर्णय जो कथन ने नहीं लिए थे: दिशा (यहाँ, छोटे से बड़े की ओर); यह कि क्रम सख़्त नहीं है ( और नहीं, क्योंकि दोहराए गए मानों के साथ सख़्त क्रम असंभव है); और यह कि आउटपुट एक पुनर्विन्यास होना चाहिए। क्रमबद्ध है पर उसमें एक है जो पहले नहीं था; में चार मान हैं; दोनों शर्तें पूरी करता है। पुनर्विन्यास की शर्त के बिना किसी भी इनपुट के लिए सही आउटपुट होता।
क्रमांकित चरणों में एक एल्गोरिदम लिखिए जो एक प्राकृत संख्या ले और उसके अंक अंतिम से पहले की ओर दे ( के लिए , , , देता है), केवल से भाग देने के भागफल और शेषफल का उपयोग करते हुए। इसे और के लिए हाथ से चलाइए, और सिद्ध कीजिए कि यह हर के लिए समाप्त होता है।
संकेत
से भाग देने का शेषफल अंतिम अंक है, और भागफल उस अंक के बिना की संख्या है। तय करो कि शर्त अंक देने से पहले पूछी जाए या बाद में, और देखो कि के साथ क्या होता है, जिसमें एक अंक है।
उत्तर
एक सही एल्गोरिदम: (1) को से भाग देने का शेषफल दें; (2) का मान को से भाग देने का भागफल रखें; (3) यदि , तो चरण 1 पर लौटें; अन्यथा समाप्त करें। प्रश्न बॉडी के बाद आता है, इसलिए बॉडी कम से कम एक बार चलती है: के लिए दिया जाता है, जो उसका एकमात्र अंक है। यदि प्रश्न आरंभ में पूछा जाता (“जब तक ”), तो के लिए कुछ भी न दिया जाता; यह उस स्थिति को भूलने की त्रुटि है जिसमें पुनरावृत्ति नहीं चलती। के लिए ट्रेस: , , , दिए जाते हैं, और क्रम से , , , होता है। समाप्ति: वैरिएंट है, एक प्राकृत संख्या; यदि चरण 1 पर लौटा जाता है तो इसलिए कि , और के लिए से भागफल से छोटा है, इसलिए हर चक्कर में निरंतर घटता है।
, और के सिक्के असीमित संख्या में उपलब्ध हैं। विनिर्देश: इनपुट, एक प्राकृत संख्या ; आउटपुट, तीन प्राकृत संख्याएँ , , , जिनके लिए और सिक्कों की संख्या, , न्यूनतम संभव हो। कोई व्यक्ति नीचे दिया गया एल्गोरिदम प्रस्तावित करता है। ऐसा इनपुट ढूँढ़िए जिसमें यह विफल होता है, हर दोष समझाइए और उसे सुधारिए।
- , और का मान रखें।
- यदि , तो का मान रखें और का मान रखें।
- जब तक : का मान रखें और का मान रखें।
- जब तक : का मान रखें और का मान रखें।
- , और आउटपुट करें।
संकेत
सिक्कों का योग हमेशा होता है; जो विफल हो सकती है, वह उनकी संख्या है। का कोई सटीक गुणज और से अधिक की कोई राशि आज़माओ, और स्वयं से पूछो कि चरण 2 चरणों को जोड़ने का कौन-सा तरीका प्रयोग करता है और उसे कौन-सा प्रयोग करना चाहिए।
उत्तर
दो दोष हैं। पहला, चरण 3 का प्रयोग करता है: होने पर वह का एक भी सिक्का नहीं देता और के दस सिक्के देता है, एक के स्थान पर। दूसरा, चरण 2 एक निर्णय है जहाँ पुनरावृत्ति चाहिए: होने पर, जैसा लिखा है वैसा एल्गोरिदम का एक सिक्का, के छह और के दस (सत्रह सिक्के) देता है, और चरण 3 सुधारने के बाद भी का एक और के सात (आठ) देता, जबकि के दो और के दो (चार) चाहिए। इनपुट दोनों दोष दिखाता है: एल्गोरिदम का एक, के चार और के दस, कुल पंद्रह सिक्के देता है, दो के स्थान पर। सुधार: चरण 2, “जब तक , …”; चरण 3, “जब तक , …”। इस प्रकार सुधारने पर एल्गोरिदम हमेशा वह सबसे बड़ा सिक्का लेता है जो समा सके, और इन तीन सिक्कों के साथ इससे न्यूनतम संख्या मिलती है: का अंतिम अंक केवल के सिक्कों से चुकाया जा सकता है, और जो बचता है उसे जितना संभव हो के सिक्कों से चुकाना लाभदायक है, क्योंकि के पाँच सिक्के के एक सिक्के के बराबर हैं। दूसरे सिक्कों के साथ, उदाहरण के लिए , और के सिक्के के लिए, हमेशा सबसे बड़ा लेने से न्यूनतम नहीं मिलता ( बनाम )।